migration-test.vala 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893
  1. using Invercargill.DataStructures;
  2. using InvercargillSql;
  3. using InvercargillSql.Migrations;
  4. using InvercargillSql.Dialects;
  5. using InvercargillSql.Orm;
  6. /**
  7. * Test migrations for testing the migration system
  8. */
  9. public class V001_CreateUsers : Migration {
  10. public override int version { get { return 1; } }
  11. public override string name { get { return "CreateUsers"; } }
  12. public override void up(MigrationBuilder b) throws SqlError {
  13. b.create_table("users", t => {
  14. // Type inferred from generic parameter - no redundant type_int() needed
  15. t.column<int64?>("id")
  16. .primary_key()
  17. .auto_increment();
  18. t.column<string>("name")
  19. .not_null();
  20. t.column<string>("email")
  21. .unique();
  22. // Index created within table builder using fluent API
  23. t.index("idx_users_email").on_column("email");
  24. });
  25. }
  26. public override void down(MigrationBuilder b) throws SqlError {
  27. b.drop_table("users"); // Indexes dropped automatically with table
  28. }
  29. }
  30. public class V002_AddAgeColumn : Migration {
  31. public override int version { get { return 2; } }
  32. public override string name { get { return "AddAgeColumn"; } }
  33. public override void up(MigrationBuilder b) throws SqlError {
  34. b.alter_table("users", t => {
  35. // Type inferred from generic parameter - no redundant type_int() needed
  36. t.add_column<int?>("age");
  37. });
  38. }
  39. public override void down(MigrationBuilder b) throws SqlError {
  40. b.alter_table("users", t => {
  41. t.drop_column("age");
  42. });
  43. }
  44. }
  45. /**
  46. * Migration demonstrating index operations in alter table
  47. */
  48. public class V003_AddIndexOnName : Migration {
  49. public override int version { get { return 3; } }
  50. public override string name { get { return "AddIndexOnName"; } }
  51. public override void up(MigrationBuilder b) throws SqlError {
  52. b.alter_table("users", t => {
  53. // Create index using alter table builder
  54. t.create_index("idx_users_name").on_column("name");
  55. });
  56. }
  57. public override void down(MigrationBuilder b) throws SqlError {
  58. b.alter_table("users", t => {
  59. t.drop_index("idx_users_name");
  60. });
  61. }
  62. }
  63. /**
  64. * Migration tests for Invercargill-Sql library.
  65. */
  66. public int main(string[] args) {
  67. print("=== Invercargill-Sql Migration Tests ===\n\n");
  68. try {
  69. // SQL generation tests
  70. test_create_table_sql();
  71. test_drop_table_sql();
  72. test_create_index_sql();
  73. test_create_unique_index_sql();
  74. test_add_column_sql();
  75. test_drop_column_sql();
  76. test_rename_column_sql();
  77. // MigrationBuilder tests
  78. test_migration_builder_create_table();
  79. test_migration_builder_create_table_with_indexes();
  80. test_migration_builder_alter_table();
  81. test_migration_builder_alter_table_with_indexes();
  82. // Index builder tests
  83. test_index_builder_single_column();
  84. test_index_builder_composite();
  85. test_index_builder_unique();
  86. // Foreign Key tests
  87. test_fk_creation_with_auto_generated_name();
  88. test_fk_creation_with_explicit_name();
  89. test_fk_on_delete_actions();
  90. test_fk_on_update_actions();
  91. test_fk_in_alter_table_add_column();
  92. // Indexed column tests
  93. test_indexed_with_auto_generated_name();
  94. test_indexed_with_custom_name();
  95. test_unique_indexed_creates_unique_index();
  96. test_drop_index_on();
  97. // MigrationRunner tests
  98. test_migration_runner_registration();
  99. test_migration_runner_migrate_to_latest();
  100. test_migration_runner_migrate_to_version();
  101. test_migration_runner_rollback();
  102. test_migration_runner_rollback_all();
  103. print("\n=== All migration tests passed! ===\n");
  104. return 0;
  105. } catch (Error e) {
  106. printerr("\n=== Test failed: %s ===\n", e.message);
  107. return 1;
  108. }
  109. }
  110. void test_create_table_sql() throws SqlError {
  111. print("Test: CREATE TABLE SQL generation... ");
  112. var dialect = new SqliteDialect();
  113. var op = new CreateTableOperation() { table_name = "test" };
  114. op.columns.add(new ColumnDefinition() {
  115. name = "id",
  116. column_type = ColumnType.INT_64,
  117. is_primary_key = true,
  118. auto_increment = true
  119. });
  120. op.columns.add(new ColumnDefinition() {
  121. name = "name",
  122. column_type = ColumnType.TEXT,
  123. is_required = true
  124. });
  125. var sql = dialect.create_table_sql(op);
  126. assert("CREATE TABLE test" in sql);
  127. assert("id" in sql);
  128. assert("INTEGER" in sql);
  129. assert("PRIMARY KEY" in sql);
  130. assert("name" in sql);
  131. assert("TEXT" in sql);
  132. assert("NOT NULL" in sql);
  133. print("PASSED\n");
  134. }
  135. void test_drop_table_sql() throws SqlError {
  136. print("Test: DROP TABLE SQL generation... ");
  137. var dialect = new SqliteDialect();
  138. var op = new DropTableOperation() { table_name = "test" };
  139. var sql = dialect.drop_table_sql(op);
  140. assert(sql == "DROP TABLE IF EXISTS test");
  141. print("PASSED\n");
  142. }
  143. void test_create_index_sql() throws SqlError {
  144. print("Test: CREATE INDEX SQL generation... ");
  145. var dialect = new SqliteDialect();
  146. var op = new CreateIndexOperation() {
  147. index_name = "idx_test",
  148. table_name = "test",
  149. is_unique = false
  150. };
  151. op.columns.add("name");
  152. var sql = dialect.create_index_sql(op);
  153. assert("CREATE INDEX idx_test ON test (name)" == sql);
  154. print("PASSED\n");
  155. }
  156. void test_create_unique_index_sql() throws SqlError {
  157. print("Test: CREATE UNIQUE INDEX SQL generation... ");
  158. var dialect = new SqliteDialect();
  159. var op = new CreateIndexOperation() {
  160. index_name = "idx_unique",
  161. table_name = "test",
  162. is_unique = true
  163. };
  164. op.columns.add("email");
  165. var sql = dialect.create_index_sql(op);
  166. assert("CREATE UNIQUE INDEX idx_unique ON test (email)" == sql);
  167. print("PASSED\n");
  168. }
  169. void test_add_column_sql() throws SqlError {
  170. print("Test: ADD COLUMN SQL generation... ");
  171. var dialect = new SqliteDialect();
  172. var op = new AddColumnOperation() {
  173. table_name = "users",
  174. column = new ColumnDefinition() {
  175. name = "age",
  176. column_type = ColumnType.INT_32
  177. }
  178. };
  179. var sql = dialect.add_column_sql(op);
  180. assert("ALTER TABLE users ADD COLUMN age" in sql);
  181. assert("INTEGER" in sql);
  182. print("PASSED\n");
  183. }
  184. void test_drop_column_sql() throws SqlError {
  185. print("Test: DROP COLUMN SQL generation... ");
  186. var dialect = new SqliteDialect();
  187. var op = new DropColumnOperation() {
  188. table_name = "users",
  189. column_name = "age"
  190. };
  191. var sql = dialect.drop_column_sql(op);
  192. assert("ALTER TABLE users DROP COLUMN age" == sql);
  193. print("PASSED\n");
  194. }
  195. void test_rename_column_sql() throws SqlError {
  196. print("Test: RENAME COLUMN SQL generation... ");
  197. var dialect = new SqliteDialect();
  198. var op = new RenameColumnOperation() {
  199. table_name = "users",
  200. old_name = "old_name",
  201. new_name = "new_name"
  202. };
  203. var sql = dialect.rename_column_sql(op);
  204. assert("ALTER TABLE users RENAME COLUMN old_name TO new_name" == sql);
  205. print("PASSED\n");
  206. }
  207. void test_migration_builder_create_table() throws SqlError {
  208. print("Test: MigrationBuilder create_table... ");
  209. var dialect = new SqliteDialect();
  210. var builder = new MigrationBuilder(dialect);
  211. // Type inferred from generic parameter - cleaner API
  212. builder.create_table("users", t => {
  213. t.column<int64?>("id").primary_key().auto_increment();
  214. t.column<string>("name").not_null();
  215. });
  216. var ops = builder.get_operations();
  217. assert(ops.length == 1);
  218. var op = ops[0] as CreateTableOperation;
  219. assert(op != null);
  220. assert(op.table_name == "users");
  221. assert(op.columns.length == 2);
  222. print("PASSED\n");
  223. }
  224. void test_migration_builder_create_table_with_indexes() throws SqlError {
  225. print("Test: MigrationBuilder create_table with indexes... ");
  226. var dialect = new SqliteDialect();
  227. var builder = new MigrationBuilder(dialect);
  228. // Create table with indexes defined inline
  229. builder.create_table("users", t => {
  230. t.column<int64?>("id").primary_key().auto_increment();
  231. t.column<string>("email").not_null();
  232. t.column<string>("name").not_null();
  233. // Single column index
  234. t.index("idx_email").on_column("email");
  235. // Composite unique index
  236. t.index("idx_email_name").on_columns("email", "name").unique();
  237. });
  238. var ops = builder.get_operations();
  239. // Should have 1 create table + 2 create index operations
  240. assert(ops.length == 3);
  241. var table_op = ops[0] as CreateTableOperation;
  242. assert(table_op != null);
  243. assert(table_op.table_name == "users");
  244. var idx1_op = ops[1] as CreateIndexOperation;
  245. assert(idx1_op != null);
  246. assert(idx1_op.index_name == "idx_email");
  247. assert(idx1_op.is_unique == false);
  248. var idx2_op = ops[2] as CreateIndexOperation;
  249. assert(idx2_op != null);
  250. assert(idx2_op.index_name == "idx_email_name");
  251. assert(idx2_op.is_unique == true);
  252. print("PASSED\n");
  253. }
  254. void test_migration_builder_alter_table() throws SqlError {
  255. print("Test: MigrationBuilder alter_table... ");
  256. var dialect = new SqliteDialect();
  257. var builder = new MigrationBuilder(dialect);
  258. // Type inferred from generic parameter - cleaner API
  259. builder.alter_table("users", t => {
  260. t.add_column<string>("email").not_null();
  261. t.drop_column("old_column");
  262. t.rename_column("old_name", "new_name");
  263. });
  264. var ops = builder.get_operations();
  265. assert(ops.length == 3);
  266. assert(ops[0] is AddColumnOperation);
  267. assert(ops[1] is DropColumnOperation);
  268. assert(ops[2] is RenameColumnOperation);
  269. print("PASSED\n");
  270. }
  271. void test_migration_builder_alter_table_with_indexes() throws SqlError {
  272. print("Test: MigrationBuilder alter_table with indexes... ");
  273. var dialect = new SqliteDialect();
  274. var builder = new MigrationBuilder(dialect);
  275. builder.alter_table("users", t => {
  276. t.add_column<string>("phone");
  277. // Create index on new column
  278. t.create_index("idx_phone").on_column("phone");
  279. // Drop old index
  280. t.drop_index("idx_old_email");
  281. });
  282. var ops = builder.get_operations();
  283. assert(ops.length == 3);
  284. assert(ops[0] is AddColumnOperation);
  285. assert(ops[1] is CreateIndexOperation);
  286. assert(ops[2] is DropIndexOperation);
  287. print("PASSED\n");
  288. }
  289. void test_index_builder_single_column() throws SqlError {
  290. print("Test: IndexBuilder single column... ");
  291. var dialect = new SqliteDialect();
  292. var builder = new MigrationBuilder(dialect);
  293. builder.create_table("test", t => {
  294. t.column<string>("name").not_null();
  295. t.index("idx_name").on_column("name");
  296. });
  297. var ops = builder.get_operations();
  298. var idx_op = ops[1] as CreateIndexOperation;
  299. assert(idx_op != null);
  300. assert(idx_op.index_name == "idx_name");
  301. assert(idx_op.columns.length == 1);
  302. assert(idx_op.columns[0] == "name");
  303. assert(idx_op.is_unique == false);
  304. print("PASSED\n");
  305. }
  306. void test_index_builder_composite() throws SqlError {
  307. print("Test: IndexBuilder composite... ");
  308. var dialect = new SqliteDialect();
  309. var builder = new MigrationBuilder(dialect);
  310. builder.create_table("test", t => {
  311. t.column<string>("col1").not_null();
  312. t.column<string>("col2").not_null();
  313. t.column<string>("col3").not_null();
  314. t.index("idx_composite").on_columns("col1", "col2", "col3");
  315. });
  316. var ops = builder.get_operations();
  317. var idx_op = ops[1] as CreateIndexOperation;
  318. assert(idx_op != null);
  319. assert(idx_op.index_name == "idx_composite");
  320. assert(idx_op.columns.length == 3);
  321. assert(idx_op.columns[0] == "col1");
  322. assert(idx_op.columns[1] == "col2");
  323. assert(idx_op.columns[2] == "col3");
  324. print("PASSED\n");
  325. }
  326. void test_index_builder_unique() throws SqlError {
  327. print("Test: IndexBuilder unique... ");
  328. var dialect = new SqliteDialect();
  329. var builder = new MigrationBuilder(dialect);
  330. builder.create_table("test", t => {
  331. t.column<string>("email").not_null();
  332. t.index("uq_email").on_column("email").unique();
  333. });
  334. var ops = builder.get_operations();
  335. var idx_op = ops[1] as CreateIndexOperation;
  336. assert(idx_op != null);
  337. assert(idx_op.index_name == "uq_email");
  338. assert(idx_op.is_unique == true);
  339. print("PASSED\n");
  340. }
  341. void test_migration_runner_registration() throws SqlError {
  342. print("Test: MigrationRunner registration... ");
  343. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  344. var dialect = new SqliteDialect();
  345. var runner = new MigrationRunner(conn, dialect);
  346. runner.register_migration(new V001_CreateUsers());
  347. runner.register_migration(new V002_AddAgeColumn());
  348. // Verify migrations are registered - current version should be 0
  349. var current = runner.get_current_version();
  350. assert(current == 0); // No migrations applied yet
  351. print("PASSED\n");
  352. conn.close();
  353. }
  354. void test_migration_runner_migrate_to_latest() throws SqlError {
  355. print("Test: MigrationRunner migrate_to_latest... ");
  356. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  357. var dialect = new SqliteDialect();
  358. var runner = new MigrationRunner(conn, dialect);
  359. runner.register_migration(new V001_CreateUsers());
  360. runner.register_migration(new V002_AddAgeColumn());
  361. runner.migrate_to_latest();
  362. var current = runner.get_current_version();
  363. assert(current == 2);
  364. // Verify table was created
  365. var cmd = conn.create_command("SELECT name FROM sqlite_master WHERE type='table' AND name='users'");
  366. var results = cmd.execute_query();
  367. assert(results.any());
  368. print("PASSED\n");
  369. conn.close();
  370. }
  371. void test_migration_runner_migrate_to_version() throws SqlError {
  372. print("Test: MigrationRunner migrate_to_version... ");
  373. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  374. var dialect = new SqliteDialect();
  375. var runner = new MigrationRunner(conn, dialect);
  376. runner.register_migration(new V001_CreateUsers());
  377. runner.register_migration(new V002_AddAgeColumn());
  378. // Migrate to version 1 only
  379. runner.migrate_to(1);
  380. assert(runner.get_current_version() == 1);
  381. // Then migrate to version 2
  382. runner.migrate_to(2);
  383. assert(runner.get_current_version() == 2);
  384. print("PASSED\n");
  385. conn.close();
  386. }
  387. void test_migration_runner_rollback() throws SqlError {
  388. print("Test: MigrationRunner rollback... ");
  389. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  390. var dialect = new SqliteDialect();
  391. var runner = new MigrationRunner(conn, dialect);
  392. runner.register_migration(new V001_CreateUsers());
  393. runner.register_migration(new V002_AddAgeColumn());
  394. runner.migrate_to_latest();
  395. assert(runner.get_current_version() == 2);
  396. // Rollback one step
  397. runner.rollback(1);
  398. assert(runner.get_current_version() == 1);
  399. print("PASSED\n");
  400. conn.close();
  401. }
  402. void test_migration_runner_rollback_all() throws SqlError {
  403. print("Test: MigrationRunner rollback_all... ");
  404. var conn = ConnectionFactory.create_and_open("sqlite::memory:");
  405. var dialect = new SqliteDialect();
  406. var runner = new MigrationRunner(conn, dialect);
  407. runner.register_migration(new V001_CreateUsers());
  408. runner.register_migration(new V002_AddAgeColumn());
  409. runner.migrate_to_latest();
  410. assert(runner.get_current_version() == 2);
  411. // Rollback all
  412. runner.rollback_all();
  413. assert(runner.get_current_version() == 0);
  414. print("PASSED\n");
  415. conn.close();
  416. }
  417. // ========== Foreign Key Tests ==========
  418. void test_fk_creation_with_auto_generated_name() throws SqlError {
  419. print("Test: FK creation with auto-generated name... ");
  420. var dialect = new SqliteDialect();
  421. var builder = new MigrationBuilder(dialect);
  422. builder.create_table("orders", t => {
  423. t.column<int64?>("id").primary_key().auto_increment();
  424. t.column<int64?>("user_id")
  425. .not_null()
  426. .references("users", "id");
  427. });
  428. var ops = builder.get_operations();
  429. assert(ops.length == 1);
  430. var table_op = ops[0] as CreateTableOperation;
  431. assert(table_op != null);
  432. // Debug: print constraint count
  433. if (table_op.constraints.length != 1) {
  434. print("\n DEBUG: constraints.length = %u (expected 1)\n", table_op.constraints.length);
  435. }
  436. assert(table_op.constraints.length == 1);
  437. var constraint = table_op.constraints.get(0);
  438. assert(constraint.constraint_type == "FOREIGN KEY");
  439. assert(constraint.name == "fk_orders_user_id"); // Auto-generated name
  440. assert(constraint.reference_table == "users");
  441. assert(constraint.reference_columns.get(0) == "id");
  442. assert(constraint.columns.get(0) == "user_id");
  443. // Verify SQL contains proper FOREIGN KEY clause
  444. var sql = dialect.create_table_sql(table_op);
  445. assert("FOREIGN KEY" in sql);
  446. assert("REFERENCES users (id)" in sql);
  447. assert("fk_orders_user_id" in sql);
  448. print("PASSED\n");
  449. }
  450. void test_fk_creation_with_explicit_name() throws SqlError {
  451. print("Test: FK creation with explicit name... ");
  452. var dialect = new SqliteDialect();
  453. var builder = new MigrationBuilder(dialect);
  454. builder.create_table("orders", t => {
  455. t.column<int64?>("id").primary_key().auto_increment();
  456. t.column<int64?>("user_id")
  457. .not_null()
  458. .references("users", "id")
  459. .name("custom_fk_orders_users");
  460. });
  461. var ops = builder.get_operations();
  462. var table_op = ops[0] as CreateTableOperation;
  463. assert(table_op != null);
  464. var constraint = table_op.constraints.get(0);
  465. assert(constraint.name == "custom_fk_orders_users"); // Custom name
  466. var sql = dialect.create_table_sql(table_op);
  467. assert("custom_fk_orders_users" in sql);
  468. print("PASSED\n");
  469. }
  470. void test_fk_on_delete_actions() throws SqlError {
  471. print("Test: FK ON DELETE actions... ");
  472. var dialect = new SqliteDialect();
  473. // Test CASCADE
  474. var builder = new MigrationBuilder(dialect);
  475. builder.create_table("orders", t => {
  476. t.column<int64?>("user_id")
  477. .references("users", "id")
  478. .on_delete_cascade();
  479. });
  480. var ops = builder.get_operations();
  481. var table_op = ops[0] as CreateTableOperation;
  482. var constraint = table_op.constraints.get(0);
  483. assert(constraint.on_delete_action == ReferentialAction.CASCADE);
  484. var sql = dialect.create_table_sql(table_op);
  485. assert("ON DELETE CASCADE" in sql);
  486. // Test SET NULL
  487. builder = new MigrationBuilder(dialect);
  488. builder.create_table("orders", t => {
  489. t.column<int64?>("user_id")
  490. .references("users", "id")
  491. .on_delete_set_null();
  492. });
  493. ops = builder.get_operations();
  494. table_op = ops[0] as CreateTableOperation;
  495. constraint = table_op.constraints.get(0);
  496. assert(constraint.on_delete_action == ReferentialAction.SET_NULL);
  497. sql = dialect.create_table_sql(table_op);
  498. assert("ON DELETE SET NULL" in sql);
  499. // Test SET DEFAULT
  500. builder = new MigrationBuilder(dialect);
  501. builder.create_table("orders", t => {
  502. t.column<int64?>("user_id")
  503. .references("users", "id")
  504. .on_delete_set_default();
  505. });
  506. ops = builder.get_operations();
  507. table_op = ops[0] as CreateTableOperation;
  508. constraint = table_op.constraints.get(0);
  509. assert(constraint.on_delete_action == ReferentialAction.SET_DEFAULT);
  510. sql = dialect.create_table_sql(table_op);
  511. assert("ON DELETE SET DEFAULT" in sql);
  512. // Test NO ACTION
  513. builder = new MigrationBuilder(dialect);
  514. builder.create_table("orders", t => {
  515. t.column<int64?>("user_id")
  516. .references("users", "id")
  517. .on_delete_no_action();
  518. });
  519. ops = builder.get_operations();
  520. table_op = ops[0] as CreateTableOperation;
  521. constraint = table_op.constraints.get(0);
  522. assert(constraint.on_delete_action == ReferentialAction.NO_ACTION);
  523. // Test RESTRICT
  524. builder = new MigrationBuilder(dialect);
  525. builder.create_table("orders", t => {
  526. t.column<int64?>("user_id")
  527. .references("users", "id")
  528. .on_delete_restrict();
  529. });
  530. ops = builder.get_operations();
  531. table_op = ops[0] as CreateTableOperation;
  532. constraint = table_op.constraints.get(0);
  533. assert(constraint.on_delete_action == ReferentialAction.RESTRICT);
  534. sql = dialect.create_table_sql(table_op);
  535. assert("ON DELETE RESTRICT" in sql);
  536. print("PASSED\n");
  537. }
  538. void test_fk_on_update_actions() throws SqlError {
  539. print("Test: FK ON UPDATE actions... ");
  540. var dialect = new SqliteDialect();
  541. // Test CASCADE
  542. var builder = new MigrationBuilder(dialect);
  543. builder.create_table("orders", t => {
  544. t.column<int64?>("user_id")
  545. .references("users", "id")
  546. .on_update_cascade();
  547. });
  548. var ops = builder.get_operations();
  549. var table_op = ops[0] as CreateTableOperation;
  550. var constraint = table_op.constraints.get(0);
  551. assert(constraint.on_update_action == ReferentialAction.CASCADE);
  552. var sql = dialect.create_table_sql(table_op);
  553. assert("ON UPDATE CASCADE" in sql);
  554. // Test SET NULL
  555. builder = new MigrationBuilder(dialect);
  556. builder.create_table("orders", t => {
  557. t.column<int64?>("user_id")
  558. .references("users", "id")
  559. .on_update_set_null();
  560. });
  561. ops = builder.get_operations();
  562. table_op = ops[0] as CreateTableOperation;
  563. constraint = table_op.constraints.get(0);
  564. assert(constraint.on_update_action == ReferentialAction.SET_NULL);
  565. sql = dialect.create_table_sql(table_op);
  566. assert("ON UPDATE SET NULL" in sql);
  567. // Test SET DEFAULT
  568. builder = new MigrationBuilder(dialect);
  569. builder.create_table("orders", t => {
  570. t.column<int64?>("user_id")
  571. .references("users", "id")
  572. .on_update_set_default();
  573. });
  574. ops = builder.get_operations();
  575. table_op = ops[0] as CreateTableOperation;
  576. constraint = table_op.constraints.get(0);
  577. assert(constraint.on_update_action == ReferentialAction.SET_DEFAULT);
  578. sql = dialect.create_table_sql(table_op);
  579. assert("ON UPDATE SET DEFAULT" in sql);
  580. // Test NO ACTION
  581. builder = new MigrationBuilder(dialect);
  582. builder.create_table("orders", t => {
  583. t.column<int64?>("user_id")
  584. .references("users", "id")
  585. .on_update_no_action();
  586. });
  587. ops = builder.get_operations();
  588. table_op = ops[0] as CreateTableOperation;
  589. constraint = table_op.constraints.get(0);
  590. assert(constraint.on_update_action == ReferentialAction.NO_ACTION);
  591. // Test RESTRICT
  592. builder = new MigrationBuilder(dialect);
  593. builder.create_table("orders", t => {
  594. t.column<int64?>("user_id")
  595. .references("users", "id")
  596. .on_update_restrict();
  597. });
  598. ops = builder.get_operations();
  599. table_op = ops[0] as CreateTableOperation;
  600. constraint = table_op.constraints.get(0);
  601. assert(constraint.on_update_action == ReferentialAction.RESTRICT);
  602. sql = dialect.create_table_sql(table_op);
  603. assert("ON UPDATE RESTRICT" in sql);
  604. print("PASSED\n");
  605. }
  606. void test_fk_in_alter_table_add_column() throws SqlError {
  607. print("Test: FK in ALTER TABLE add column... ");
  608. var dialect = new SqliteDialect();
  609. var builder = new MigrationBuilder(dialect);
  610. builder.alter_table("orders", t => {
  611. t.add_column<int64?>("product_id")
  612. .not_null()
  613. .references("products", "id")
  614. .on_delete_restrict();
  615. });
  616. var ops = builder.get_operations();
  617. assert(ops.length == 1);
  618. var add_col_op = ops[0] as AddColumnOperation;
  619. assert(add_col_op != null);
  620. assert(add_col_op.foreign_key_constraint != null);
  621. var fk = add_col_op.foreign_key_constraint;
  622. assert(fk.constraint_type == "FOREIGN KEY");
  623. assert(fk.reference_table == "products");
  624. assert(fk.reference_columns.get(0) == "id");
  625. assert(fk.on_delete_action == ReferentialAction.RESTRICT);
  626. // Verify SQL contains inline REFERENCES clause
  627. var sql = dialect.add_column_sql(add_col_op);
  628. assert("REFERENCES products (id)" in sql);
  629. assert("ON DELETE RESTRICT" in sql);
  630. print("PASSED\n");
  631. }
  632. // ========== Indexed Column Tests ==========
  633. void test_indexed_with_auto_generated_name() throws SqlError {
  634. print("Test: indexed() with auto-generated name... ");
  635. var dialect = new SqliteDialect();
  636. var builder = new MigrationBuilder(dialect);
  637. builder.create_table("users", t => {
  638. t.column<int64?>("id").primary_key().auto_increment();
  639. t.column<string>("email")
  640. .not_null()
  641. .indexed();
  642. });
  643. var ops = builder.get_operations();
  644. // Should have 1 create table + 1 create index operation
  645. assert(ops.length == 2);
  646. var table_op = ops[0] as CreateTableOperation;
  647. assert(table_op != null);
  648. var idx_op = ops[1] as CreateIndexOperation;
  649. assert(idx_op != null);
  650. assert(idx_op.index_name == "idx_users_email"); // Auto-generated name
  651. assert(idx_op.table_name == "users");
  652. assert(idx_op.columns.length == 1);
  653. assert(idx_op.columns.get(0) == "email");
  654. assert(idx_op.is_unique == false);
  655. print("PASSED\n");
  656. }
  657. void test_indexed_with_custom_name() throws SqlError {
  658. print("Test: indexed() with custom name... ");
  659. var dialect = new SqliteDialect();
  660. var builder = new MigrationBuilder(dialect);
  661. builder.create_table("users", t => {
  662. t.column<int64?>("id").primary_key().auto_increment();
  663. t.column<string>("email")
  664. .not_null()
  665. .indexed("idx_users_email_address");
  666. });
  667. var ops = builder.get_operations();
  668. assert(ops.length == 2);
  669. var idx_op = ops[1] as CreateIndexOperation;
  670. assert(idx_op != null);
  671. assert(idx_op.index_name == "idx_users_email_address"); // Custom name
  672. print("PASSED\n");
  673. }
  674. void test_unique_indexed_creates_unique_index() throws SqlError {
  675. print("Test: unique().indexed() creates UNIQUE index... ");
  676. var dialect = new SqliteDialect();
  677. var builder = new MigrationBuilder(dialect);
  678. builder.create_table("users", t => {
  679. t.column<int64?>("id").primary_key().auto_increment();
  680. t.column<string>("email")
  681. .not_null()
  682. .unique()
  683. .indexed();
  684. });
  685. var ops = builder.get_operations();
  686. assert(ops.length == 2);
  687. var idx_op = ops[1] as CreateIndexOperation;
  688. assert(idx_op != null);
  689. assert(idx_op.is_unique == true); // Uniqueness inferred from column's unique() flag
  690. var sql = dialect.create_index_sql(idx_op);
  691. assert("CREATE UNIQUE INDEX" in sql);
  692. print("PASSED\n");
  693. }
  694. void test_drop_index_on() throws SqlError {
  695. print("Test: drop_index_on() generates correct DropIndexOperation... ");
  696. var dialect = new SqliteDialect();
  697. var builder = new MigrationBuilder(dialect);
  698. builder.alter_table("users", t => {
  699. t.drop_index_on("email");
  700. });
  701. var ops = builder.get_operations();
  702. assert(ops.length == 1);
  703. var drop_op = ops[0] as DropIndexOperation;
  704. assert(drop_op != null);
  705. assert(drop_op.index_name == "idx_users_email"); // Auto-generated name
  706. assert(drop_op.table_name == "users");
  707. print("PASSED\n");
  708. }