orm-test.vala 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431
  1. using Invercargill.DataStructures;
  2. using Invercargill.Expressions;
  3. using InvercargillSql;
  4. using InvercargillSql.Orm;
  5. using InvercargillSql.Dialects;
  6. using InvercargillSql.Expressions;
  7. /**
  8. * Test entity for ORM tests.
  9. */
  10. public class TestUser : Object {
  11. public int64 id { get; set; }
  12. public string name { get; set; }
  13. public string email { get; set; }
  14. public int64 age { get; set; } // Use int64 for SQLite compatibility
  15. public double? salary { get; set; } // Nullable for database compatibility
  16. public bool? is_active { get; set; } // Nullable for database compatibility
  17. public DateTime? created_at { get; set; } // Nullable for database compatibility
  18. public TestUser() {
  19. name = "";
  20. email = "";
  21. }
  22. }
  23. /**
  24. * Test entity with binary data.
  25. */
  26. public class TestDocument : Object {
  27. public int64 id { get; set; }
  28. public string filename { get; set; }
  29. public Invercargill.BinaryData? content { get; set; }
  30. public TestDocument() {
  31. filename = "";
  32. }
  33. }
  34. /**
  35. * Test entity for Product ORM tests.
  36. */
  37. public class TestProduct : Object {
  38. public int64 id { get; set; }
  39. public string name { get; set; }
  40. public string category { get; set; }
  41. public double price { get; set; }
  42. public int64 stock { get; set; }
  43. public TestProduct() {
  44. name = "";
  45. category = "";
  46. }
  47. }
  48. /**
  49. * Test entity for Order ORM tests.
  50. */
  51. public class TestOrder : Object {
  52. public int64 id { get; set; }
  53. public int64 user_id { get; set; }
  54. public int64 product_id { get; set; }
  55. public int64 quantity { get; set; }
  56. public double total { get; set; }
  57. public string status { get; set; }
  58. public DateTime? created_at { get; set; }
  59. public TestOrder() {
  60. status = "";
  61. }
  62. }
  63. /**
  64. * Comprehensive tests for Invercargill-Sql ORM.
  65. */
  66. public int main(string[] args) {
  67. print("=== Invercargill-Sql ORM Tests ===\n\n");
  68. try {
  69. // ColumnType tests
  70. print("--- ColumnType Tests ---\n");
  71. test_column_type_from_gtype_int();
  72. test_column_type_from_gtype_int64();
  73. test_column_type_from_gtype_string();
  74. test_column_type_from_gtype_bool();
  75. test_column_type_from_gtype_double();
  76. test_column_type_from_gtype_float();
  77. test_column_type_from_gtype_datetime();
  78. test_column_type_from_gtype_binary();
  79. test_column_type_from_gtype_unsupported();
  80. // ColumnDefinition tests
  81. print("\n--- ColumnDefinition Tests ---\n");
  82. test_column_definition_defaults();
  83. test_column_definition_properties();
  84. // IndexDefinition tests
  85. print("\n--- IndexDefinition Tests ---\n");
  86. test_index_definition_defaults();
  87. test_index_definition_add_columns();
  88. // EntityMapperBuilder tests
  89. print("\n--- EntityMapperBuilder Tests ---\n");
  90. test_entity_mapper_builder_table_name();
  91. test_entity_mapper_builder_simple_column();
  92. test_entity_mapper_builder_chained_columns();
  93. // EntityMapper tests
  94. print("\n--- EntityMapper Tests ---\n");
  95. test_entity_mapper_build_for();
  96. test_entity_mapper_materialise();
  97. test_entity_mapper_map_from();
  98. // SqliteDialect tests
  99. print("\n--- SqliteDialect Tests ---\n");
  100. test_sqlite_dialect_translate_type_int32();
  101. test_sqlite_dialect_translate_type_int64();
  102. test_sqlite_dialect_translate_type_text();
  103. test_sqlite_dialect_translate_type_boolean();
  104. test_sqlite_dialect_translate_type_decimal();
  105. test_sqlite_dialect_translate_type_datetime();
  106. test_sqlite_dialect_translate_type_binary();
  107. test_sqlite_dialect_translate_type_uuid();
  108. test_sqlite_dialect_build_select_all();
  109. test_sqlite_dialect_build_select_by_id();
  110. test_sqlite_dialect_build_insert_sql();
  111. test_sqlite_dialect_build_update_sql();
  112. test_sqlite_dialect_build_delete_sql();
  113. // ExpressionToSqlVisitor tests
  114. print("\n--- ExpressionToSqlVisitor Tests ---\n");
  115. test_expression_visitor_binary_equal();
  116. test_expression_visitor_binary_not_equal();
  117. test_expression_visitor_binary_greater_than();
  118. test_expression_visitor_binary_greater_equal();
  119. test_expression_visitor_binary_less_than();
  120. test_expression_visitor_binary_less_equal();
  121. test_expression_visitor_binary_and();
  122. test_expression_visitor_binary_or();
  123. test_expression_visitor_unary_not();
  124. test_expression_visitor_unary_negate();
  125. test_expression_visitor_literal();
  126. test_expression_visitor_property();
  127. test_expression_visitor_complex_nested();
  128. test_expression_visitor_arithmetic();
  129. // OrmSession integration tests
  130. print("\n--- OrmSession Integration Tests ---\n");
  131. test_orm_session_register();
  132. test_orm_session_register_with_schema();
  133. test_orm_session_create_query();
  134. test_orm_session_insert();
  135. test_orm_session_query_with_where();
  136. test_orm_session_update();
  137. test_orm_session_delete();
  138. test_orm_session_order_by();
  139. test_orm_session_limit_offset();
  140. test_orm_session_first();
  141. // Schema introspection tests
  142. print("\n--- Schema Introspection Tests ---\n");
  143. test_schema_introspection_basic();
  144. test_schema_introspection_with_register();
  145. // Primary key back-population tests
  146. print("\n--- Primary Key Back-Population Tests ---\n");
  147. test_insert_back_populates_primary_key();
  148. test_insert_back_populates_different_ids();
  149. test_insert_back_populates_product();
  150. test_insert_back_populates_order();
  151. print("\n=== All ORM tests passed! ===\n");
  152. return 0;
  153. } catch (Error e) {
  154. printerr("\n=== Test failed: %s ===\n", e.message);
  155. return 1;
  156. }
  157. }
  158. // ========================================
  159. // ColumnType Tests
  160. // ========================================
  161. void test_column_type_from_gtype_int() throws Error {
  162. print("Test: ColumnType.from_gtype(int)... ");
  163. var result = ColumnType.from_gtype(typeof(int));
  164. assert(result == ColumnType.INT_32);
  165. print("PASSED\n");
  166. }
  167. void test_column_type_from_gtype_int64() throws Error {
  168. print("Test: ColumnType.from_gtype(int64)... ");
  169. var result = ColumnType.from_gtype(typeof(int64));
  170. assert(result == ColumnType.INT_64);
  171. print("PASSED\n");
  172. }
  173. void test_column_type_from_gtype_string() throws Error {
  174. print("Test: ColumnType.from_gtype(string)... ");
  175. var result = ColumnType.from_gtype(typeof(string));
  176. assert(result == ColumnType.TEXT);
  177. print("PASSED\n");
  178. }
  179. void test_column_type_from_gtype_bool() throws Error {
  180. print("Test: ColumnType.from_gtype(bool)... ");
  181. var result = ColumnType.from_gtype(typeof(bool));
  182. assert(result == ColumnType.BOOLEAN);
  183. print("PASSED\n");
  184. }
  185. void test_column_type_from_gtype_double() throws Error {
  186. print("Test: ColumnType.from_gtype(double)... ");
  187. var result = ColumnType.from_gtype(typeof(double));
  188. assert(result == ColumnType.DECIMAL);
  189. print("PASSED\n");
  190. }
  191. void test_column_type_from_gtype_float() throws Error {
  192. print("Test: ColumnType.from_gtype(float)... ");
  193. var result = ColumnType.from_gtype(typeof(float));
  194. assert(result == ColumnType.DECIMAL);
  195. print("PASSED\n");
  196. }
  197. void test_column_type_from_gtype_datetime() throws Error {
  198. print("Test: ColumnType.from_gtype(DateTime)... ");
  199. var result = ColumnType.from_gtype(typeof(DateTime));
  200. assert(result == ColumnType.DATETIME);
  201. print("PASSED\n");
  202. }
  203. void test_column_type_from_gtype_binary() throws Error {
  204. print("Test: ColumnType.from_gtype(BinaryData)... ");
  205. var result = ColumnType.from_gtype(typeof(Invercargill.BinaryData));
  206. assert(result == ColumnType.BINARY);
  207. print("PASSED\n");
  208. }
  209. void test_column_type_from_gtype_unsupported() throws Error {
  210. print("Test: ColumnType.from_gtype(unsupported)... ");
  211. var result = ColumnType.from_gtype(typeof(Object));
  212. assert(result == null);
  213. print("PASSED\n");
  214. }
  215. // ========================================
  216. // ColumnDefinition Tests
  217. // ========================================
  218. void test_column_definition_defaults() throws Error {
  219. print("Test: ColumnDefinition defaults... ");
  220. var col = new ColumnDefinition();
  221. col.name = "test_col";
  222. col.column_type = ColumnType.TEXT;
  223. assert(col.name == "test_col");
  224. assert(col.column_type == ColumnType.TEXT);
  225. print("PASSED\n");
  226. }
  227. void test_column_definition_properties() throws Error {
  228. print("Test: ColumnDefinition properties... ");
  229. var col = new ColumnDefinition();
  230. col.name = "id";
  231. col.column_type = ColumnType.INT_64;
  232. assert(col.name == "id");
  233. assert(col.column_type == ColumnType.INT_64);
  234. print("PASSED\n");
  235. }
  236. // ========================================
  237. // IndexDefinition Tests
  238. // ========================================
  239. void test_index_definition_defaults() throws Error {
  240. print("Test: IndexDefinition defaults... ");
  241. var idx = new IndexDefinition();
  242. idx.name = "idx_test";
  243. assert(idx.name == "idx_test");
  244. assert(idx.is_unique == false);
  245. assert(idx.columns != null);
  246. assert(idx.columns.count() == 0);
  247. print("PASSED\n");
  248. }
  249. void test_index_definition_add_columns() throws Error {
  250. print("Test: IndexDefinition add columns... ");
  251. var idx = new IndexDefinition();
  252. idx.name = "idx_multi";
  253. idx.is_unique = true;
  254. idx.columns.add("col1");
  255. idx.columns.add("col2");
  256. idx.columns.add("col3");
  257. assert(idx.name == "idx_multi");
  258. assert(idx.is_unique == true);
  259. assert(idx.columns.count() == 3);
  260. var arr = idx.columns.to_array();
  261. assert(arr[0] == "col1");
  262. assert(arr[1] == "col2");
  263. assert(arr[2] == "col3");
  264. print("PASSED\n");
  265. }
  266. // ========================================
  267. // EntityMapperBuilder Tests
  268. // ========================================
  269. void test_entity_mapper_builder_table_name() throws Error {
  270. print("Test: EntityMapperBuilder table name... ");
  271. var mapper = EntityMapper.build_for<TestUser>(b => {
  272. b.table("users");
  273. });
  274. assert(mapper.table_name == "users");
  275. print("PASSED\n");
  276. }
  277. void test_entity_mapper_builder_simple_column() throws Error {
  278. print("Test: EntityMapperBuilder simple column... ");
  279. var mapper = EntityMapper.build_for<TestUser>(b => {
  280. b.table("users");
  281. b.column<int64?>("id", u => u.id, (u, v) => u.id = v);
  282. b.column<string>("name", u => u.name, (u, v) => u.name = v);
  283. });
  284. assert(mapper.columns.count() == 2);
  285. var cols = mapper.columns.to_array();
  286. assert(cols[0].name == "id");
  287. assert(cols[0].column_type == ColumnType.INT_64);
  288. assert(cols[1].name == "name");
  289. assert(cols[1].column_type == ColumnType.TEXT);
  290. print("PASSED\n");
  291. }
  292. void test_entity_mapper_builder_chained_columns() throws Error {
  293. print("Test: EntityMapperBuilder chained columns... ");
  294. // The new API allows natural chaining since column<T>() returns EntityMapperBuilder<T>
  295. var mapper = EntityMapper.build_for<TestUser>(b => {
  296. b.table("users")
  297. .column<int64?>("id", u => u.id, (u, v) => u.id = v)
  298. .column<string>("name", u => u.name, (u, v) => u.name = v)
  299. .column<string>("email", u => u.email, (u, v) => u.email = v);
  300. });
  301. assert(mapper.table_name == "users");
  302. assert(mapper.columns.count() == 3);
  303. var cols = mapper.columns.to_array();
  304. assert(cols[0].name == "id");
  305. assert(cols[1].name == "name");
  306. assert(cols[2].name == "email");
  307. print("PASSED\n");
  308. }
  309. // ========================================
  310. // EntityMapper Tests
  311. // ========================================
  312. void test_entity_mapper_build_for() throws Error {
  313. print("Test: EntityMapper.build_for... ");
  314. var mapper = EntityMapper.build_for<TestUser>(b => {
  315. b.table("users");
  316. b.column<int64?>("id", u => u.id, (u, v) => u.id = v);
  317. b.column<string>("name", u => u.name, (u, v) => u.name = v);
  318. });
  319. assert(mapper != null);
  320. assert(mapper.table_name == "users");
  321. assert(mapper.columns.count() == 2);
  322. assert(mapper.property_mapper != null);
  323. print("PASSED\n");
  324. }
  325. void test_entity_mapper_materialise() throws Error {
  326. print("Test: EntityMapper.materialise... ");
  327. var mapper = EntityMapper.build_for<TestUser>(b => {
  328. b.table("users");
  329. b.column<int64?>("id", u => u.id, (u, v) => u.id = v);
  330. b.column<string>("name", u => u.name, (u, v) => u.name = v);
  331. b.column<string>("email", u => u.email, (u, v) => u.email = v);
  332. });
  333. var props = new PropertyDictionary();
  334. props.set_native<int64?>("id", 42);
  335. props.set_native<string>("name", "Alice");
  336. props.set_native<string>("email", "alice@example.com");
  337. var user = mapper.materialise(props);
  338. assert(user.id == 42);
  339. assert(user.name == "Alice");
  340. assert(user.email == "alice@example.com");
  341. print("PASSED\n");
  342. }
  343. void test_entity_mapper_map_from() throws Error {
  344. print("Test: EntityMapper.map_from... ");
  345. var mapper = EntityMapper.build_for<TestUser>(b => {
  346. b.table("users");
  347. b.column<int64?>("id", u => u.id, (u, v) => u.id = v);
  348. b.column<string>("name", u => u.name, (u, v) => u.name = v);
  349. b.column<string>("email", u => u.email, (u, v) => u.email = v);
  350. });
  351. var user = new TestUser();
  352. user.id = 123;
  353. user.name = "Bob";
  354. user.email = "bob@example.com";
  355. var props = mapper.map_from(user);
  356. assert(props != null);
  357. var id_elem = props.get("id");
  358. var name_elem = props.get("name");
  359. var email_elem = props.get("email");
  360. assert(id_elem != null);
  361. assert(name_elem != null);
  362. assert(email_elem != null);
  363. print("PASSED\n");
  364. }
  365. // ========================================
  366. // SqliteDialect Tests
  367. // ========================================
  368. void test_sqlite_dialect_translate_type_int32() throws Error {
  369. print("Test: SqliteDialect.translate_type(INT_32)... ");
  370. var dialect = new SqliteDialect();
  371. var result = dialect.translate_type(ColumnType.INT_32);
  372. assert(result == "INTEGER");
  373. print("PASSED\n");
  374. }
  375. void test_sqlite_dialect_translate_type_int64() throws Error {
  376. print("Test: SqliteDialect.translate_type(INT_64)... ");
  377. var dialect = new SqliteDialect();
  378. var result = dialect.translate_type(ColumnType.INT_64);
  379. assert(result == "INTEGER");
  380. print("PASSED\n");
  381. }
  382. void test_sqlite_dialect_translate_type_text() throws Error {
  383. print("Test: SqliteDialect.translate_type(TEXT)... ");
  384. var dialect = new SqliteDialect();
  385. var result = dialect.translate_type(ColumnType.TEXT);
  386. assert(result == "TEXT");
  387. print("PASSED\n");
  388. }
  389. void test_sqlite_dialect_translate_type_boolean() throws Error {
  390. print("Test: SqliteDialect.translate_type(BOOLEAN)... ");
  391. var dialect = new SqliteDialect();
  392. var result = dialect.translate_type(ColumnType.BOOLEAN);
  393. assert(result == "INTEGER");
  394. print("PASSED\n");
  395. }
  396. void test_sqlite_dialect_translate_type_decimal() throws Error {
  397. print("Test: SqliteDialect.translate_type(DECIMAL)... ");
  398. var dialect = new SqliteDialect();
  399. var result = dialect.translate_type(ColumnType.DECIMAL);
  400. assert(result == "REAL");
  401. print("PASSED\n");
  402. }
  403. void test_sqlite_dialect_translate_type_datetime() throws Error {
  404. print("Test: SqliteDialect.translate_type(DATETIME)... ");
  405. var dialect = new SqliteDialect();
  406. var result = dialect.translate_type(ColumnType.DATETIME);
  407. assert(result == "INTEGER");
  408. print("PASSED\n");
  409. }
  410. void test_sqlite_dialect_translate_type_binary() throws Error {
  411. print("Test: SqliteDialect.translate_type(BINARY)... ");
  412. var dialect = new SqliteDialect();
  413. var result = dialect.translate_type(ColumnType.BINARY);
  414. assert(result == "BLOB");
  415. print("PASSED\n");
  416. }
  417. void test_sqlite_dialect_translate_type_uuid() throws Error {
  418. print("Test: SqliteDialect.translate_type(UUID)... ");
  419. var dialect = new SqliteDialect();
  420. var result = dialect.translate_type(ColumnType.UUID);
  421. assert(result == "TEXT");
  422. print("PASSED\n");
  423. }
  424. void test_sqlite_dialect_build_select_all() throws Error {
  425. print("Test: SqliteDialect.build_select_all... ");
  426. var dialect = new SqliteDialect();
  427. var sql = dialect.build_select_all("users");
  428. assert(sql == "SELECT * FROM users");
  429. print("PASSED\n");
  430. }
  431. void test_sqlite_dialect_build_select_by_id() throws Error {
  432. print("Test: SqliteDialect.build_select_by_id... ");
  433. var dialect = new SqliteDialect();
  434. var sql = dialect.build_select_by_id("users", "id");
  435. assert(sql == "SELECT * FROM users WHERE id = :id");
  436. print("PASSED\n");
  437. }
  438. void test_sqlite_dialect_build_insert_sql() throws Error {
  439. print("Test: SqliteDialect.build_insert_sql... ");
  440. var dialect = new SqliteDialect();
  441. var columns = new Vector<string>();
  442. columns.add("name");
  443. columns.add("email");
  444. var sql = dialect.build_insert_sql("users", columns);
  445. assert(sql == "INSERT INTO users (name, email) VALUES (:name, :email)");
  446. print("PASSED\n");
  447. }
  448. void test_sqlite_dialect_build_update_sql() throws Error {
  449. print("Test: SqliteDialect.build_update_sql... ");
  450. var dialect = new SqliteDialect();
  451. var columns = new Vector<string>();
  452. columns.add("id");
  453. columns.add("name");
  454. columns.add("email");
  455. var sql = dialect.build_update_sql("users", columns, "id");
  456. // id should be excluded from SET clause
  457. assert("UPDATE users SET" in sql);
  458. assert("name = :name" in sql);
  459. assert("email = :email" in sql);
  460. assert("WHERE id = :id" in sql);
  461. print("PASSED\n");
  462. }
  463. void test_sqlite_dialect_build_delete_sql() throws Error {
  464. print("Test: SqliteDialect.build_delete_sql... ");
  465. var dialect = new SqliteDialect();
  466. var sql = dialect.build_delete_sql("users", "id");
  467. assert(sql == "DELETE FROM users WHERE id = :id");
  468. print("PASSED\n");
  469. }
  470. // ========================================
  471. // ExpressionToSqlVisitor Tests
  472. // ========================================
  473. EntityMapper get_test_mapper() {
  474. return EntityMapper.build_for<TestUser>(b => {
  475. b.table("users");
  476. b.column<int64?>("id", u => u.id, (u, v) => u.id = v);
  477. b.column<string>("name", u => u.name, (u, v) => u.name = v);
  478. b.column<int64?>("age", u => u.age, (u, v) => u.age = v);
  479. });
  480. }
  481. void test_expression_visitor_binary_equal() throws Error {
  482. print("Test: ExpressionToSqlVisitor binary equal... ");
  483. var dialect = new SqliteDialect();
  484. var mapper = get_test_mapper();
  485. var visitor = new ExpressionToSqlVisitor(dialect, mapper);
  486. var left = new PropertyExpression(new VariableExpression("u"), "age");
  487. var right = new LiteralExpression(new Invercargill.NativeElement<int?>(25));
  488. var expr = new BinaryExpression(left, right, BinaryOperator.EQUAL);
  489. expr.accept(visitor);
  490. var sql = visitor.get_sql();
  491. assert("(age = :p1)" == sql);
  492. assert(visitor.get_parameters().count() == 1);
  493. print("PASSED\n");
  494. }
  495. void test_expression_visitor_binary_not_equal() throws Error {
  496. print("Test: ExpressionToSqlVisitor binary not equal... ");
  497. var dialect = new SqliteDialect();
  498. var mapper = get_test_mapper();
  499. var visitor = new ExpressionToSqlVisitor(dialect, mapper);
  500. var left = new PropertyExpression(new VariableExpression("u"), "age");
  501. var right = new LiteralExpression(new Invercargill.NativeElement<int?>(25));
  502. var expr = new BinaryExpression(left, right, BinaryOperator.NOT_EQUAL);
  503. expr.accept(visitor);
  504. var sql = visitor.get_sql();
  505. assert("(age <> :p1)" == sql);
  506. print("PASSED\n");
  507. }
  508. void test_expression_visitor_binary_greater_than() throws Error {
  509. print("Test: ExpressionToSqlVisitor binary greater than... ");
  510. var dialect = new SqliteDialect();
  511. var mapper = get_test_mapper();
  512. var visitor = new ExpressionToSqlVisitor(dialect, mapper);
  513. var left = new PropertyExpression(new VariableExpression("u"), "age");
  514. var right = new LiteralExpression(new Invercargill.NativeElement<int?>(18));
  515. var expr = new BinaryExpression(left, right, BinaryOperator.GREATER_THAN);
  516. expr.accept(visitor);
  517. var sql = visitor.get_sql();
  518. assert("(age > :p1)" == sql);
  519. print("PASSED\n");
  520. }
  521. void test_expression_visitor_binary_greater_equal() throws Error {
  522. print("Test: ExpressionToSqlVisitor binary greater equal... ");
  523. var dialect = new SqliteDialect();
  524. var mapper = get_test_mapper();
  525. var visitor = new ExpressionToSqlVisitor(dialect, mapper);
  526. var left = new PropertyExpression(new VariableExpression("u"), "age");
  527. var right = new LiteralExpression(new Invercargill.NativeElement<int?>(18));
  528. var expr = new BinaryExpression(left, right, BinaryOperator.GREATER_EQUAL);
  529. expr.accept(visitor);
  530. var sql = visitor.get_sql();
  531. assert("(age >= :p1)" == sql);
  532. print("PASSED\n");
  533. }
  534. void test_expression_visitor_binary_less_than() throws Error {
  535. print("Test: ExpressionToSqlVisitor binary less than... ");
  536. var dialect = new SqliteDialect();
  537. var mapper = get_test_mapper();
  538. var visitor = new ExpressionToSqlVisitor(dialect, mapper);
  539. var left = new PropertyExpression(new VariableExpression("u"), "age");
  540. var right = new LiteralExpression(new Invercargill.NativeElement<int?>(65));
  541. var expr = new BinaryExpression(left, right, BinaryOperator.LESS_THAN);
  542. expr.accept(visitor);
  543. var sql = visitor.get_sql();
  544. assert("(age < :p1)" == sql);
  545. print("PASSED\n");
  546. }
  547. void test_expression_visitor_binary_less_equal() throws Error {
  548. print("Test: ExpressionToSqlVisitor binary less equal... ");
  549. var dialect = new SqliteDialect();
  550. var mapper = get_test_mapper();
  551. var visitor = new ExpressionToSqlVisitor(dialect, mapper);
  552. var left = new PropertyExpression(new VariableExpression("u"), "age");
  553. var right = new LiteralExpression(new Invercargill.NativeElement<int?>(65));
  554. var expr = new BinaryExpression(left, right, BinaryOperator.LESS_EQUAL);
  555. expr.accept(visitor);
  556. var sql = visitor.get_sql();
  557. assert("(age <= :p1)" == sql);
  558. print("PASSED\n");
  559. }
  560. void test_expression_visitor_binary_and() throws Error {
  561. print("Test: ExpressionToSqlVisitor binary AND... ");
  562. var dialect = new SqliteDialect();
  563. var mapper = get_test_mapper();
  564. var visitor = new ExpressionToSqlVisitor(dialect, mapper);
  565. var left = new BinaryExpression(
  566. new PropertyExpression(new VariableExpression("u"), "age"),
  567. new LiteralExpression(new Invercargill.NativeElement<int?>(18)),
  568. BinaryOperator.GREATER_THAN
  569. );
  570. var right = new BinaryExpression(
  571. new PropertyExpression(new VariableExpression("u"), "age"),
  572. new LiteralExpression(new Invercargill.NativeElement<int?>(65)),
  573. BinaryOperator.LESS_THAN
  574. );
  575. var expr = new BinaryExpression(left, right, BinaryOperator.AND);
  576. expr.accept(visitor);
  577. var sql = visitor.get_sql();
  578. assert("((age > :p1) AND (age < :p2))" == sql);
  579. assert(visitor.get_parameters().count() == 2);
  580. print("PASSED\n");
  581. }
  582. void test_expression_visitor_binary_or() throws Error {
  583. print("Test: ExpressionToSqlVisitor binary OR... ");
  584. var dialect = new SqliteDialect();
  585. var mapper = get_test_mapper();
  586. var visitor = new ExpressionToSqlVisitor(dialect, mapper);
  587. var left = new BinaryExpression(
  588. new PropertyExpression(new VariableExpression("u"), "name"),
  589. new LiteralExpression(new Invercargill.NativeElement<string>("Alice")),
  590. BinaryOperator.EQUAL
  591. );
  592. var right = new BinaryExpression(
  593. new PropertyExpression(new VariableExpression("u"), "name"),
  594. new LiteralExpression(new Invercargill.NativeElement<string>("Bob")),
  595. BinaryOperator.EQUAL
  596. );
  597. var expr = new BinaryExpression(left, right, BinaryOperator.OR);
  598. expr.accept(visitor);
  599. var sql = visitor.get_sql();
  600. assert("((name = :p1) OR (name = :p2))" == sql);
  601. assert(visitor.get_parameters().count() == 2);
  602. print("PASSED\n");
  603. }
  604. void test_expression_visitor_unary_not() throws Error {
  605. print("Test: ExpressionToSqlVisitor unary NOT... ");
  606. var dialect = new SqliteDialect();
  607. var mapper = get_test_mapper();
  608. var visitor = new ExpressionToSqlVisitor(dialect, mapper);
  609. var operand = new BinaryExpression(
  610. new PropertyExpression(new VariableExpression("u"), "age"),
  611. new LiteralExpression(new Invercargill.NativeElement<int?>(25)),
  612. BinaryOperator.EQUAL
  613. );
  614. var expr = new UnaryExpression(UnaryOperator.NOT, operand);
  615. expr.accept(visitor);
  616. var sql = visitor.get_sql();
  617. assert("NOT (age = :p1)" == sql);
  618. print("PASSED\n");
  619. }
  620. void test_expression_visitor_unary_negate() throws Error {
  621. print("Test: ExpressionToSqlVisitor unary NEGATE... ");
  622. var dialect = new SqliteDialect();
  623. var mapper = get_test_mapper();
  624. var visitor = new ExpressionToSqlVisitor(dialect, mapper);
  625. var operand = new PropertyExpression(new VariableExpression("u"), "age");
  626. var expr = new UnaryExpression(UnaryOperator.NEGATE, operand);
  627. expr.accept(visitor);
  628. var sql = visitor.get_sql();
  629. assert("-age" == sql);
  630. print("PASSED\n");
  631. }
  632. void test_expression_visitor_literal() throws Error {
  633. print("Test: ExpressionToSqlVisitor literal... ");
  634. var dialect = new SqliteDialect();
  635. var mapper = get_test_mapper();
  636. var visitor = new ExpressionToSqlVisitor(dialect, mapper);
  637. var expr = new LiteralExpression(new Invercargill.NativeElement<string>("test"));
  638. expr.accept(visitor);
  639. var sql = visitor.get_sql();
  640. assert(":p1" == sql);
  641. assert(visitor.get_parameters().count() == 1);
  642. print("PASSED\n");
  643. }
  644. void test_expression_visitor_property() throws Error {
  645. print("Test: ExpressionToSqlVisitor property... ");
  646. var dialect = new SqliteDialect();
  647. var mapper = get_test_mapper();
  648. var visitor = new ExpressionToSqlVisitor(dialect, mapper);
  649. var expr = new PropertyExpression(new VariableExpression("u"), "name");
  650. expr.accept(visitor);
  651. var sql = visitor.get_sql();
  652. assert("name" == sql);
  653. print("PASSED\n");
  654. }
  655. void test_expression_visitor_complex_nested() throws Error {
  656. print("Test: ExpressionToSqlVisitor complex nested... ");
  657. var dialect = new SqliteDialect();
  658. var mapper = get_test_mapper();
  659. var visitor = new ExpressionToSqlVisitor(dialect, mapper);
  660. // (age > 18 AND age < 65) OR (name = 'Admin')
  661. var age_range = new BinaryExpression(
  662. new BinaryExpression(
  663. new PropertyExpression(new VariableExpression("u"), "age"),
  664. new LiteralExpression(new Invercargill.NativeElement<int?>(18)),
  665. BinaryOperator.GREATER_THAN
  666. ),
  667. new BinaryExpression(
  668. new PropertyExpression(new VariableExpression("u"), "age"),
  669. new LiteralExpression(new Invercargill.NativeElement<int?>(65)),
  670. BinaryOperator.LESS_THAN
  671. ),
  672. BinaryOperator.AND
  673. );
  674. var is_admin = new BinaryExpression(
  675. new PropertyExpression(new VariableExpression("u"), "name"),
  676. new LiteralExpression(new Invercargill.NativeElement<string>("Admin")),
  677. BinaryOperator.EQUAL
  678. );
  679. var expr = new BinaryExpression(age_range, is_admin, BinaryOperator.OR);
  680. expr.accept(visitor);
  681. var sql = visitor.get_sql();
  682. assert("(((age > :p1) AND (age < :p2)) OR (name = :p3))" == sql);
  683. assert(visitor.get_parameters().count() == 3);
  684. print("PASSED\n");
  685. }
  686. void test_expression_visitor_arithmetic() throws Error {
  687. print("Test: ExpressionToSqlVisitor arithmetic... ");
  688. var dialect = new SqliteDialect();
  689. var mapper = get_test_mapper();
  690. var visitor = new ExpressionToSqlVisitor(dialect, mapper);
  691. // age + 10
  692. var expr = new BinaryExpression(
  693. new PropertyExpression(new VariableExpression("u"), "age"),
  694. new LiteralExpression(new Invercargill.NativeElement<int?>(10)),
  695. BinaryOperator.ADD
  696. );
  697. expr.accept(visitor);
  698. var sql = visitor.get_sql();
  699. assert("(age + :p1)" == sql);
  700. print("PASSED\n");
  701. }
  702. // ========================================
  703. // OrmSession Integration Tests
  704. // ========================================
  705. OrmSession setup_test_session() throws SqlError {
  706. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  707. var session = new OrmSession(conn, new SqliteDialect());
  708. // Create table first (schema is managed by migrations)
  709. conn.execute("""
  710. CREATE TABLE users (
  711. id INTEGER PRIMARY KEY AUTOINCREMENT,
  712. name TEXT NOT NULL,
  713. email TEXT,
  714. age INTEGER,
  715. salary REAL,
  716. is_active INTEGER,
  717. created_at INTEGER
  718. )
  719. """);
  720. // Register TestUser mapper with schema introspection
  721. session.register_with_schema<TestUser>("users", b => {
  722. b.column<int64?>("id", u => u.id, (u, v) => u.id = v);
  723. b.column<string>("name", u => u.name, (u, v) => u.name = v);
  724. b.column<string>("email", u => u.email, (u, v) => u.email = v);
  725. b.column<int64?>("age", u => u.age, (u, v) => u.age = v);
  726. b.column<double?>("salary", u => u.salary, (u, v) => u.salary = v);
  727. b.column<bool?>("is_active", u => u.is_active, (u, v) => u.is_active = v);
  728. b.column<DateTime?>("created_at", u => u.created_at, (u, v) => u.created_at = v);
  729. });
  730. return session;
  731. }
  732. void test_orm_session_register() throws Error {
  733. print("Test: OrmSession register... ");
  734. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  735. var session = new OrmSession(conn, new SqliteDialect());
  736. // Create table first
  737. conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
  738. // Register using the simplified API (without schema methods)
  739. session.register<TestUser>(b => {
  740. b.table("users");
  741. b.column<int64?>("id", u => u.id, (u, v) => u.id = v);
  742. b.column<string>("name", u => u.name, (u, v) => u.name = v);
  743. });
  744. // If we got here without exception, registration worked
  745. print("PASSED\n");
  746. conn.close();
  747. }
  748. void test_orm_session_register_with_schema() throws Error {
  749. print("Test: OrmSession register_with_schema... ");
  750. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  751. var session = new OrmSession(conn, new SqliteDialect());
  752. // Create table with auto-increment primary key
  753. conn.execute("""
  754. CREATE TABLE users (
  755. id INTEGER PRIMARY KEY AUTOINCREMENT,
  756. name TEXT NOT NULL,
  757. email TEXT
  758. )
  759. """);
  760. // Register with schema introspection - PK and auto-increment discovered automatically
  761. session.register_with_schema<TestUser>("users", b => {
  762. b.column<int64?>("id", u => u.id, (u, v) => u.id = v);
  763. b.column<string>("name", u => u.name, (u, v) => u.name = v);
  764. b.column<string>("email", u => u.email, (u, v) => u.email = v);
  765. });
  766. // Verify the mapper has the schema information
  767. var mapper = session.get_mapper<TestUser>();
  768. assert(mapper != null);
  769. assert(mapper.table_name == "users");
  770. assert(mapper.get_effective_primary_key() == "id");
  771. assert(mapper.is_auto_increment("id") == true);
  772. print("PASSED\n");
  773. conn.close();
  774. }
  775. void test_orm_session_create_query() throws Error {
  776. print("Test: OrmSession create query... ");
  777. var session = setup_test_session();
  778. var query = session.query<TestUser>();
  779. assert(query != null);
  780. print("PASSED\n");
  781. }
  782. void test_orm_session_insert() throws Error {
  783. print("Test: OrmSession insert... ");
  784. var session = setup_test_session();
  785. var user = new TestUser();
  786. user.name = "Alice";
  787. user.email = "alice@example.com";
  788. user.age = 30;
  789. user.salary = 50000.0;
  790. user.is_active = true;
  791. user.created_at = new DateTime.now_utc();
  792. session.insert(user);
  793. // Verify insert worked by querying
  794. var results = session.query<TestUser>().materialise();
  795. var arr = results.to_array();
  796. assert(arr.length == 1);
  797. var inserted = arr[0];
  798. assert(inserted != null);
  799. assert(inserted.name == "Alice");
  800. assert(inserted.email == "alice@example.com");
  801. assert(inserted.age == 30);
  802. print("PASSED\n");
  803. }
  804. void test_orm_session_query_with_where() throws Error {
  805. print("Test: OrmSession query with where... ");
  806. var session = setup_test_session();
  807. // Insert test data
  808. var alice = new TestUser();
  809. alice.name = "Alice";
  810. alice.age = 30;
  811. session.insert(alice);
  812. var bob = new TestUser();
  813. bob.name = "Bob";
  814. bob.age = 25;
  815. session.insert(bob);
  816. var charlie = new TestUser();
  817. charlie.name = "Charlie";
  818. charlie.age = 35;
  819. session.insert(charlie);
  820. // Query with where clause
  821. var results = session.query<TestUser>()
  822. .where("age > 28")
  823. .materialise();
  824. var arr = results.to_array();
  825. assert(arr.length == 2);
  826. print("PASSED\n");
  827. }
  828. void test_orm_session_update() throws Error {
  829. print("Test: OrmSession update... ");
  830. var session = setup_test_session();
  831. // Insert test data
  832. var user = new TestUser();
  833. user.name = "Alice";
  834. user.email = "alice@example.com";
  835. user.age = 30;
  836. session.insert(user);
  837. // Get the inserted user with ID - use age for lookup since string literals
  838. // in expressions aren't supported by the expression parser
  839. var inserted_arr = session.query<TestUser>()
  840. .where("age == 30")
  841. .first();
  842. assert(inserted_arr != null);
  843. // Update the user
  844. inserted_arr.name = "Alice Updated";
  845. inserted_arr.age = 31;
  846. session.update(inserted_arr);
  847. // Verify update - use the ID we now know
  848. var updated = session.query<TestUser>()
  849. .where("id == " + inserted_arr.id.to_string())
  850. .first();
  851. assert(updated != null);
  852. assert(updated.name == "Alice Updated");
  853. assert(updated.age == 31);
  854. print("PASSED\n");
  855. }
  856. void test_orm_session_delete() throws Error {
  857. print("Test: OrmSession delete... ");
  858. var session = setup_test_session();
  859. // Insert test data
  860. var user = new TestUser();
  861. user.name = "ToDelete";
  862. user.age = 99;
  863. session.insert(user);
  864. // Get the inserted user with ID - use age for lookup
  865. var inserted = session.query<TestUser>()
  866. .where("age == 99")
  867. .first();
  868. assert(inserted != null);
  869. // Delete the user
  870. session.delete(inserted);
  871. // Verify deletion - use the ID
  872. var results = session.query<TestUser>()
  873. .where("id == " + inserted.id.to_string())
  874. .materialise();
  875. var arr = results.to_array();
  876. assert(arr.length == 0);
  877. print("PASSED\n");
  878. }
  879. void test_orm_session_order_by() throws Error {
  880. print("Test: OrmSession order by... ");
  881. var session = setup_test_session();
  882. // Insert test data
  883. var alice = new TestUser();
  884. alice.name = "Alice";
  885. alice.age = 30;
  886. session.insert(alice);
  887. var bob = new TestUser();
  888. bob.name = "Bob";
  889. bob.age = 25;
  890. session.insert(bob);
  891. var charlie = new TestUser();
  892. charlie.name = "Charlie";
  893. charlie.age = 35;
  894. session.insert(charlie);
  895. // Query with order by ascending
  896. var results = session.query<TestUser>()
  897. .order_by("age")
  898. .materialise();
  899. var arr = results.to_array();
  900. assert(arr.length == 3);
  901. assert(arr[0].age == 25); // Bob
  902. assert(arr[1].age == 30); // Alice
  903. assert(arr[2].age == 35); // Charlie
  904. // Query with order by descending
  905. var desc_results = session.query<TestUser>()
  906. .order_by_desc("age")
  907. .materialise();
  908. var desc_arr = desc_results.to_array();
  909. assert(desc_arr.length == 3);
  910. assert(desc_arr[0].age == 35); // Charlie
  911. assert(desc_arr[1].age == 30); // Alice
  912. assert(desc_arr[2].age == 25); // Bob
  913. print("PASSED\n");
  914. }
  915. void test_orm_session_limit_offset() throws Error {
  916. print("Test: OrmSession limit/offset... ");
  917. var session = setup_test_session();
  918. // Insert test data
  919. for (int i = 0; i < 10; i++) {
  920. var user = new TestUser();
  921. user.name = "User%d".printf(i);
  922. user.age = 20 + i;
  923. session.insert(user);
  924. }
  925. // Query with limit
  926. var limited = session.query<TestUser>()
  927. .order_by("age")
  928. .limit(3)
  929. .materialise();
  930. var limited_arr = limited.to_array();
  931. assert(limited_arr.length == 3);
  932. // Query with limit and offset
  933. var paged = session.query<TestUser>()
  934. .order_by("age")
  935. .limit(3)
  936. .offset(3)
  937. .materialise();
  938. var paged_arr = paged.to_array();
  939. assert(paged_arr.length == 3);
  940. assert(paged_arr[0].age == 23); // Skipped 20, 21, 22
  941. print("PASSED\n");
  942. }
  943. void test_orm_session_first() throws Error {
  944. print("Test: OrmSession first... ");
  945. var session = setup_test_session();
  946. // Insert test data
  947. var alice = new TestUser();
  948. alice.name = "Alice";
  949. alice.age = 30;
  950. session.insert(alice);
  951. var bob = new TestUser();
  952. bob.name = "Bob";
  953. bob.age = 25;
  954. session.insert(bob);
  955. // Get first with order
  956. var first = session.query<TestUser>()
  957. .order_by("age")
  958. .first();
  959. assert(first != null);
  960. assert(first.name == "Bob"); // Youngest
  961. assert(first.age == 25);
  962. // Query empty result
  963. var empty = session.query<TestUser>()
  964. .where("age > 100")
  965. .first();
  966. assert(empty == null);
  967. print("PASSED\n");
  968. }
  969. // ========================================
  970. // Schema Introspection Tests
  971. // ========================================
  972. void test_schema_introspection_basic() throws Error {
  973. print("Test: Schema introspection basic... ");
  974. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  975. var dialect = new SqliteDialect();
  976. // Create a table with various column types
  977. conn.execute("""
  978. CREATE TABLE test_table (
  979. id INTEGER PRIMARY KEY AUTOINCREMENT,
  980. name TEXT NOT NULL,
  981. email TEXT,
  982. age INTEGER,
  983. salary REAL,
  984. created_at INTEGER
  985. )
  986. """);
  987. // Introspect the schema
  988. var schema = dialect.introspect_schema(conn, "test_table");
  989. assert(schema != null);
  990. assert(schema.table_name == "test_table");
  991. assert(schema.primary_key_column == "id");
  992. assert(schema.columns.count() == 6);
  993. // Find the id column and verify it's marked as PK and auto-increment
  994. var id_col = schema.get_column("id");
  995. assert(id_col != null);
  996. assert(id_col.is_primary_key == true);
  997. assert(id_col.auto_increment == true);
  998. // Find the name column and verify it's required
  999. var name_col = schema.get_column("name");
  1000. assert(name_col != null);
  1001. assert(name_col.is_required == true);
  1002. print("PASSED\n");
  1003. conn.close();
  1004. }
  1005. void test_schema_introspection_with_register() throws Error {
  1006. print("Test: Schema introspection with register... ");
  1007. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  1008. var session = new OrmSession(conn, new SqliteDialect());
  1009. // Create table with composite structure
  1010. conn.execute("""
  1011. CREATE TABLE products (
  1012. id INTEGER PRIMARY KEY AUTOINCREMENT,
  1013. name TEXT NOT NULL,
  1014. price REAL NOT NULL,
  1015. description TEXT,
  1016. created_at INTEGER
  1017. )
  1018. """);
  1019. // Define a simple product class inline using TestUser as proxy
  1020. // Register with schema introspection
  1021. session.register_with_schema<TestUser>("products", b => {
  1022. b.column<int64?>("id", u => u.id, (u, v) => u.id = v);
  1023. b.column<string>("name", u => u.name, (u, v) => u.name = v);
  1024. });
  1025. // Verify schema was introspected and applied
  1026. var mapper = session.get_mapper<TestUser>();
  1027. assert(mapper != null);
  1028. assert(mapper.table_name == "products");
  1029. assert(mapper.get_effective_primary_key() == "id");
  1030. print("PASSED\n");
  1031. conn.close();
  1032. }
  1033. // ========================================
  1034. // Primary Key Back-Population Tests
  1035. // ========================================
  1036. /**
  1037. * Helper to set up a session with products table for testing.
  1038. */
  1039. OrmSession setup_product_session() throws SqlError {
  1040. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  1041. var session = new OrmSession(conn, new SqliteDialect());
  1042. conn.execute("""
  1043. CREATE TABLE products (
  1044. id INTEGER PRIMARY KEY AUTOINCREMENT,
  1045. name TEXT NOT NULL,
  1046. category TEXT,
  1047. price REAL,
  1048. stock INTEGER
  1049. )
  1050. """);
  1051. session.register_with_schema<TestProduct>("products", b => {
  1052. b.column<int64?>("id", p => p.id, (p, v) => p.id = v);
  1053. b.column<string>("name", p => p.name, (p, v) => p.name = v);
  1054. b.column<string>("category", p => p.category, (p, v) => p.category = v);
  1055. b.column<double?>("price", p => p.price, (p, v) => p.price = v);
  1056. b.column<int64?>("stock", p => p.stock, (p, v) => p.stock = v);
  1057. });
  1058. return session;
  1059. }
  1060. /**
  1061. * Helper to set up a session with orders table for testing.
  1062. */
  1063. OrmSession setup_order_session() throws SqlError {
  1064. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  1065. var session = new OrmSession(conn, new SqliteDialect());
  1066. conn.execute("""
  1067. CREATE TABLE orders (
  1068. id INTEGER PRIMARY KEY AUTOINCREMENT,
  1069. user_id INTEGER,
  1070. product_id INTEGER,
  1071. quantity INTEGER,
  1072. total REAL,
  1073. status TEXT,
  1074. created_at INTEGER
  1075. )
  1076. """);
  1077. session.register_with_schema<TestOrder>("orders", b => {
  1078. b.column<int64?>("id", o => o.id, (o, v) => o.id = v);
  1079. b.column<int64?>("user_id", o => o.user_id, (o, v) => o.user_id = v);
  1080. b.column<int64?>("product_id", o => o.product_id, (o, v) => o.product_id = v);
  1081. b.column<int64?>("quantity", o => o.quantity, (o, v) => o.quantity = v);
  1082. b.column<double?>("total", o => o.total, (o, v) => o.total = v);
  1083. b.column<string>("status", o => o.status, (o, v) => o.status = v);
  1084. b.column<DateTime?>("created_at", o => o.created_at, (o, v) => o.created_at = v);
  1085. });
  1086. return session;
  1087. }
  1088. /**
  1089. * Test: Basic insert back-populates primary key on User entity.
  1090. *
  1091. * Verifies that after inserting a new User entity with id = 0,
  1092. * the id property is updated with the auto-generated database ID.
  1093. */
  1094. void test_insert_back_populates_primary_key() throws Error {
  1095. print("Test: insert back-populates primary key on User... ");
  1096. var session = setup_test_session();
  1097. // Create a new user with id = 0 (default for new entities)
  1098. var user = new TestUser();
  1099. user.name = "TestUser";
  1100. user.email = "test@example.com";
  1101. user.age = 25;
  1102. // Verify initial state - id should be 0
  1103. assert(user.id == 0);
  1104. // Insert the user
  1105. session.insert(user);
  1106. // After insert, the id should be back-populated with the generated ID
  1107. assert(user.id > 0);
  1108. print("PASSED\n");
  1109. }
  1110. /**
  1111. * Test: Multiple inserts get different auto-generated IDs.
  1112. *
  1113. * Verifies that inserting multiple entities results in each
  1114. * getting a unique, incrementing primary key.
  1115. */
  1116. void test_insert_back_populates_different_ids() throws Error {
  1117. print("Test: multiple inserts get different IDs... ");
  1118. var session = setup_test_session();
  1119. // Create and insert first user
  1120. var user1 = new TestUser();
  1121. user1.name = "User1";
  1122. user1.age = 20;
  1123. session.insert(user1);
  1124. // Create and insert second user
  1125. var user2 = new TestUser();
  1126. user2.name = "User2";
  1127. user2.age = 25;
  1128. session.insert(user2);
  1129. // Both should have IDs > 0
  1130. assert(user1.id > 0);
  1131. assert(user2.id > 0);
  1132. // IDs should be different
  1133. assert(user1.id != user2.id);
  1134. print("PASSED\n");
  1135. }
  1136. /**
  1137. * Test: Insert back-populates primary key on Product entity.
  1138. *
  1139. * Verifies that the back-population works correctly for different
  1140. * entity types, not just User.
  1141. */
  1142. void test_insert_back_populates_product() throws Error {
  1143. print("Test: insert back-populates primary key on Product... ");
  1144. var session = setup_product_session();
  1145. // Create a new product with id = 0
  1146. var product = new TestProduct();
  1147. product.name = "Test Product";
  1148. product.category = "Electronics";
  1149. product.price = 99.99;
  1150. product.stock = 100;
  1151. // Verify initial state
  1152. assert(product.id == 0);
  1153. // Insert the product
  1154. session.insert(product);
  1155. // After insert, the id should be back-populated
  1156. assert(product.id > 0);
  1157. print("PASSED\n");
  1158. }
  1159. /**
  1160. * Test: Insert back-populates primary key on Order entity.
  1161. *
  1162. * Verifies that the back-population works correctly for Order entities
  1163. * which have different column types including DateTime.
  1164. */
  1165. void test_insert_back_populates_order() throws Error {
  1166. print("Test: insert back-populates primary key on Order... ");
  1167. var session = setup_order_session();
  1168. // Create a new order with id = 0
  1169. var order = new TestOrder();
  1170. order.user_id = 1;
  1171. order.product_id = 1;
  1172. order.quantity = 2;
  1173. order.total = 199.98;
  1174. order.status = "pending";
  1175. order.created_at = new DateTime.now_utc();
  1176. // Verify initial state
  1177. assert(order.id == 0);
  1178. // Insert the order
  1179. session.insert(order);
  1180. // After insert, the id should be back-populated
  1181. assert(order.id > 0);
  1182. print("PASSED\n");
  1183. }