orm-test.vala 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448
  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 dialect = new SqliteDialect();
  708. var registry = new TypeRegistry();
  709. // Create table first (schema is managed by migrations)
  710. conn.execute("""
  711. CREATE TABLE users (
  712. id INTEGER PRIMARY KEY AUTOINCREMENT,
  713. name TEXT NOT NULL,
  714. email TEXT,
  715. age INTEGER,
  716. salary REAL,
  717. is_active INTEGER,
  718. created_at INTEGER
  719. )
  720. """);
  721. // Register TestUser mapper with schema introspection on registry
  722. registry.register_entity<TestUser>(EntityMapper.build_for<TestUser>(b => {
  723. b.table("users");
  724. b.column<int64?>("id", u => u.id, (u, v) => u.id = v);
  725. b.column<string>("name", u => u.name, (u, v) => u.name = v);
  726. b.column<string>("email", u => u.email, (u, v) => u.email = v);
  727. b.column<int64?>("age", u => u.age, (u, v) => u.age = v);
  728. b.column<double?>("salary", u => u.salary, (u, v) => u.salary = v);
  729. b.column<bool?>("is_active", u => u.is_active, (u, v) => u.is_active = v);
  730. b.column<DateTime?>("created_at", u => u.created_at, (u, v) => u.created_at = v);
  731. }));
  732. return new OrmSession(conn, registry, dialect);
  733. }
  734. void test_orm_session_register() throws Error {
  735. print("Test: OrmSession register... ");
  736. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  737. var dialect = new SqliteDialect();
  738. var registry = new TypeRegistry();
  739. // Create table first
  740. conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
  741. // Register using the simplified API on registry
  742. registry.register_entity<TestUser>(EntityMapper.build_for<TestUser>(b => {
  743. b.table("users");
  744. b.column<int64?>("id", u => u.id, (u, v) => u.id = v);
  745. b.column<string>("name", u => u.name, (u, v) => u.name = v);
  746. }));
  747. var session = new OrmSession(conn, registry, dialect);
  748. // If we got here without exception, registration worked
  749. print("PASSED\n");
  750. conn.close();
  751. }
  752. void test_orm_session_register_with_schema() throws Error {
  753. print("Test: OrmSession register_with_schema... ");
  754. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  755. var dialect = new SqliteDialect();
  756. var registry = new TypeRegistry();
  757. // Create table with auto-increment primary key
  758. conn.execute("""
  759. CREATE TABLE users (
  760. id INTEGER PRIMARY KEY AUTOINCREMENT,
  761. name TEXT NOT NULL,
  762. email TEXT
  763. )
  764. """);
  765. // Register with schema introspection on registry - PK and auto-increment discovered automatically
  766. registry.register_entity<TestUser>(EntityMapper.build_for<TestUser>(b => {
  767. b.table("users");
  768. b.column<int64?>("id", u => u.id, (u, v) => u.id = v);
  769. b.column<string>("name", u => u.name, (u, v) => u.name = v);
  770. b.column<string>("email", u => u.email, (u, v) => u.email = v);
  771. }));
  772. var session = new OrmSession(conn, registry, dialect);
  773. // Verify the mapper has the schema information
  774. var mapper = session.get_mapper<TestUser>();
  775. assert(mapper != null);
  776. assert(mapper.table_name == "users");
  777. assert(mapper.get_effective_primary_key() == "id");
  778. assert(mapper.is_auto_increment("id") == true);
  779. print("PASSED\n");
  780. conn.close();
  781. }
  782. void test_orm_session_create_query() throws Error {
  783. print("Test: OrmSession create query... ");
  784. var session = setup_test_session();
  785. var query = session.query<TestUser>();
  786. assert(query != null);
  787. print("PASSED\n");
  788. }
  789. void test_orm_session_insert() throws Error {
  790. print("Test: OrmSession insert... ");
  791. var session = setup_test_session();
  792. var user = new TestUser();
  793. user.name = "Alice";
  794. user.email = "alice@example.com";
  795. user.age = 30;
  796. user.salary = 50000.0;
  797. user.is_active = true;
  798. user.created_at = new DateTime.now_utc();
  799. session.insert(user);
  800. // Verify insert worked by querying
  801. var results = session.query<TestUser>().materialise();
  802. var arr = results.to_array();
  803. assert(arr.length == 1);
  804. var inserted = arr[0];
  805. assert(inserted != null);
  806. assert(inserted.name == "Alice");
  807. assert(inserted.email == "alice@example.com");
  808. assert(inserted.age == 30);
  809. print("PASSED\n");
  810. }
  811. void test_orm_session_query_with_where() throws Error {
  812. print("Test: OrmSession query with where... ");
  813. var session = setup_test_session();
  814. // Insert test data
  815. var alice = new TestUser();
  816. alice.name = "Alice";
  817. alice.age = 30;
  818. session.insert(alice);
  819. var bob = new TestUser();
  820. bob.name = "Bob";
  821. bob.age = 25;
  822. session.insert(bob);
  823. var charlie = new TestUser();
  824. charlie.name = "Charlie";
  825. charlie.age = 35;
  826. session.insert(charlie);
  827. // Query with where clause
  828. var results = session.query<TestUser>()
  829. .where("age > 28")
  830. .materialise();
  831. var arr = results.to_array();
  832. assert(arr.length == 2);
  833. print("PASSED\n");
  834. }
  835. void test_orm_session_update() throws Error {
  836. print("Test: OrmSession update... ");
  837. var session = setup_test_session();
  838. // Insert test data
  839. var user = new TestUser();
  840. user.name = "Alice";
  841. user.email = "alice@example.com";
  842. user.age = 30;
  843. session.insert(user);
  844. // Get the inserted user with ID - use age for lookup since string literals
  845. // in expressions aren't supported by the expression parser
  846. var inserted_arr = session.query<TestUser>()
  847. .where("age == 30")
  848. .first();
  849. assert(inserted_arr != null);
  850. // Update the user
  851. inserted_arr.name = "Alice Updated";
  852. inserted_arr.age = 31;
  853. session.update(inserted_arr);
  854. // Verify update - use the ID we now know
  855. var updated = session.query<TestUser>()
  856. .where("id == " + inserted_arr.id.to_string())
  857. .first();
  858. assert(updated != null);
  859. assert(updated.name == "Alice Updated");
  860. assert(updated.age == 31);
  861. print("PASSED\n");
  862. }
  863. void test_orm_session_delete() throws Error {
  864. print("Test: OrmSession delete... ");
  865. var session = setup_test_session();
  866. // Insert test data
  867. var user = new TestUser();
  868. user.name = "ToDelete";
  869. user.age = 99;
  870. session.insert(user);
  871. // Get the inserted user with ID - use age for lookup
  872. var inserted = session.query<TestUser>()
  873. .where("age == 99")
  874. .first();
  875. assert(inserted != null);
  876. // Delete the user
  877. session.delete(inserted);
  878. // Verify deletion - use the ID
  879. var results = session.query<TestUser>()
  880. .where("id == " + inserted.id.to_string())
  881. .materialise();
  882. var arr = results.to_array();
  883. assert(arr.length == 0);
  884. print("PASSED\n");
  885. }
  886. void test_orm_session_order_by() throws Error {
  887. print("Test: OrmSession order by... ");
  888. var session = setup_test_session();
  889. // Insert test data
  890. var alice = new TestUser();
  891. alice.name = "Alice";
  892. alice.age = 30;
  893. session.insert(alice);
  894. var bob = new TestUser();
  895. bob.name = "Bob";
  896. bob.age = 25;
  897. session.insert(bob);
  898. var charlie = new TestUser();
  899. charlie.name = "Charlie";
  900. charlie.age = 35;
  901. session.insert(charlie);
  902. // Query with order by ascending
  903. var results = session.query<TestUser>()
  904. .order_by("age")
  905. .materialise();
  906. var arr = results.to_array();
  907. assert(arr.length == 3);
  908. assert(arr[0].age == 25); // Bob
  909. assert(arr[1].age == 30); // Alice
  910. assert(arr[2].age == 35); // Charlie
  911. // Query with order by descending
  912. var desc_results = session.query<TestUser>()
  913. .order_by_desc("age")
  914. .materialise();
  915. var desc_arr = desc_results.to_array();
  916. assert(desc_arr.length == 3);
  917. assert(desc_arr[0].age == 35); // Charlie
  918. assert(desc_arr[1].age == 30); // Alice
  919. assert(desc_arr[2].age == 25); // Bob
  920. print("PASSED\n");
  921. }
  922. void test_orm_session_limit_offset() throws Error {
  923. print("Test: OrmSession limit/offset... ");
  924. var session = setup_test_session();
  925. // Insert test data
  926. for (int i = 0; i < 10; i++) {
  927. var user = new TestUser();
  928. user.name = "User%d".printf(i);
  929. user.age = 20 + i;
  930. session.insert(user);
  931. }
  932. // Query with limit
  933. var limited = session.query<TestUser>()
  934. .order_by("age")
  935. .limit(3)
  936. .materialise();
  937. var limited_arr = limited.to_array();
  938. assert(limited_arr.length == 3);
  939. // Query with limit and offset
  940. var paged = session.query<TestUser>()
  941. .order_by("age")
  942. .limit(3)
  943. .offset(3)
  944. .materialise();
  945. var paged_arr = paged.to_array();
  946. assert(paged_arr.length == 3);
  947. assert(paged_arr[0].age == 23); // Skipped 20, 21, 22
  948. print("PASSED\n");
  949. }
  950. void test_orm_session_first() throws Error {
  951. print("Test: OrmSession first... ");
  952. var session = setup_test_session();
  953. // Insert test data
  954. var alice = new TestUser();
  955. alice.name = "Alice";
  956. alice.age = 30;
  957. session.insert(alice);
  958. var bob = new TestUser();
  959. bob.name = "Bob";
  960. bob.age = 25;
  961. session.insert(bob);
  962. // Get first with order
  963. var first = session.query<TestUser>()
  964. .order_by("age")
  965. .first();
  966. assert(first != null);
  967. assert(first.name == "Bob"); // Youngest
  968. assert(first.age == 25);
  969. // Query empty result
  970. var empty = session.query<TestUser>()
  971. .where("age > 100")
  972. .first();
  973. assert(empty == null);
  974. print("PASSED\n");
  975. }
  976. // ========================================
  977. // Schema Introspection Tests
  978. // ========================================
  979. void test_schema_introspection_basic() throws Error {
  980. print("Test: Schema introspection basic... ");
  981. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  982. var dialect = new SqliteDialect();
  983. // Create a table with various column types
  984. conn.execute("""
  985. CREATE TABLE test_table (
  986. id INTEGER PRIMARY KEY AUTOINCREMENT,
  987. name TEXT NOT NULL,
  988. email TEXT,
  989. age INTEGER,
  990. salary REAL,
  991. created_at INTEGER
  992. )
  993. """);
  994. // Introspect the schema
  995. var schema = dialect.introspect_schema(conn, "test_table");
  996. assert(schema != null);
  997. assert(schema.table_name == "test_table");
  998. assert(schema.primary_key_column == "id");
  999. assert(schema.columns.count() == 6);
  1000. // Find the id column and verify it's marked as PK and auto-increment
  1001. var id_col = schema.get_column("id");
  1002. assert(id_col != null);
  1003. assert(id_col.is_primary_key == true);
  1004. assert(id_col.auto_increment == true);
  1005. // Find the name column and verify it's required
  1006. var name_col = schema.get_column("name");
  1007. assert(name_col != null);
  1008. assert(name_col.is_required == true);
  1009. print("PASSED\n");
  1010. conn.close();
  1011. }
  1012. void test_schema_introspection_with_register() throws Error {
  1013. print("Test: Schema introspection with register... ");
  1014. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  1015. var dialect = new SqliteDialect();
  1016. var registry = new TypeRegistry();
  1017. // Create table with composite structure
  1018. conn.execute("""
  1019. CREATE TABLE products (
  1020. id INTEGER PRIMARY KEY AUTOINCREMENT,
  1021. name TEXT NOT NULL,
  1022. price REAL NOT NULL,
  1023. description TEXT,
  1024. created_at INTEGER
  1025. )
  1026. """);
  1027. // Define a simple product class inline using TestUser as proxy
  1028. // Register with schema introspection on registry
  1029. registry.register_entity<TestUser>(EntityMapper.build_for<TestUser>(b => {
  1030. b.table("products");
  1031. b.column<int64?>("id", u => u.id, (u, v) => u.id = v);
  1032. b.column<string>("name", u => u.name, (u, v) => u.name = v);
  1033. }));
  1034. var session = new OrmSession(conn, registry, dialect);
  1035. // Verify schema was introspected and applied
  1036. var mapper = session.get_mapper<TestUser>();
  1037. assert(mapper != null);
  1038. assert(mapper.table_name == "products");
  1039. assert(mapper.get_effective_primary_key() == "id");
  1040. print("PASSED\n");
  1041. conn.close();
  1042. }
  1043. // ========================================
  1044. // Primary Key Back-Population Tests
  1045. // ========================================
  1046. /**
  1047. * Helper to set up a session with products table for testing.
  1048. */
  1049. OrmSession setup_product_session() throws SqlError {
  1050. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  1051. var dialect = new SqliteDialect();
  1052. var registry = new TypeRegistry();
  1053. conn.execute("""
  1054. CREATE TABLE products (
  1055. id INTEGER PRIMARY KEY AUTOINCREMENT,
  1056. name TEXT NOT NULL,
  1057. category TEXT,
  1058. price REAL,
  1059. stock INTEGER
  1060. )
  1061. """);
  1062. registry.register_entity<TestProduct>(EntityMapper.build_for<TestProduct>(b => {
  1063. b.table("products");
  1064. b.column<int64?>("id", p => p.id, (p, v) => p.id = v);
  1065. b.column<string>("name", p => p.name, (p, v) => p.name = v);
  1066. b.column<string>("category", p => p.category, (p, v) => p.category = v);
  1067. b.column<double?>("price", p => p.price, (p, v) => p.price = v);
  1068. b.column<int64?>("stock", p => p.stock, (p, v) => p.stock = v);
  1069. }));
  1070. return new OrmSession(conn, registry, dialect);
  1071. }
  1072. /**
  1073. * Helper to set up a session with orders table for testing.
  1074. */
  1075. OrmSession setup_order_session() throws SqlError {
  1076. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  1077. var dialect = new SqliteDialect();
  1078. var registry = new TypeRegistry();
  1079. conn.execute("""
  1080. CREATE TABLE orders (
  1081. id INTEGER PRIMARY KEY AUTOINCREMENT,
  1082. user_id INTEGER,
  1083. product_id INTEGER,
  1084. quantity INTEGER,
  1085. total REAL,
  1086. status TEXT,
  1087. created_at INTEGER
  1088. )
  1089. """);
  1090. registry.register_entity<TestOrder>(EntityMapper.build_for<TestOrder>(b => {
  1091. b.table("orders");
  1092. b.column<int64?>("id", o => o.id, (o, v) => o.id = v);
  1093. b.column<int64?>("user_id", o => o.user_id, (o, v) => o.user_id = v);
  1094. b.column<int64?>("product_id", o => o.product_id, (o, v) => o.product_id = v);
  1095. b.column<int64?>("quantity", o => o.quantity, (o, v) => o.quantity = v);
  1096. b.column<double?>("total", o => o.total, (o, v) => o.total = v);
  1097. b.column<string>("status", o => o.status, (o, v) => o.status = v);
  1098. b.column<DateTime?>("created_at", o => o.created_at, (o, v) => o.created_at = v);
  1099. }));
  1100. return new OrmSession(conn, registry, dialect);
  1101. }
  1102. /**
  1103. * Test: Basic insert back-populates primary key on User entity.
  1104. *
  1105. * Verifies that after inserting a new User entity with id = 0,
  1106. * the id property is updated with the auto-generated database ID.
  1107. */
  1108. void test_insert_back_populates_primary_key() throws Error {
  1109. print("Test: insert back-populates primary key on User... ");
  1110. var session = setup_test_session();
  1111. // Create a new user with id = 0 (default for new entities)
  1112. var user = new TestUser();
  1113. user.name = "TestUser";
  1114. user.email = "test@example.com";
  1115. user.age = 25;
  1116. // Verify initial state - id should be 0
  1117. assert(user.id == 0);
  1118. // Insert the user
  1119. session.insert(user);
  1120. // After insert, the id should be back-populated with the generated ID
  1121. assert(user.id > 0);
  1122. print("PASSED\n");
  1123. }
  1124. /**
  1125. * Test: Multiple inserts get different auto-generated IDs.
  1126. *
  1127. * Verifies that inserting multiple entities results in each
  1128. * getting a unique, incrementing primary key.
  1129. */
  1130. void test_insert_back_populates_different_ids() throws Error {
  1131. print("Test: multiple inserts get different IDs... ");
  1132. var session = setup_test_session();
  1133. // Create and insert first user
  1134. var user1 = new TestUser();
  1135. user1.name = "User1";
  1136. user1.age = 20;
  1137. session.insert(user1);
  1138. // Create and insert second user
  1139. var user2 = new TestUser();
  1140. user2.name = "User2";
  1141. user2.age = 25;
  1142. session.insert(user2);
  1143. // Both should have IDs > 0
  1144. assert(user1.id > 0);
  1145. assert(user2.id > 0);
  1146. // IDs should be different
  1147. assert(user1.id != user2.id);
  1148. print("PASSED\n");
  1149. }
  1150. /**
  1151. * Test: Insert back-populates primary key on Product entity.
  1152. *
  1153. * Verifies that the back-population works correctly for different
  1154. * entity types, not just User.
  1155. */
  1156. void test_insert_back_populates_product() throws Error {
  1157. print("Test: insert back-populates primary key on Product... ");
  1158. var session = setup_product_session();
  1159. // Create a new product with id = 0
  1160. var product = new TestProduct();
  1161. product.name = "Test Product";
  1162. product.category = "Electronics";
  1163. product.price = 99.99;
  1164. product.stock = 100;
  1165. // Verify initial state
  1166. assert(product.id == 0);
  1167. // Insert the product
  1168. session.insert(product);
  1169. // After insert, the id should be back-populated
  1170. assert(product.id > 0);
  1171. print("PASSED\n");
  1172. }
  1173. /**
  1174. * Test: Insert back-populates primary key on Order entity.
  1175. *
  1176. * Verifies that the back-population works correctly for Order entities
  1177. * which have different column types including DateTime.
  1178. */
  1179. void test_insert_back_populates_order() throws Error {
  1180. print("Test: insert back-populates primary key on Order... ");
  1181. var session = setup_order_session();
  1182. // Create a new order with id = 0
  1183. var order = new TestOrder();
  1184. order.user_id = 1;
  1185. order.product_id = 1;
  1186. order.quantity = 2;
  1187. order.total = 199.98;
  1188. order.status = "pending";
  1189. order.created_at = new DateTime.now_utc();
  1190. // Verify initial state
  1191. assert(order.id == 0);
  1192. // Insert the order
  1193. session.insert(order);
  1194. // After insert, the id should be back-populated
  1195. assert(order.id > 0);
  1196. print("PASSED\n");
  1197. }