| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431 |
- using Invercargill.DataStructures;
- using Invercargill.Expressions;
- using InvercargillSql;
- using InvercargillSql.Orm;
- using InvercargillSql.Dialects;
- using InvercargillSql.Expressions;
- /**
- * Test entity for ORM tests.
- */
- public class TestUser : Object {
- public int64 id { get; set; }
- public string name { get; set; }
- public string email { get; set; }
- public int64 age { get; set; } // Use int64 for SQLite compatibility
- public double? salary { get; set; } // Nullable for database compatibility
- public bool? is_active { get; set; } // Nullable for database compatibility
- public DateTime? created_at { get; set; } // Nullable for database compatibility
-
- public TestUser() {
- name = "";
- email = "";
- }
- }
- /**
- * Test entity with binary data.
- */
- public class TestDocument : Object {
- public int64 id { get; set; }
- public string filename { get; set; }
- public Invercargill.BinaryData? content { get; set; }
-
- public TestDocument() {
- filename = "";
- }
- }
- /**
- * Test entity for Product ORM tests.
- */
- public class TestProduct : Object {
- public int64 id { get; set; }
- public string name { get; set; }
- public string category { get; set; }
- public double price { get; set; }
- public int64 stock { get; set; }
-
- public TestProduct() {
- name = "";
- category = "";
- }
- }
- /**
- * Test entity for Order ORM tests.
- */
- public class TestOrder : Object {
- public int64 id { get; set; }
- public int64 user_id { get; set; }
- public int64 product_id { get; set; }
- public int64 quantity { get; set; }
- public double total { get; set; }
- public string status { get; set; }
- public DateTime? created_at { get; set; }
-
- public TestOrder() {
- status = "";
- }
- }
- /**
- * Comprehensive tests for Invercargill-Sql ORM.
- */
- public int main(string[] args) {
- print("=== Invercargill-Sql ORM Tests ===\n\n");
-
- try {
- // ColumnType tests
- print("--- ColumnType Tests ---\n");
- test_column_type_from_gtype_int();
- test_column_type_from_gtype_int64();
- test_column_type_from_gtype_string();
- test_column_type_from_gtype_bool();
- test_column_type_from_gtype_double();
- test_column_type_from_gtype_float();
- test_column_type_from_gtype_datetime();
- test_column_type_from_gtype_binary();
- test_column_type_from_gtype_unsupported();
-
- // ColumnDefinition tests
- print("\n--- ColumnDefinition Tests ---\n");
- test_column_definition_defaults();
- test_column_definition_properties();
-
- // IndexDefinition tests
- print("\n--- IndexDefinition Tests ---\n");
- test_index_definition_defaults();
- test_index_definition_add_columns();
-
- // EntityMapperBuilder tests
- print("\n--- EntityMapperBuilder Tests ---\n");
- test_entity_mapper_builder_table_name();
- test_entity_mapper_builder_simple_column();
- test_entity_mapper_builder_chained_columns();
-
- // EntityMapper tests
- print("\n--- EntityMapper Tests ---\n");
- test_entity_mapper_build_for();
- test_entity_mapper_materialise();
- test_entity_mapper_map_from();
-
- // SqliteDialect tests
- print("\n--- SqliteDialect Tests ---\n");
- test_sqlite_dialect_translate_type_int32();
- test_sqlite_dialect_translate_type_int64();
- test_sqlite_dialect_translate_type_text();
- test_sqlite_dialect_translate_type_boolean();
- test_sqlite_dialect_translate_type_decimal();
- test_sqlite_dialect_translate_type_datetime();
- test_sqlite_dialect_translate_type_binary();
- test_sqlite_dialect_translate_type_uuid();
- test_sqlite_dialect_build_select_all();
- test_sqlite_dialect_build_select_by_id();
- test_sqlite_dialect_build_insert_sql();
- test_sqlite_dialect_build_update_sql();
- test_sqlite_dialect_build_delete_sql();
-
- // ExpressionToSqlVisitor tests
- print("\n--- ExpressionToSqlVisitor Tests ---\n");
- test_expression_visitor_binary_equal();
- test_expression_visitor_binary_not_equal();
- test_expression_visitor_binary_greater_than();
- test_expression_visitor_binary_greater_equal();
- test_expression_visitor_binary_less_than();
- test_expression_visitor_binary_less_equal();
- test_expression_visitor_binary_and();
- test_expression_visitor_binary_or();
- test_expression_visitor_unary_not();
- test_expression_visitor_unary_negate();
- test_expression_visitor_literal();
- test_expression_visitor_property();
- test_expression_visitor_complex_nested();
- test_expression_visitor_arithmetic();
-
- // OrmSession integration tests
- print("\n--- OrmSession Integration Tests ---\n");
- test_orm_session_register();
- test_orm_session_register_with_schema();
- test_orm_session_create_query();
- test_orm_session_insert();
- test_orm_session_query_with_where();
- test_orm_session_update();
- test_orm_session_delete();
- test_orm_session_order_by();
- test_orm_session_limit_offset();
- test_orm_session_first();
-
- // Schema introspection tests
- print("\n--- Schema Introspection Tests ---\n");
- test_schema_introspection_basic();
- test_schema_introspection_with_register();
-
- // Primary key back-population tests
- print("\n--- Primary Key Back-Population Tests ---\n");
- test_insert_back_populates_primary_key();
- test_insert_back_populates_different_ids();
- test_insert_back_populates_product();
- test_insert_back_populates_order();
-
- print("\n=== All ORM tests passed! ===\n");
- return 0;
- } catch (Error e) {
- printerr("\n=== Test failed: %s ===\n", e.message);
- return 1;
- }
- }
- // ========================================
- // ColumnType Tests
- // ========================================
- void test_column_type_from_gtype_int() throws Error {
- print("Test: ColumnType.from_gtype(int)... ");
- var result = ColumnType.from_gtype(typeof(int));
- assert(result == ColumnType.INT_32);
- print("PASSED\n");
- }
- void test_column_type_from_gtype_int64() throws Error {
- print("Test: ColumnType.from_gtype(int64)... ");
- var result = ColumnType.from_gtype(typeof(int64));
- assert(result == ColumnType.INT_64);
- print("PASSED\n");
- }
- void test_column_type_from_gtype_string() throws Error {
- print("Test: ColumnType.from_gtype(string)... ");
- var result = ColumnType.from_gtype(typeof(string));
- assert(result == ColumnType.TEXT);
- print("PASSED\n");
- }
- void test_column_type_from_gtype_bool() throws Error {
- print("Test: ColumnType.from_gtype(bool)... ");
- var result = ColumnType.from_gtype(typeof(bool));
- assert(result == ColumnType.BOOLEAN);
- print("PASSED\n");
- }
- void test_column_type_from_gtype_double() throws Error {
- print("Test: ColumnType.from_gtype(double)... ");
- var result = ColumnType.from_gtype(typeof(double));
- assert(result == ColumnType.DECIMAL);
- print("PASSED\n");
- }
- void test_column_type_from_gtype_float() throws Error {
- print("Test: ColumnType.from_gtype(float)... ");
- var result = ColumnType.from_gtype(typeof(float));
- assert(result == ColumnType.DECIMAL);
- print("PASSED\n");
- }
- void test_column_type_from_gtype_datetime() throws Error {
- print("Test: ColumnType.from_gtype(DateTime)... ");
- var result = ColumnType.from_gtype(typeof(DateTime));
- assert(result == ColumnType.DATETIME);
- print("PASSED\n");
- }
- void test_column_type_from_gtype_binary() throws Error {
- print("Test: ColumnType.from_gtype(BinaryData)... ");
- var result = ColumnType.from_gtype(typeof(Invercargill.BinaryData));
- assert(result == ColumnType.BINARY);
- print("PASSED\n");
- }
- void test_column_type_from_gtype_unsupported() throws Error {
- print("Test: ColumnType.from_gtype(unsupported)... ");
- var result = ColumnType.from_gtype(typeof(Object));
- assert(result == null);
- print("PASSED\n");
- }
- // ========================================
- // ColumnDefinition Tests
- // ========================================
- void test_column_definition_defaults() throws Error {
- print("Test: ColumnDefinition defaults... ");
- var col = new ColumnDefinition();
- col.name = "test_col";
- col.column_type = ColumnType.TEXT;
-
- assert(col.name == "test_col");
- assert(col.column_type == ColumnType.TEXT);
- print("PASSED\n");
- }
- void test_column_definition_properties() throws Error {
- print("Test: ColumnDefinition properties... ");
- var col = new ColumnDefinition();
- col.name = "id";
- col.column_type = ColumnType.INT_64;
-
- assert(col.name == "id");
- assert(col.column_type == ColumnType.INT_64);
- print("PASSED\n");
- }
- // ========================================
- // IndexDefinition Tests
- // ========================================
- void test_index_definition_defaults() throws Error {
- print("Test: IndexDefinition defaults... ");
- var idx = new IndexDefinition();
- idx.name = "idx_test";
-
- assert(idx.name == "idx_test");
- assert(idx.is_unique == false);
- assert(idx.columns != null);
- assert(idx.columns.count() == 0);
- print("PASSED\n");
- }
- void test_index_definition_add_columns() throws Error {
- print("Test: IndexDefinition add columns... ");
- var idx = new IndexDefinition();
- idx.name = "idx_multi";
- idx.is_unique = true;
- idx.columns.add("col1");
- idx.columns.add("col2");
- idx.columns.add("col3");
-
- assert(idx.name == "idx_multi");
- assert(idx.is_unique == true);
- assert(idx.columns.count() == 3);
-
- var arr = idx.columns.to_array();
- assert(arr[0] == "col1");
- assert(arr[1] == "col2");
- assert(arr[2] == "col3");
- print("PASSED\n");
- }
- // ========================================
- // EntityMapperBuilder Tests
- // ========================================
- void test_entity_mapper_builder_table_name() throws Error {
- print("Test: EntityMapperBuilder table name... ");
- var mapper = EntityMapper.build_for<TestUser>(b => {
- b.table("users");
- });
-
- assert(mapper.table_name == "users");
- print("PASSED\n");
- }
- void test_entity_mapper_builder_simple_column() throws Error {
- print("Test: EntityMapperBuilder simple column... ");
- var mapper = EntityMapper.build_for<TestUser>(b => {
- b.table("users");
- b.column<int64?>("id", u => u.id, (u, v) => u.id = v);
- b.column<string>("name", u => u.name, (u, v) => u.name = v);
- });
-
- assert(mapper.columns.count() == 2);
-
- var cols = mapper.columns.to_array();
- assert(cols[0].name == "id");
- assert(cols[0].column_type == ColumnType.INT_64);
- assert(cols[1].name == "name");
- assert(cols[1].column_type == ColumnType.TEXT);
- print("PASSED\n");
- }
- void test_entity_mapper_builder_chained_columns() throws Error {
- print("Test: EntityMapperBuilder chained columns... ");
- // The new API allows natural chaining since column<T>() returns EntityMapperBuilder<T>
- var mapper = EntityMapper.build_for<TestUser>(b => {
- b.table("users")
- .column<int64?>("id", u => u.id, (u, v) => u.id = v)
- .column<string>("name", u => u.name, (u, v) => u.name = v)
- .column<string>("email", u => u.email, (u, v) => u.email = v);
- });
-
- assert(mapper.table_name == "users");
- assert(mapper.columns.count() == 3);
-
- var cols = mapper.columns.to_array();
- assert(cols[0].name == "id");
- assert(cols[1].name == "name");
- assert(cols[2].name == "email");
- print("PASSED\n");
- }
- // ========================================
- // EntityMapper Tests
- // ========================================
- void test_entity_mapper_build_for() throws Error {
- print("Test: EntityMapper.build_for... ");
- var mapper = EntityMapper.build_for<TestUser>(b => {
- b.table("users");
- b.column<int64?>("id", u => u.id, (u, v) => u.id = v);
- b.column<string>("name", u => u.name, (u, v) => u.name = v);
- });
-
- assert(mapper != null);
- assert(mapper.table_name == "users");
- assert(mapper.columns.count() == 2);
- assert(mapper.property_mapper != null);
- print("PASSED\n");
- }
- void test_entity_mapper_materialise() throws Error {
- print("Test: EntityMapper.materialise... ");
- var mapper = EntityMapper.build_for<TestUser>(b => {
- b.table("users");
- b.column<int64?>("id", u => u.id, (u, v) => u.id = v);
- b.column<string>("name", u => u.name, (u, v) => u.name = v);
- b.column<string>("email", u => u.email, (u, v) => u.email = v);
- });
-
- var props = new PropertyDictionary();
- props.set_native<int64?>("id", 42);
- props.set_native<string>("name", "Alice");
- props.set_native<string>("email", "alice@example.com");
-
- var user = mapper.materialise(props);
-
- assert(user.id == 42);
- assert(user.name == "Alice");
- assert(user.email == "alice@example.com");
- print("PASSED\n");
- }
- void test_entity_mapper_map_from() throws Error {
- print("Test: EntityMapper.map_from... ");
- var mapper = EntityMapper.build_for<TestUser>(b => {
- b.table("users");
- b.column<int64?>("id", u => u.id, (u, v) => u.id = v);
- b.column<string>("name", u => u.name, (u, v) => u.name = v);
- b.column<string>("email", u => u.email, (u, v) => u.email = v);
- });
-
- var user = new TestUser();
- user.id = 123;
- user.name = "Bob";
- user.email = "bob@example.com";
-
- var props = mapper.map_from(user);
-
- assert(props != null);
- var id_elem = props.get("id");
- var name_elem = props.get("name");
- var email_elem = props.get("email");
-
- assert(id_elem != null);
- assert(name_elem != null);
- assert(email_elem != null);
- print("PASSED\n");
- }
- // ========================================
- // SqliteDialect Tests
- // ========================================
- void test_sqlite_dialect_translate_type_int32() throws Error {
- print("Test: SqliteDialect.translate_type(INT_32)... ");
- var dialect = new SqliteDialect();
- var result = dialect.translate_type(ColumnType.INT_32);
- assert(result == "INTEGER");
- print("PASSED\n");
- }
- void test_sqlite_dialect_translate_type_int64() throws Error {
- print("Test: SqliteDialect.translate_type(INT_64)... ");
- var dialect = new SqliteDialect();
- var result = dialect.translate_type(ColumnType.INT_64);
- assert(result == "INTEGER");
- print("PASSED\n");
- }
- void test_sqlite_dialect_translate_type_text() throws Error {
- print("Test: SqliteDialect.translate_type(TEXT)... ");
- var dialect = new SqliteDialect();
- var result = dialect.translate_type(ColumnType.TEXT);
- assert(result == "TEXT");
- print("PASSED\n");
- }
- void test_sqlite_dialect_translate_type_boolean() throws Error {
- print("Test: SqliteDialect.translate_type(BOOLEAN)... ");
- var dialect = new SqliteDialect();
- var result = dialect.translate_type(ColumnType.BOOLEAN);
- assert(result == "INTEGER");
- print("PASSED\n");
- }
- void test_sqlite_dialect_translate_type_decimal() throws Error {
- print("Test: SqliteDialect.translate_type(DECIMAL)... ");
- var dialect = new SqliteDialect();
- var result = dialect.translate_type(ColumnType.DECIMAL);
- assert(result == "REAL");
- print("PASSED\n");
- }
- void test_sqlite_dialect_translate_type_datetime() throws Error {
- print("Test: SqliteDialect.translate_type(DATETIME)... ");
- var dialect = new SqliteDialect();
- var result = dialect.translate_type(ColumnType.DATETIME);
- assert(result == "INTEGER");
- print("PASSED\n");
- }
- void test_sqlite_dialect_translate_type_binary() throws Error {
- print("Test: SqliteDialect.translate_type(BINARY)... ");
- var dialect = new SqliteDialect();
- var result = dialect.translate_type(ColumnType.BINARY);
- assert(result == "BLOB");
- print("PASSED\n");
- }
- void test_sqlite_dialect_translate_type_uuid() throws Error {
- print("Test: SqliteDialect.translate_type(UUID)... ");
- var dialect = new SqliteDialect();
- var result = dialect.translate_type(ColumnType.UUID);
- assert(result == "TEXT");
- print("PASSED\n");
- }
- void test_sqlite_dialect_build_select_all() throws Error {
- print("Test: SqliteDialect.build_select_all... ");
- var dialect = new SqliteDialect();
- var sql = dialect.build_select_all("users");
- assert(sql == "SELECT * FROM users");
- print("PASSED\n");
- }
- void test_sqlite_dialect_build_select_by_id() throws Error {
- print("Test: SqliteDialect.build_select_by_id... ");
- var dialect = new SqliteDialect();
- var sql = dialect.build_select_by_id("users", "id");
- assert(sql == "SELECT * FROM users WHERE id = :id");
- print("PASSED\n");
- }
- void test_sqlite_dialect_build_insert_sql() throws Error {
- print("Test: SqliteDialect.build_insert_sql... ");
- var dialect = new SqliteDialect();
- var columns = new Vector<string>();
- columns.add("name");
- columns.add("email");
-
- var sql = dialect.build_insert_sql("users", columns);
- assert(sql == "INSERT INTO users (name, email) VALUES (:name, :email)");
- print("PASSED\n");
- }
- void test_sqlite_dialect_build_update_sql() throws Error {
- print("Test: SqliteDialect.build_update_sql... ");
- var dialect = new SqliteDialect();
- var columns = new Vector<string>();
- columns.add("id");
- columns.add("name");
- columns.add("email");
-
- var sql = dialect.build_update_sql("users", columns, "id");
- // id should be excluded from SET clause
- assert("UPDATE users SET" in sql);
- assert("name = :name" in sql);
- assert("email = :email" in sql);
- assert("WHERE id = :id" in sql);
- print("PASSED\n");
- }
- void test_sqlite_dialect_build_delete_sql() throws Error {
- print("Test: SqliteDialect.build_delete_sql... ");
- var dialect = new SqliteDialect();
- var sql = dialect.build_delete_sql("users", "id");
- assert(sql == "DELETE FROM users WHERE id = :id");
- print("PASSED\n");
- }
- // ========================================
- // ExpressionToSqlVisitor Tests
- // ========================================
- EntityMapper get_test_mapper() {
- return EntityMapper.build_for<TestUser>(b => {
- b.table("users");
- b.column<int64?>("id", u => u.id, (u, v) => u.id = v);
- b.column<string>("name", u => u.name, (u, v) => u.name = v);
- b.column<int64?>("age", u => u.age, (u, v) => u.age = v);
- });
- }
- void test_expression_visitor_binary_equal() throws Error {
- print("Test: ExpressionToSqlVisitor binary equal... ");
- var dialect = new SqliteDialect();
- var mapper = get_test_mapper();
- var visitor = new ExpressionToSqlVisitor(dialect, mapper);
-
- var left = new PropertyExpression(new VariableExpression("u"), "age");
- var right = new LiteralExpression(new Invercargill.NativeElement<int?>(25));
- var expr = new BinaryExpression(left, right, BinaryOperator.EQUAL);
-
- expr.accept(visitor);
-
- var sql = visitor.get_sql();
- assert("(age = :p1)" == sql);
- assert(visitor.get_parameters().count() == 1);
- print("PASSED\n");
- }
- void test_expression_visitor_binary_not_equal() throws Error {
- print("Test: ExpressionToSqlVisitor binary not equal... ");
- var dialect = new SqliteDialect();
- var mapper = get_test_mapper();
- var visitor = new ExpressionToSqlVisitor(dialect, mapper);
-
- var left = new PropertyExpression(new VariableExpression("u"), "age");
- var right = new LiteralExpression(new Invercargill.NativeElement<int?>(25));
- var expr = new BinaryExpression(left, right, BinaryOperator.NOT_EQUAL);
-
- expr.accept(visitor);
-
- var sql = visitor.get_sql();
- assert("(age <> :p1)" == sql);
- print("PASSED\n");
- }
- void test_expression_visitor_binary_greater_than() throws Error {
- print("Test: ExpressionToSqlVisitor binary greater than... ");
- var dialect = new SqliteDialect();
- var mapper = get_test_mapper();
- var visitor = new ExpressionToSqlVisitor(dialect, mapper);
-
- var left = new PropertyExpression(new VariableExpression("u"), "age");
- var right = new LiteralExpression(new Invercargill.NativeElement<int?>(18));
- var expr = new BinaryExpression(left, right, BinaryOperator.GREATER_THAN);
-
- expr.accept(visitor);
-
- var sql = visitor.get_sql();
- assert("(age > :p1)" == sql);
- print("PASSED\n");
- }
- void test_expression_visitor_binary_greater_equal() throws Error {
- print("Test: ExpressionToSqlVisitor binary greater equal... ");
- var dialect = new SqliteDialect();
- var mapper = get_test_mapper();
- var visitor = new ExpressionToSqlVisitor(dialect, mapper);
-
- var left = new PropertyExpression(new VariableExpression("u"), "age");
- var right = new LiteralExpression(new Invercargill.NativeElement<int?>(18));
- var expr = new BinaryExpression(left, right, BinaryOperator.GREATER_EQUAL);
-
- expr.accept(visitor);
-
- var sql = visitor.get_sql();
- assert("(age >= :p1)" == sql);
- print("PASSED\n");
- }
- void test_expression_visitor_binary_less_than() throws Error {
- print("Test: ExpressionToSqlVisitor binary less than... ");
- var dialect = new SqliteDialect();
- var mapper = get_test_mapper();
- var visitor = new ExpressionToSqlVisitor(dialect, mapper);
-
- var left = new PropertyExpression(new VariableExpression("u"), "age");
- var right = new LiteralExpression(new Invercargill.NativeElement<int?>(65));
- var expr = new BinaryExpression(left, right, BinaryOperator.LESS_THAN);
-
- expr.accept(visitor);
-
- var sql = visitor.get_sql();
- assert("(age < :p1)" == sql);
- print("PASSED\n");
- }
- void test_expression_visitor_binary_less_equal() throws Error {
- print("Test: ExpressionToSqlVisitor binary less equal... ");
- var dialect = new SqliteDialect();
- var mapper = get_test_mapper();
- var visitor = new ExpressionToSqlVisitor(dialect, mapper);
-
- var left = new PropertyExpression(new VariableExpression("u"), "age");
- var right = new LiteralExpression(new Invercargill.NativeElement<int?>(65));
- var expr = new BinaryExpression(left, right, BinaryOperator.LESS_EQUAL);
-
- expr.accept(visitor);
-
- var sql = visitor.get_sql();
- assert("(age <= :p1)" == sql);
- print("PASSED\n");
- }
- void test_expression_visitor_binary_and() throws Error {
- print("Test: ExpressionToSqlVisitor binary AND... ");
- var dialect = new SqliteDialect();
- var mapper = get_test_mapper();
- var visitor = new ExpressionToSqlVisitor(dialect, mapper);
-
- var left = new BinaryExpression(
- new PropertyExpression(new VariableExpression("u"), "age"),
- new LiteralExpression(new Invercargill.NativeElement<int?>(18)),
- BinaryOperator.GREATER_THAN
- );
- var right = new BinaryExpression(
- new PropertyExpression(new VariableExpression("u"), "age"),
- new LiteralExpression(new Invercargill.NativeElement<int?>(65)),
- BinaryOperator.LESS_THAN
- );
- var expr = new BinaryExpression(left, right, BinaryOperator.AND);
-
- expr.accept(visitor);
-
- var sql = visitor.get_sql();
- assert("((age > :p1) AND (age < :p2))" == sql);
- assert(visitor.get_parameters().count() == 2);
- print("PASSED\n");
- }
- void test_expression_visitor_binary_or() throws Error {
- print("Test: ExpressionToSqlVisitor binary OR... ");
- var dialect = new SqliteDialect();
- var mapper = get_test_mapper();
- var visitor = new ExpressionToSqlVisitor(dialect, mapper);
-
- var left = new BinaryExpression(
- new PropertyExpression(new VariableExpression("u"), "name"),
- new LiteralExpression(new Invercargill.NativeElement<string>("Alice")),
- BinaryOperator.EQUAL
- );
- var right = new BinaryExpression(
- new PropertyExpression(new VariableExpression("u"), "name"),
- new LiteralExpression(new Invercargill.NativeElement<string>("Bob")),
- BinaryOperator.EQUAL
- );
- var expr = new BinaryExpression(left, right, BinaryOperator.OR);
-
- expr.accept(visitor);
-
- var sql = visitor.get_sql();
- assert("((name = :p1) OR (name = :p2))" == sql);
- assert(visitor.get_parameters().count() == 2);
- print("PASSED\n");
- }
- void test_expression_visitor_unary_not() throws Error {
- print("Test: ExpressionToSqlVisitor unary NOT... ");
- var dialect = new SqliteDialect();
- var mapper = get_test_mapper();
- var visitor = new ExpressionToSqlVisitor(dialect, mapper);
-
- var operand = new BinaryExpression(
- new PropertyExpression(new VariableExpression("u"), "age"),
- new LiteralExpression(new Invercargill.NativeElement<int?>(25)),
- BinaryOperator.EQUAL
- );
- var expr = new UnaryExpression(UnaryOperator.NOT, operand);
-
- expr.accept(visitor);
-
- var sql = visitor.get_sql();
- assert("NOT (age = :p1)" == sql);
- print("PASSED\n");
- }
- void test_expression_visitor_unary_negate() throws Error {
- print("Test: ExpressionToSqlVisitor unary NEGATE... ");
- var dialect = new SqliteDialect();
- var mapper = get_test_mapper();
- var visitor = new ExpressionToSqlVisitor(dialect, mapper);
-
- var operand = new PropertyExpression(new VariableExpression("u"), "age");
- var expr = new UnaryExpression(UnaryOperator.NEGATE, operand);
-
- expr.accept(visitor);
-
- var sql = visitor.get_sql();
- assert("-age" == sql);
- print("PASSED\n");
- }
- void test_expression_visitor_literal() throws Error {
- print("Test: ExpressionToSqlVisitor literal... ");
- var dialect = new SqliteDialect();
- var mapper = get_test_mapper();
- var visitor = new ExpressionToSqlVisitor(dialect, mapper);
-
- var expr = new LiteralExpression(new Invercargill.NativeElement<string>("test"));
-
- expr.accept(visitor);
-
- var sql = visitor.get_sql();
- assert(":p1" == sql);
- assert(visitor.get_parameters().count() == 1);
- print("PASSED\n");
- }
- void test_expression_visitor_property() throws Error {
- print("Test: ExpressionToSqlVisitor property... ");
- var dialect = new SqliteDialect();
- var mapper = get_test_mapper();
- var visitor = new ExpressionToSqlVisitor(dialect, mapper);
-
- var expr = new PropertyExpression(new VariableExpression("u"), "name");
-
- expr.accept(visitor);
-
- var sql = visitor.get_sql();
- assert("name" == sql);
- print("PASSED\n");
- }
- void test_expression_visitor_complex_nested() throws Error {
- print("Test: ExpressionToSqlVisitor complex nested... ");
- var dialect = new SqliteDialect();
- var mapper = get_test_mapper();
- var visitor = new ExpressionToSqlVisitor(dialect, mapper);
-
- // (age > 18 AND age < 65) OR (name = 'Admin')
- var age_range = new BinaryExpression(
- new BinaryExpression(
- new PropertyExpression(new VariableExpression("u"), "age"),
- new LiteralExpression(new Invercargill.NativeElement<int?>(18)),
- BinaryOperator.GREATER_THAN
- ),
- new BinaryExpression(
- new PropertyExpression(new VariableExpression("u"), "age"),
- new LiteralExpression(new Invercargill.NativeElement<int?>(65)),
- BinaryOperator.LESS_THAN
- ),
- BinaryOperator.AND
- );
- var is_admin = new BinaryExpression(
- new PropertyExpression(new VariableExpression("u"), "name"),
- new LiteralExpression(new Invercargill.NativeElement<string>("Admin")),
- BinaryOperator.EQUAL
- );
- var expr = new BinaryExpression(age_range, is_admin, BinaryOperator.OR);
-
- expr.accept(visitor);
-
- var sql = visitor.get_sql();
- assert("(((age > :p1) AND (age < :p2)) OR (name = :p3))" == sql);
- assert(visitor.get_parameters().count() == 3);
- print("PASSED\n");
- }
- void test_expression_visitor_arithmetic() throws Error {
- print("Test: ExpressionToSqlVisitor arithmetic... ");
- var dialect = new SqliteDialect();
- var mapper = get_test_mapper();
- var visitor = new ExpressionToSqlVisitor(dialect, mapper);
-
- // age + 10
- var expr = new BinaryExpression(
- new PropertyExpression(new VariableExpression("u"), "age"),
- new LiteralExpression(new Invercargill.NativeElement<int?>(10)),
- BinaryOperator.ADD
- );
-
- expr.accept(visitor);
-
- var sql = visitor.get_sql();
- assert("(age + :p1)" == sql);
- print("PASSED\n");
- }
- // ========================================
- // OrmSession Integration Tests
- // ========================================
- OrmSession setup_test_session() throws SqlError {
- var conn = ConnectionFactory.create_and_open("sqlite::memory:");
- var session = new OrmSession(conn, new SqliteDialect());
-
- // Create table first (schema is managed by migrations)
- conn.execute("""
- CREATE TABLE users (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- name TEXT NOT NULL,
- email TEXT,
- age INTEGER,
- salary REAL,
- is_active INTEGER,
- created_at INTEGER
- )
- """);
-
- // Register TestUser mapper with schema introspection
- session.register_with_schema<TestUser>("users", b => {
- b.column<int64?>("id", u => u.id, (u, v) => u.id = v);
- b.column<string>("name", u => u.name, (u, v) => u.name = v);
- b.column<string>("email", u => u.email, (u, v) => u.email = v);
- b.column<int64?>("age", u => u.age, (u, v) => u.age = v);
- b.column<double?>("salary", u => u.salary, (u, v) => u.salary = v);
- b.column<bool?>("is_active", u => u.is_active, (u, v) => u.is_active = v);
- b.column<DateTime?>("created_at", u => u.created_at, (u, v) => u.created_at = v);
- });
-
- return session;
- }
- void test_orm_session_register() throws Error {
- print("Test: OrmSession register... ");
- var conn = ConnectionFactory.create_and_open("sqlite::memory:");
- var session = new OrmSession(conn, new SqliteDialect());
-
- // Create table first
- conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
-
- // Register using the simplified API (without schema methods)
- session.register<TestUser>(b => {
- b.table("users");
- b.column<int64?>("id", u => u.id, (u, v) => u.id = v);
- b.column<string>("name", u => u.name, (u, v) => u.name = v);
- });
-
- // If we got here without exception, registration worked
- print("PASSED\n");
- conn.close();
- }
- void test_orm_session_register_with_schema() throws Error {
- print("Test: OrmSession register_with_schema... ");
- var conn = ConnectionFactory.create_and_open("sqlite::memory:");
- var session = new OrmSession(conn, new SqliteDialect());
-
- // Create table with auto-increment primary key
- conn.execute("""
- CREATE TABLE users (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- name TEXT NOT NULL,
- email TEXT
- )
- """);
-
- // Register with schema introspection - PK and auto-increment discovered automatically
- session.register_with_schema<TestUser>("users", b => {
- b.column<int64?>("id", u => u.id, (u, v) => u.id = v);
- b.column<string>("name", u => u.name, (u, v) => u.name = v);
- b.column<string>("email", u => u.email, (u, v) => u.email = v);
- });
-
- // Verify the mapper has the schema information
- var mapper = session.get_mapper<TestUser>();
- assert(mapper != null);
- assert(mapper.table_name == "users");
- assert(mapper.get_effective_primary_key() == "id");
- assert(mapper.is_auto_increment("id") == true);
-
- print("PASSED\n");
- conn.close();
- }
- void test_orm_session_create_query() throws Error {
- print("Test: OrmSession create query... ");
- var session = setup_test_session();
-
- var query = session.query<TestUser>();
- assert(query != null);
-
- print("PASSED\n");
- }
- void test_orm_session_insert() throws Error {
- print("Test: OrmSession insert... ");
- var session = setup_test_session();
-
- var user = new TestUser();
- user.name = "Alice";
- user.email = "alice@example.com";
- user.age = 30;
- user.salary = 50000.0;
- user.is_active = true;
- user.created_at = new DateTime.now_utc();
-
- session.insert(user);
-
- // Verify insert worked by querying
- var results = session.query<TestUser>().materialise();
- var arr = results.to_array();
- assert(arr.length == 1);
-
- var inserted = arr[0];
- assert(inserted != null);
- assert(inserted.name == "Alice");
- assert(inserted.email == "alice@example.com");
- assert(inserted.age == 30);
-
- print("PASSED\n");
- }
- void test_orm_session_query_with_where() throws Error {
- print("Test: OrmSession query with where... ");
- var session = setup_test_session();
-
- // Insert test data
- var alice = new TestUser();
- alice.name = "Alice";
- alice.age = 30;
- session.insert(alice);
-
- var bob = new TestUser();
- bob.name = "Bob";
- bob.age = 25;
- session.insert(bob);
-
- var charlie = new TestUser();
- charlie.name = "Charlie";
- charlie.age = 35;
- session.insert(charlie);
-
- // Query with where clause
- var results = session.query<TestUser>()
- .where("age > 28")
- .materialise();
-
- var arr = results.to_array();
- assert(arr.length == 2);
-
- print("PASSED\n");
- }
- void test_orm_session_update() throws Error {
- print("Test: OrmSession update... ");
- var session = setup_test_session();
-
- // Insert test data
- var user = new TestUser();
- user.name = "Alice";
- user.email = "alice@example.com";
- user.age = 30;
- session.insert(user);
-
- // Get the inserted user with ID - use age for lookup since string literals
- // in expressions aren't supported by the expression parser
- var inserted_arr = session.query<TestUser>()
- .where("age == 30")
- .first();
- assert(inserted_arr != null);
-
- // Update the user
- inserted_arr.name = "Alice Updated";
- inserted_arr.age = 31;
- session.update(inserted_arr);
-
- // Verify update - use the ID we now know
- var updated = session.query<TestUser>()
- .where("id == " + inserted_arr.id.to_string())
- .first();
- assert(updated != null);
- assert(updated.name == "Alice Updated");
- assert(updated.age == 31);
-
- print("PASSED\n");
- }
- void test_orm_session_delete() throws Error {
- print("Test: OrmSession delete... ");
- var session = setup_test_session();
-
- // Insert test data
- var user = new TestUser();
- user.name = "ToDelete";
- user.age = 99;
- session.insert(user);
-
- // Get the inserted user with ID - use age for lookup
- var inserted = session.query<TestUser>()
- .where("age == 99")
- .first();
- assert(inserted != null);
-
- // Delete the user
- session.delete(inserted);
-
- // Verify deletion - use the ID
- var results = session.query<TestUser>()
- .where("id == " + inserted.id.to_string())
- .materialise();
- var arr = results.to_array();
- assert(arr.length == 0);
-
- print("PASSED\n");
- }
- void test_orm_session_order_by() throws Error {
- print("Test: OrmSession order by... ");
- var session = setup_test_session();
-
- // Insert test data
- var alice = new TestUser();
- alice.name = "Alice";
- alice.age = 30;
- session.insert(alice);
-
- var bob = new TestUser();
- bob.name = "Bob";
- bob.age = 25;
- session.insert(bob);
-
- var charlie = new TestUser();
- charlie.name = "Charlie";
- charlie.age = 35;
- session.insert(charlie);
-
- // Query with order by ascending
- var results = session.query<TestUser>()
- .order_by("age")
- .materialise();
-
- var arr = results.to_array();
- assert(arr.length == 3);
- assert(arr[0].age == 25); // Bob
- assert(arr[1].age == 30); // Alice
- assert(arr[2].age == 35); // Charlie
-
- // Query with order by descending
- var desc_results = session.query<TestUser>()
- .order_by_desc("age")
- .materialise();
-
- var desc_arr = desc_results.to_array();
- assert(desc_arr.length == 3);
- assert(desc_arr[0].age == 35); // Charlie
- assert(desc_arr[1].age == 30); // Alice
- assert(desc_arr[2].age == 25); // Bob
-
- print("PASSED\n");
- }
- void test_orm_session_limit_offset() throws Error {
- print("Test: OrmSession limit/offset... ");
- var session = setup_test_session();
-
- // Insert test data
- for (int i = 0; i < 10; i++) {
- var user = new TestUser();
- user.name = "User%d".printf(i);
- user.age = 20 + i;
- session.insert(user);
- }
-
- // Query with limit
- var limited = session.query<TestUser>()
- .order_by("age")
- .limit(3)
- .materialise();
-
- var limited_arr = limited.to_array();
- assert(limited_arr.length == 3);
-
- // Query with limit and offset
- var paged = session.query<TestUser>()
- .order_by("age")
- .limit(3)
- .offset(3)
- .materialise();
-
- var paged_arr = paged.to_array();
- assert(paged_arr.length == 3);
- assert(paged_arr[0].age == 23); // Skipped 20, 21, 22
-
- print("PASSED\n");
- }
- void test_orm_session_first() throws Error {
- print("Test: OrmSession first... ");
- var session = setup_test_session();
-
- // Insert test data
- var alice = new TestUser();
- alice.name = "Alice";
- alice.age = 30;
- session.insert(alice);
-
- var bob = new TestUser();
- bob.name = "Bob";
- bob.age = 25;
- session.insert(bob);
-
- // Get first with order
- var first = session.query<TestUser>()
- .order_by("age")
- .first();
-
- assert(first != null);
- assert(first.name == "Bob"); // Youngest
- assert(first.age == 25);
-
- // Query empty result
- var empty = session.query<TestUser>()
- .where("age > 100")
- .first();
-
- assert(empty == null);
-
- print("PASSED\n");
- }
- // ========================================
- // Schema Introspection Tests
- // ========================================
- void test_schema_introspection_basic() throws Error {
- print("Test: Schema introspection basic... ");
- var conn = ConnectionFactory.create_and_open("sqlite::memory:");
- var dialect = new SqliteDialect();
-
- // Create a table with various column types
- conn.execute("""
- CREATE TABLE test_table (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- name TEXT NOT NULL,
- email TEXT,
- age INTEGER,
- salary REAL,
- created_at INTEGER
- )
- """);
-
- // Introspect the schema
- var schema = dialect.introspect_schema(conn, "test_table");
-
- assert(schema != null);
- assert(schema.table_name == "test_table");
- assert(schema.primary_key_column == "id");
- assert(schema.columns.count() == 6);
-
- // Find the id column and verify it's marked as PK and auto-increment
- var id_col = schema.get_column("id");
- assert(id_col != null);
- assert(id_col.is_primary_key == true);
- assert(id_col.auto_increment == true);
-
- // Find the name column and verify it's required
- var name_col = schema.get_column("name");
- assert(name_col != null);
- assert(name_col.is_required == true);
-
- print("PASSED\n");
- conn.close();
- }
- void test_schema_introspection_with_register() throws Error {
- print("Test: Schema introspection with register... ");
- var conn = ConnectionFactory.create_and_open("sqlite::memory:");
- var session = new OrmSession(conn, new SqliteDialect());
-
- // Create table with composite structure
- conn.execute("""
- CREATE TABLE products (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- name TEXT NOT NULL,
- price REAL NOT NULL,
- description TEXT,
- created_at INTEGER
- )
- """);
-
- // Define a simple product class inline using TestUser as proxy
- // Register with schema introspection
- session.register_with_schema<TestUser>("products", b => {
- b.column<int64?>("id", u => u.id, (u, v) => u.id = v);
- b.column<string>("name", u => u.name, (u, v) => u.name = v);
- });
-
- // Verify schema was introspected and applied
- var mapper = session.get_mapper<TestUser>();
- assert(mapper != null);
- assert(mapper.table_name == "products");
- assert(mapper.get_effective_primary_key() == "id");
-
- print("PASSED\n");
- conn.close();
- }
- // ========================================
- // Primary Key Back-Population Tests
- // ========================================
- /**
- * Helper to set up a session with products table for testing.
- */
- OrmSession setup_product_session() throws SqlError {
- var conn = ConnectionFactory.create_and_open("sqlite::memory:");
- var session = new OrmSession(conn, new SqliteDialect());
-
- conn.execute("""
- CREATE TABLE products (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- name TEXT NOT NULL,
- category TEXT,
- price REAL,
- stock INTEGER
- )
- """);
-
- session.register_with_schema<TestProduct>("products", b => {
- b.column<int64?>("id", p => p.id, (p, v) => p.id = v);
- b.column<string>("name", p => p.name, (p, v) => p.name = v);
- b.column<string>("category", p => p.category, (p, v) => p.category = v);
- b.column<double?>("price", p => p.price, (p, v) => p.price = v);
- b.column<int64?>("stock", p => p.stock, (p, v) => p.stock = v);
- });
-
- return session;
- }
- /**
- * Helper to set up a session with orders table for testing.
- */
- OrmSession setup_order_session() throws SqlError {
- var conn = ConnectionFactory.create_and_open("sqlite::memory:");
- var session = new OrmSession(conn, new SqliteDialect());
-
- conn.execute("""
- CREATE TABLE orders (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- user_id INTEGER,
- product_id INTEGER,
- quantity INTEGER,
- total REAL,
- status TEXT,
- created_at INTEGER
- )
- """);
-
- session.register_with_schema<TestOrder>("orders", b => {
- b.column<int64?>("id", o => o.id, (o, v) => o.id = v);
- b.column<int64?>("user_id", o => o.user_id, (o, v) => o.user_id = v);
- b.column<int64?>("product_id", o => o.product_id, (o, v) => o.product_id = v);
- b.column<int64?>("quantity", o => o.quantity, (o, v) => o.quantity = v);
- b.column<double?>("total", o => o.total, (o, v) => o.total = v);
- b.column<string>("status", o => o.status, (o, v) => o.status = v);
- b.column<DateTime?>("created_at", o => o.created_at, (o, v) => o.created_at = v);
- });
-
- return session;
- }
- /**
- * Test: Basic insert back-populates primary key on User entity.
- *
- * Verifies that after inserting a new User entity with id = 0,
- * the id property is updated with the auto-generated database ID.
- */
- void test_insert_back_populates_primary_key() throws Error {
- print("Test: insert back-populates primary key on User... ");
- var session = setup_test_session();
-
- // Create a new user with id = 0 (default for new entities)
- var user = new TestUser();
- user.name = "TestUser";
- user.email = "test@example.com";
- user.age = 25;
-
- // Verify initial state - id should be 0
- assert(user.id == 0);
-
- // Insert the user
- session.insert(user);
-
- // After insert, the id should be back-populated with the generated ID
- assert(user.id > 0);
-
- print("PASSED\n");
- }
- /**
- * Test: Multiple inserts get different auto-generated IDs.
- *
- * Verifies that inserting multiple entities results in each
- * getting a unique, incrementing primary key.
- */
- void test_insert_back_populates_different_ids() throws Error {
- print("Test: multiple inserts get different IDs... ");
- var session = setup_test_session();
-
- // Create and insert first user
- var user1 = new TestUser();
- user1.name = "User1";
- user1.age = 20;
- session.insert(user1);
-
- // Create and insert second user
- var user2 = new TestUser();
- user2.name = "User2";
- user2.age = 25;
- session.insert(user2);
-
- // Both should have IDs > 0
- assert(user1.id > 0);
- assert(user2.id > 0);
-
- // IDs should be different
- assert(user1.id != user2.id);
-
- print("PASSED\n");
- }
- /**
- * Test: Insert back-populates primary key on Product entity.
- *
- * Verifies that the back-population works correctly for different
- * entity types, not just User.
- */
- void test_insert_back_populates_product() throws Error {
- print("Test: insert back-populates primary key on Product... ");
- var session = setup_product_session();
-
- // Create a new product with id = 0
- var product = new TestProduct();
- product.name = "Test Product";
- product.category = "Electronics";
- product.price = 99.99;
- product.stock = 100;
-
- // Verify initial state
- assert(product.id == 0);
-
- // Insert the product
- session.insert(product);
-
- // After insert, the id should be back-populated
- assert(product.id > 0);
-
- print("PASSED\n");
- }
- /**
- * Test: Insert back-populates primary key on Order entity.
- *
- * Verifies that the back-population works correctly for Order entities
- * which have different column types including DateTime.
- */
- void test_insert_back_populates_order() throws Error {
- print("Test: insert back-populates primary key on Order... ");
- var session = setup_order_session();
-
- // Create a new order with id = 0
- var order = new TestOrder();
- order.user_id = 1;
- order.product_id = 1;
- order.quantity = 2;
- order.total = 199.98;
- order.status = "pending";
- order.created_at = new DateTime.now_utc();
-
- // Verify initial state
- assert(order.id == 0);
-
- // Insert the order
- session.insert(order);
-
- // After insert, the id should be back-populated
- assert(order.id > 0);
-
- print("PASSED\n");
- }
|