orm-test.vala 51 KB

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