sqlite-test.vala 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832
  1. using InvercargillSql;
  2. using Sqlite;
  3. /**
  4. * Comprehensive SQLite integration tests including Element type conversions.
  5. *
  6. * These tests verify that data can be written to and read from SQLite
  7. * with proper type conversions through the Element interface.
  8. */
  9. public int main(string[] args) {
  10. print("=== SQLite Integration Tests ===\n\n");
  11. try {
  12. // Element type conversion tests
  13. print("--- Integer Element Tests ---\n");
  14. test_integer_element_conversions();
  15. test_integer_element_bool_conversion();
  16. test_integer_element_datetime_conversion();
  17. test_integer_element_null_handling();
  18. print("\n--- Real Element Tests ---\n");
  19. test_real_element_conversions();
  20. test_real_element_null_handling();
  21. print("\n--- Text Element Tests ---\n");
  22. test_text_element_conversions();
  23. test_text_element_bool_parsing();
  24. test_text_element_null_handling();
  25. print("\n--- Blob Element Tests ---\n");
  26. test_blob_element_round_trip();
  27. test_blob_element_null_handling();
  28. print("\n--- Null Element Tests ---\n");
  29. test_null_element_handling();
  30. print("\n--- Round-trip Conversion Tests ---\n");
  31. test_datetime_round_trip();
  32. test_bool_round_trip();
  33. test_nullable_types_round_trip();
  34. print("\n--- Complex Integration Tests ---\n");
  35. test_mixed_type_table();
  36. test_element_assignable_types();
  37. print("\n=== All SQLite integration tests passed! ===\n");
  38. return 0;
  39. } catch (Error e) {
  40. printerr("\n=== Test failed: %s ===\n", e.message);
  41. return 1;
  42. }
  43. }
  44. // ============================================================================
  45. // Integer Element Tests
  46. // ============================================================================
  47. void test_integer_element_conversions() throws SqlError {
  48. print("Test: Integer element conversions... ");
  49. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  50. // Create table with integer column
  51. conn.execute("CREATE TABLE test_int (id INTEGER PRIMARY KEY, value INTEGER)");
  52. // Insert various integer values
  53. conn.create_command("INSERT INTO test_int (value) VALUES (:val)")
  54. .with_parameter("val", 42)
  55. .execute_non_query();
  56. conn.create_command("INSERT INTO test_int (value) VALUES (:val)")
  57. .with_parameter("val", -123)
  58. .execute_non_query();
  59. int64? max_val = int64.MAX;
  60. conn.create_command("INSERT INTO test_int (value) VALUES (:val)")
  61. .with_parameter<int64?>("val", max_val)
  62. .execute_non_query();
  63. // Query back and verify conversions
  64. var results = conn.create_command("SELECT value FROM test_int ORDER BY id")
  65. .execute_query();
  66. var array = results.to_array();
  67. assert(array.length == 3);
  68. // First row: 42
  69. var elem = array[0].get("value");
  70. assert(elem != null);
  71. // Test as<int64>()
  72. int64? val_int64 = null;
  73. assert(elem.try_get_as<int64?>(out val_int64));
  74. assert(val_int64 == 42);
  75. // Test as<int>()
  76. int? val_int = null;
  77. assert(elem.try_get_as<int?>(out val_int));
  78. assert(val_int == 42);
  79. // Test as<double>()
  80. double? val_double = null;
  81. assert(elem.try_get_as<double?>(out val_double));
  82. assert(val_double == 42.0);
  83. // Test as<float>()
  84. float? val_float = null;
  85. assert(elem.try_get_as<float?>(out val_float));
  86. assert(val_float >= 41.9f && val_float <= 42.1f);
  87. // Test as<string>()
  88. string? val_str = null;
  89. assert(elem.try_get_as<string>(out val_str));
  90. assert(val_str == "42");
  91. // Second row: -123
  92. elem = array[1].get("value");
  93. assert(elem.try_get_as<int64?>(out val_int64));
  94. assert(val_int64 == -123);
  95. // Third row: int64.MAX
  96. elem = array[2].get("value");
  97. assert(elem.try_get_as<int64?>(out val_int64));
  98. assert(val_int64 == int64.MAX);
  99. print("PASSED\n");
  100. conn.close();
  101. }
  102. void test_integer_element_bool_conversion() throws SqlError {
  103. print("Test: Integer to bool conversion... ");
  104. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  105. conn.execute("CREATE TABLE test_bool (id INTEGER PRIMARY KEY, flag INTEGER)");
  106. // Insert 0 (false) and 1 (true) and other non-zero values
  107. conn.create_command("INSERT INTO test_bool (flag) VALUES (0)").execute_non_query();
  108. conn.create_command("INSERT INTO test_bool (flag) VALUES (1)").execute_non_query();
  109. conn.create_command("INSERT INTO test_bool (flag) VALUES (42)").execute_non_query();
  110. conn.create_command("INSERT INTO test_bool (flag) VALUES (-1)").execute_non_query();
  111. var results = conn.create_command("SELECT flag FROM test_bool ORDER BY id")
  112. .execute_query();
  113. var array = results.to_array();
  114. assert(array.length == 4);
  115. // 0 should be false
  116. bool? val = null;
  117. assert(array[0].get("flag").try_get_as<bool?>(out val));
  118. assert(val == false);
  119. // 1 should be true
  120. assert(array[1].get("flag").try_get_as<bool?>(out val));
  121. assert(val == true);
  122. // 42 (non-zero) should be true
  123. assert(array[2].get("flag").try_get_as<bool?>(out val));
  124. assert(val == true);
  125. // -1 (non-zero) should be true
  126. assert(array[3].get("flag").try_get_as<bool?>(out val));
  127. assert(val == true);
  128. print("PASSED\n");
  129. conn.close();
  130. }
  131. void test_integer_element_datetime_conversion() throws SqlError {
  132. print("Test: Integer to DateTime conversion... ");
  133. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  134. conn.execute("CREATE TABLE test_datetime (id INTEGER PRIMARY KEY, timestamp INTEGER)");
  135. // Insert a known Unix timestamp (2024-01-15 12:30:45 UTC)
  136. int64? known_timestamp = 1705324245;
  137. conn.create_command("INSERT INTO test_datetime (timestamp) VALUES (:ts)")
  138. .with_parameter<int64?>("ts", known_timestamp)
  139. .execute_non_query();
  140. var results = conn.create_command("SELECT timestamp FROM test_datetime")
  141. .execute_query();
  142. var array = results.to_array();
  143. assert(array.length == 1);
  144. DateTime? dt = null;
  145. var elem = array[0].get("timestamp");
  146. assert(elem.try_get_as<DateTime>(out dt));
  147. assert(dt != null);
  148. assert(dt.to_unix() == known_timestamp);
  149. print("PASSED\n");
  150. conn.close();
  151. }
  152. void test_integer_element_null_handling() throws SqlError {
  153. print("Test: Integer element null handling... ");
  154. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  155. conn.execute("CREATE TABLE test_null_int (id INTEGER PRIMARY KEY, value INTEGER)");
  156. // Insert a NULL value
  157. conn.create_command("INSERT INTO test_null_int (value) VALUES (NULL)").execute_non_query();
  158. // Insert a non-null value for comparison
  159. conn.create_command("INSERT INTO test_null_int (value) VALUES (42)").execute_non_query();
  160. var results = conn.create_command("SELECT value FROM test_null_int ORDER BY id")
  161. .execute_query();
  162. var array = results.to_array();
  163. assert(array.length == 2);
  164. // First row is NULL
  165. var elem = array[0].get("value");
  166. assert(elem.is_null() == true);
  167. int64? val = null;
  168. assert(elem.try_get_as<int64?>(out val) == false);
  169. assert(val == null);
  170. // Second row is not null
  171. elem = array[1].get("value");
  172. assert(elem.is_null() == false);
  173. assert(elem.try_get_as<int64?>(out val));
  174. assert(val == 42);
  175. print("PASSED\n");
  176. conn.close();
  177. }
  178. // ============================================================================
  179. // Real Element Tests
  180. // ============================================================================
  181. void test_real_element_conversions() throws SqlError {
  182. print("Test: Real element conversions... ");
  183. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  184. conn.execute("CREATE TABLE test_real (id INTEGER PRIMARY KEY, value REAL)");
  185. // Insert various real values
  186. double? val1 = 3.14159;
  187. conn.create_command("INSERT INTO test_real (value) VALUES (:val)")
  188. .with_parameter<double?>("val", val1)
  189. .execute_non_query();
  190. double? val2 = -2.71828;
  191. conn.create_command("INSERT INTO test_real (value) VALUES (:val)")
  192. .with_parameter<double?>("val", val2)
  193. .execute_non_query();
  194. double? val3 = 42.0;
  195. conn.create_command("INSERT INTO test_real (value) VALUES (:val)")
  196. .with_parameter<double?>("val", val3)
  197. .execute_non_query();
  198. var results = conn.create_command("SELECT value FROM test_real ORDER BY id")
  199. .execute_query();
  200. var array = results.to_array();
  201. assert(array.length == 3);
  202. // First row: 3.14159
  203. var elem = array[0].get("value");
  204. assert(elem != null);
  205. // Test as<double>()
  206. double? val_double = null;
  207. assert(elem.try_get_as<double?>(out val_double));
  208. assert(val_double >= 3.14158 && val_double <= 3.14160);
  209. // Test as<float>()
  210. float? val_float = null;
  211. assert(elem.try_get_as<float?>(out val_float));
  212. assert(val_float >= 3.1415f && val_float <= 3.1417f);
  213. // Test as<int64>() - truncation
  214. int64? val_int64 = null;
  215. assert(elem.try_get_as<int64?>(out val_int64));
  216. assert(val_int64 == 3);
  217. // Test as<int>() - truncation
  218. int? val_int = null;
  219. assert(elem.try_get_as<int?>(out val_int));
  220. assert(val_int == 3);
  221. // Test as<string>()
  222. string? val_str = null;
  223. assert(elem.try_get_as<string>(out val_str));
  224. assert(val_str != null);
  225. assert(val_str.contains("3.14"));
  226. // Second row: -2.71828 (verify negative truncation)
  227. elem = array[1].get("value");
  228. assert(elem.try_get_as<int64?>(out val_int64));
  229. assert(val_int64 == -2);
  230. // Third row: 42.0 (whole number)
  231. elem = array[2].get("value");
  232. assert(elem.try_get_as<int64?>(out val_int64));
  233. assert(val_int64 == 42);
  234. print("PASSED\n");
  235. conn.close();
  236. }
  237. void test_real_element_null_handling() throws SqlError {
  238. print("Test: Real element null handling... ");
  239. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  240. conn.execute("CREATE TABLE test_null_real (id INTEGER PRIMARY KEY, value REAL)");
  241. conn.create_command("INSERT INTO test_null_real (value) VALUES (NULL)").execute_non_query();
  242. var results = conn.create_command("SELECT value FROM test_null_real")
  243. .execute_query();
  244. var array = results.to_array();
  245. assert(array.length == 1);
  246. var elem = array[0].get("value");
  247. assert(elem.is_null() == true);
  248. double? val = null;
  249. assert(elem.try_get_as<double?>(out val) == false);
  250. assert(val == null);
  251. print("PASSED\n");
  252. conn.close();
  253. }
  254. // ============================================================================
  255. // Text Element Tests
  256. // ============================================================================
  257. void test_text_element_conversions() throws SqlError {
  258. print("Test: Text element conversions... ");
  259. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  260. conn.execute("CREATE TABLE test_text (id INTEGER PRIMARY KEY, value TEXT)");
  261. // Insert various text values
  262. conn.create_command("INSERT INTO test_text (value) VALUES ('Hello, World!')").execute_non_query();
  263. conn.create_command("INSERT INTO test_text (value) VALUES ('42')").execute_non_query();
  264. conn.create_command("INSERT INTO test_text (value) VALUES ('-123')").execute_non_query();
  265. conn.create_command("INSERT INTO test_text (value) VALUES ('3.14159')").execute_non_query();
  266. var results = conn.create_command("SELECT value FROM test_text ORDER BY id")
  267. .execute_query();
  268. var array = results.to_array();
  269. assert(array.length == 4);
  270. // First row: "Hello, World!"
  271. var elem = array[0].get("value");
  272. string? val_str = null;
  273. assert(elem.try_get_as<string>(out val_str));
  274. assert(val_str == "Hello, World!");
  275. // Second row: "42" - parse as int
  276. elem = array[1].get("value");
  277. int64? val_int64 = null;
  278. assert(elem.try_get_as<int64?>(out val_int64));
  279. assert(val_int64 == 42);
  280. int? val_int = null;
  281. assert(elem.try_get_as<int?>(out val_int));
  282. assert(val_int == 42);
  283. // Third row: "-123" - parse as negative int
  284. elem = array[2].get("value");
  285. assert(elem.try_get_as<int64?>(out val_int64));
  286. assert(val_int64 == -123);
  287. // Fourth row: "3.14159" - parse as double
  288. elem = array[3].get("value");
  289. double? val_double = null;
  290. assert(elem.try_get_as<double?>(out val_double));
  291. assert(val_double >= 3.14158 && val_double <= 3.14160);
  292. float? val_float = null;
  293. assert(elem.try_get_as<float?>(out val_float));
  294. assert(val_float >= 3.1415f && val_float <= 3.1417f);
  295. print("PASSED\n");
  296. conn.close();
  297. }
  298. void test_text_element_bool_parsing() throws SqlError {
  299. print("Test: Text element bool parsing... ");
  300. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  301. conn.execute("CREATE TABLE test_text_bool (id INTEGER PRIMARY KEY, value TEXT)");
  302. // Insert various boolean representations
  303. conn.create_command("INSERT INTO test_text_bool (value) VALUES ('1')").execute_non_query();
  304. conn.create_command("INSERT INTO test_text_bool (value) VALUES ('0')").execute_non_query();
  305. conn.create_command("INSERT INTO test_text_bool (value) VALUES ('true')").execute_non_query();
  306. conn.create_command("INSERT INTO test_text_bool (value) VALUES ('false')").execute_non_query();
  307. conn.create_command("INSERT INTO test_text_bool (value) VALUES ('TRUE')").execute_non_query();
  308. conn.create_command("INSERT INTO test_text_bool (value) VALUES ('True')").execute_non_query();
  309. var results = conn.create_command("SELECT value FROM test_text_bool ORDER BY id")
  310. .execute_query();
  311. var array = results.to_array();
  312. assert(array.length == 6);
  313. bool? val = null;
  314. // "1" -> true
  315. assert(array[0].get("value").try_get_as<bool?>(out val));
  316. assert(val == true);
  317. // "0" -> false
  318. assert(array[1].get("value").try_get_as<bool?>(out val));
  319. assert(val == false);
  320. // "true" -> true
  321. assert(array[2].get("value").try_get_as<bool?>(out val));
  322. assert(val == true);
  323. // "false" -> false
  324. assert(array[3].get("value").try_get_as<bool?>(out val));
  325. assert(val == false);
  326. // "TRUE" -> true (case insensitive)
  327. assert(array[4].get("value").try_get_as<bool?>(out val));
  328. assert(val == true);
  329. // "True" -> true (case insensitive)
  330. assert(array[5].get("value").try_get_as<bool?>(out val));
  331. assert(val == true);
  332. print("PASSED\n");
  333. conn.close();
  334. }
  335. void test_text_element_null_handling() throws SqlError {
  336. print("Test: Text element null handling... ");
  337. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  338. conn.execute("CREATE TABLE test_null_text (id INTEGER PRIMARY KEY, value TEXT)");
  339. conn.create_command("INSERT INTO test_null_text (value) VALUES (NULL)").execute_non_query();
  340. var results = conn.create_command("SELECT value FROM test_null_text")
  341. .execute_query();
  342. var array = results.to_array();
  343. assert(array.length == 1);
  344. var elem = array[0].get("value");
  345. assert(elem.is_null() == true);
  346. string? val = null;
  347. // Note: null text element may return empty string or fail
  348. // depending on implementation
  349. elem.try_get_as<string>(out val);
  350. print("PASSED\n");
  351. conn.close();
  352. }
  353. // ============================================================================
  354. // Blob Element Tests
  355. // ============================================================================
  356. void test_blob_element_round_trip() throws SqlError {
  357. print("Test: Blob element round-trip... ");
  358. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  359. conn.execute("CREATE TABLE test_blob (id INTEGER PRIMARY KEY, data BLOB)");
  360. // Create test binary data using Invercargill.Wrap.byte_array
  361. uint8[] original_bytes = { 0x00, 0x01, 0x02, 0xFF, 0xFE, 0xAB, 0xCD };
  362. var original_data = Invercargill.Wrap.byte_array(original_bytes);
  363. // Insert using parameter
  364. conn.create_command("INSERT INTO test_blob (data) VALUES (:data)")
  365. .with_parameter<Invercargill.BinaryData>("data", original_data)
  366. .execute_non_query();
  367. // Query back
  368. var results = conn.create_command("SELECT data FROM test_blob")
  369. .execute_query();
  370. var array = results.to_array();
  371. assert(array.length == 1);
  372. // Get the blob data back
  373. var elem = array[0].get("data");
  374. assert(elem != null);
  375. assert(elem.is_null() == false);
  376. // Verify the element's string representation indicates blob data
  377. string elem_str = elem.to_string();
  378. assert(elem_str.contains("bytes") || elem_str.contains("Blob"));
  379. print("PASSED\n");
  380. conn.close();
  381. }
  382. void test_blob_element_null_handling() throws SqlError {
  383. print("Test: Blob element null handling... ");
  384. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  385. conn.execute("CREATE TABLE test_null_blob (id INTEGER PRIMARY KEY, data BLOB)");
  386. conn.create_command("INSERT INTO test_null_blob (data) VALUES (NULL)").execute_non_query();
  387. var results = conn.create_command("SELECT data FROM test_null_blob")
  388. .execute_query();
  389. var array = results.to_array();
  390. assert(array.length == 1);
  391. var elem = array[0].get("data");
  392. assert(elem.is_null() == true);
  393. print("PASSED\n");
  394. conn.close();
  395. }
  396. // ============================================================================
  397. // Null Element Tests
  398. // ============================================================================
  399. void test_null_element_handling() throws SqlError {
  400. print("Test: Null element handling... ");
  401. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  402. conn.execute("CREATE TABLE test_null (id INTEGER PRIMARY KEY, int_val INTEGER, str_val TEXT, real_val REAL)");
  403. // Insert row with all NULLs
  404. conn.create_command("INSERT INTO test_null (int_val, str_val, real_val) VALUES (NULL, NULL, NULL)")
  405. .execute_non_query();
  406. var results = conn.create_command("SELECT int_val, str_val, real_val FROM test_null")
  407. .execute_query();
  408. var array = results.to_array();
  409. assert(array.length == 1);
  410. // All columns should be null
  411. var int_elem = array[0].get("int_val");
  412. var str_elem = array[0].get("str_val");
  413. var real_elem = array[0].get("real_val");
  414. assert(int_elem.is_null() == true);
  415. assert(str_elem.is_null() == true);
  416. assert(real_elem.is_null() == true);
  417. // Verify try_get_as returns false for null elements
  418. int64? int_val = null;
  419. string? str_val = null;
  420. double? real_val = null;
  421. assert(int_elem.try_get_as<int64?>(out int_val) == false);
  422. assert(str_elem.try_get_as<string>(out str_val) == false);
  423. assert(real_elem.try_get_as<double?>(out real_val) == false);
  424. // Verify assignable_to_type returns true for null elements
  425. assert(int_elem.assignable_to_type(typeof(int64?)) == true);
  426. assert(str_elem.assignable_to_type(typeof(string)) == true);
  427. assert(real_elem.assignable_to_type(typeof(double?)) == true);
  428. print("PASSED\n");
  429. conn.close();
  430. }
  431. // ============================================================================
  432. // Round-trip Conversion Tests
  433. // ============================================================================
  434. void test_datetime_round_trip() throws SqlError {
  435. print("Test: DateTime round-trip conversion... ");
  436. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  437. conn.execute("CREATE TABLE test_dt_roundtrip (id INTEGER PRIMARY KEY, created_at INTEGER)");
  438. // Create a DateTime and convert to Unix timestamp
  439. var original_dt = new DateTime.local(2024, 6, 15, 14, 30, 45.0);
  440. int64? timestamp = original_dt.to_unix();
  441. // Insert the timestamp
  442. conn.create_command("INSERT INTO test_dt_roundtrip (created_at) VALUES (:ts)")
  443. .with_parameter<int64?>("ts", timestamp)
  444. .execute_non_query();
  445. // Query back and convert to DateTime
  446. var results = conn.create_command("SELECT created_at FROM test_dt_roundtrip")
  447. .execute_query();
  448. var array = results.to_array();
  449. assert(array.length == 1);
  450. DateTime? retrieved_dt = null;
  451. var elem = array[0].get("created_at");
  452. assert(elem.try_get_as<DateTime>(out retrieved_dt));
  453. assert(retrieved_dt != null);
  454. // Verify the timestamp matches
  455. assert(retrieved_dt.to_unix() == timestamp);
  456. print("PASSED\n");
  457. conn.close();
  458. }
  459. void test_bool_round_trip() throws SqlError {
  460. print("Test: Bool round-trip conversion... ");
  461. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  462. conn.execute("CREATE TABLE test_bool_roundtrip (id INTEGER PRIMARY KEY, is_active INTEGER)");
  463. // Insert true and false values
  464. conn.create_command("INSERT INTO test_bool_roundtrip (is_active) VALUES (:val)")
  465. .with_parameter("val", true)
  466. .execute_non_query();
  467. conn.create_command("INSERT INTO test_bool_roundtrip (is_active) VALUES (:val)")
  468. .with_parameter("val", false)
  469. .execute_non_query();
  470. var results = conn.create_command("SELECT is_active FROM test_bool_roundtrip ORDER BY id")
  471. .execute_query();
  472. var array = results.to_array();
  473. assert(array.length == 2);
  474. bool? val = null;
  475. // First row should be true
  476. assert(array[0].get("is_active").try_get_as<bool?>(out val));
  477. assert(val == true);
  478. // Second row should be false
  479. assert(array[1].get("is_active").try_get_as<bool?>(out val));
  480. assert(val == false);
  481. print("PASSED\n");
  482. conn.close();
  483. }
  484. void test_nullable_types_round_trip() throws SqlError {
  485. print("Test: Nullable types round-trip... ");
  486. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  487. conn.execute("CREATE TABLE test_nullable (id INTEGER PRIMARY KEY, int_val INTEGER, real_val REAL, str_val TEXT)");
  488. // Insert with values
  489. double? real_param = 3.14;
  490. conn.create_command("INSERT INTO test_nullable (int_val, real_val, str_val) VALUES (:i, :r, :s)")
  491. .with_parameter("i", 42)
  492. .with_parameter<double?>("r", real_param)
  493. .with_parameter("s", "hello")
  494. .execute_non_query();
  495. // Insert with NULLs
  496. conn.create_command("INSERT INTO test_nullable (int_val, real_val, str_val) VALUES (NULL, NULL, NULL)")
  497. .execute_non_query();
  498. var results = conn.create_command("SELECT int_val, real_val, str_val FROM test_nullable ORDER BY id")
  499. .execute_query();
  500. var array = results.to_array();
  501. assert(array.length == 2);
  502. // First row has values
  503. int64? i = null;
  504. double? r = null;
  505. string? s = null;
  506. assert(array[0].get("int_val").try_get_as<int64?>(out i));
  507. assert(i == 42);
  508. assert(array[0].get("real_val").try_get_as<double?>(out r));
  509. assert(r >= 3.13 && r <= 3.15);
  510. assert(array[0].get("str_val").try_get_as<string>(out s));
  511. assert(s == "hello");
  512. // Second row has NULLs
  513. assert(array[1].get("int_val").is_null());
  514. assert(array[1].get("real_val").is_null());
  515. assert(array[1].get("str_val").is_null());
  516. print("PASSED\n");
  517. conn.close();
  518. }
  519. // ============================================================================
  520. // Complex Integration Tests
  521. // ============================================================================
  522. void test_mixed_type_table() throws SqlError {
  523. print("Test: Mixed type table... ");
  524. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  525. // Create a table with various column types
  526. conn.execute("""
  527. CREATE TABLE mixed_table (
  528. id INTEGER PRIMARY KEY,
  529. name TEXT NOT NULL,
  530. age INTEGER,
  531. salary REAL,
  532. is_active INTEGER,
  533. created_at INTEGER,
  534. data BLOB
  535. )
  536. """);
  537. // Insert a row with mixed types
  538. var now = new DateTime.now_local();
  539. uint8[] blob_bytes = { 0xDE, 0xAD, 0xBE, 0xEF };
  540. var blob_data = Invercargill.Wrap.byte_array(blob_bytes);
  541. double? salary_val = 75000.50;
  542. int64? created_val = now.to_unix();
  543. conn.create_command("""
  544. INSERT INTO mixed_table (name, age, salary, is_active, created_at, data)
  545. VALUES (:name, :age, :salary, :active, :created, :data)
  546. """)
  547. .with_parameter("name", "John Doe")
  548. .with_parameter("age", 30)
  549. .with_parameter<double?>("salary", salary_val)
  550. .with_parameter("active", true)
  551. .with_parameter<int64?>("created", created_val)
  552. .with_parameter<Invercargill.BinaryData>("data", blob_data)
  553. .execute_non_query();
  554. // Query back
  555. var results = conn.create_command("SELECT * FROM mixed_table").execute_query();
  556. var array = results.to_array();
  557. assert(array.length == 1);
  558. var row = array[0];
  559. // Verify name (string)
  560. string? name = null;
  561. assert(row.get("name").try_get_as<string>(out name));
  562. assert(name == "John Doe");
  563. // Verify age (int)
  564. int? age = null;
  565. assert(row.get("age").try_get_as<int?>(out age));
  566. assert(age == 30);
  567. // Verify salary (double)
  568. double? salary = null;
  569. assert(row.get("salary").try_get_as<double?>(out salary));
  570. assert(salary >= 75000.49 && salary <= 75000.51);
  571. // Verify is_active (bool)
  572. bool? active = null;
  573. assert(row.get("is_active").try_get_as<bool?>(out active));
  574. assert(active == true);
  575. // Verify created_at (DateTime)
  576. DateTime? created = null;
  577. assert(row.get("created_at").try_get_as<DateTime>(out created));
  578. assert(created != null);
  579. assert(created.to_unix() == now.to_unix());
  580. // Verify data (blob)
  581. var data_elem = row.get("data");
  582. assert(data_elem.is_null() == false);
  583. print("PASSED\n");
  584. conn.close();
  585. }
  586. void test_element_assignable_types() throws SqlError {
  587. print("Test: Element assignable_types... ");
  588. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  589. conn.execute("CREATE TABLE test_assignable (id INTEGER PRIMARY KEY, int_val INTEGER, real_val REAL, str_val TEXT)");
  590. conn.create_command("INSERT INTO test_assignable (int_val, real_val, str_val) VALUES (42, 3.14, 'test')")
  591. .execute_non_query();
  592. var results = conn.create_command("SELECT int_val, real_val, str_val FROM test_assignable")
  593. .execute_query();
  594. var array = results.to_array();
  595. assert(array.length == 1);
  596. var int_elem = array[0].get("int_val");
  597. var real_elem = array[0].get("real_val");
  598. var str_elem = array[0].get("str_val");
  599. // Integer element should be assignable to int64, int, bool, DateTime, double, float, string
  600. assert(int_elem.assignable_to_type(typeof(int64)) == true);
  601. assert(int_elem.assignable_to_type(typeof(int64?)) == true);
  602. assert(int_elem.assignable_to_type(typeof(int)) == true);
  603. assert(int_elem.assignable_to_type(typeof(int?)) == true);
  604. assert(int_elem.assignable_to_type(typeof(bool)) == true);
  605. assert(int_elem.assignable_to_type(typeof(bool?)) == true);
  606. assert(int_elem.assignable_to_type(typeof(DateTime)) == true);
  607. assert(int_elem.assignable_to_type(typeof(double)) == true);
  608. assert(int_elem.assignable_to_type(typeof(float)) == true);
  609. assert(int_elem.assignable_to_type(typeof(string)) == true);
  610. // Real element should be assignable to double, float, int64, int, string
  611. assert(real_elem.assignable_to_type(typeof(double)) == true);
  612. assert(real_elem.assignable_to_type(typeof(float)) == true);
  613. assert(real_elem.assignable_to_type(typeof(int64)) == true);
  614. assert(real_elem.assignable_to_type(typeof(int)) == true);
  615. assert(real_elem.assignable_to_type(typeof(string)) == true);
  616. // Text element should be assignable to string, int64, int, double, float, bool
  617. assert(str_elem.assignable_to_type(typeof(string)) == true);
  618. assert(str_elem.assignable_to_type(typeof(int64)) == true);
  619. assert(str_elem.assignable_to_type(typeof(int)) == true);
  620. assert(str_elem.assignable_to_type(typeof(double)) == true);
  621. assert(str_elem.assignable_to_type(typeof(float)) == true);
  622. assert(str_elem.assignable_to_type(typeof(bool)) == true);
  623. print("PASSED\n");
  624. conn.close();
  625. }