diff --git a/packages/orm/angel_orm/lib/src/builder.dart b/packages/orm/angel_orm/lib/src/builder.dart index 313d1257..4810987a 100644 --- a/packages/orm/angel_orm/lib/src/builder.dart +++ b/packages/orm/angel_orm/lib/src/builder.dart @@ -238,7 +238,7 @@ class StringSqlExpressionBuilder extends SqlExpressionBuilder { void like(String pattern, {String Function(String)? sanitize}) { sanitize ??= (s) => pattern; _raw = 'LIKE \'' + sanitize('@$substitution') + '\''; - query.substitutionValues[substitution] = pattern; + //query.substitutionValues[substitution] = pattern; _hasValue = true; _value = null; } diff --git a/packages/orm/angel_orm/lib/src/query.dart b/packages/orm/angel_orm/lib/src/query.dart index 9529e057..0a2a5bb6 100644 --- a/packages/orm/angel_orm/lib/src/query.dart +++ b/packages/orm/angel_orm/lib/src/query.dart @@ -2,13 +2,6 @@ import 'dart:async'; import 'package:angel3_orm/angel3_orm.dart'; import 'package:logging/logging.dart'; -import 'annotations.dart'; -import 'join_builder.dart'; -import 'order_by.dart'; -import 'query_base.dart'; -import 'query_executor.dart'; -import 'query_values.dart'; -import 'query_where.dart'; import 'package:optional/optional.dart'; /// A SQL `SELECT` query builder. @@ -71,7 +64,7 @@ abstract class Query extends QueryBase { nn++; _names[name] = nn; } else { - _names[name] = 1; + _names[name] = 0; //1; } return n == 0 ? name : '$name$n'; } @@ -381,17 +374,19 @@ abstract class Query extends QueryBase { throw StateError('No values have been specified for update.'); } else { var sql = compile({}); - var returningSql = sql; + var returningSql = ''; if (executor.dialect is PostgreSQLDialect) { var returning = fields.map(adornWithTableName).join(', '); sql = 'WITH $tableName as ($insertion RETURNING $returning) ' + sql; } else if (executor.dialect is MySQLDialect) { + var returningSelect = values?.compileInsertSelect(this, tableName); + returningSql = '$sql where $returningSelect'; sql = '$insertion'; } else { - _log.fine("Unsupported database dialect."); + throw ArgumentError("Unsupported database dialect."); } - _log.fine("Insert Query = $sql"); + _log.warning("Insert Query = $sql"); return executor .query(tableName, sql, substitutionValues, @@ -409,24 +404,30 @@ abstract class Query extends QueryBase { if (valuesClause == '') { throw StateError('No values have been specified for update.'); - } else { - updateSql.write(' $valuesClause'); - var whereClause = where?.compile(); - if (whereClause?.isNotEmpty == true) { - updateSql.write(' WHERE $whereClause'); - } - if (_limit != null) updateSql.write(' LIMIT $_limit'); - - var returning = fields.map(adornWithTableName).join(', '); - var sql = compile({}); - sql = 'WITH $tableName as ($updateSql RETURNING $returning) ' + sql; - - //_log.fine("Update Query = $sql"); - - return executor - .query(tableName, sql, substitutionValues) - .then((it) => deserializeList(it)); } + updateSql.write(' $valuesClause'); + var whereClause = where?.compile(); + if (whereClause?.isNotEmpty == true) { + updateSql.write(' WHERE $whereClause'); + } + if (_limit != null) updateSql.write(' LIMIT $_limit'); + + var returning = fields.map(adornWithTableName).join(', '); + var sql = compile({}); + var returningSql = ''; + if (executor.dialect is PostgreSQLDialect) { + sql = 'WITH $tableName as ($updateSql RETURNING $returning) ' + sql; + } else if (executor.dialect is MySQLDialect) { + returningSql = sql; + sql = '$updateSql'; + } else { + throw ArgumentError("Unsupported database dialect."); + } + //_log.fine("Update Query = $sql"); + + return executor + .query(tableName, sql, substitutionValues, returningQuery: returningSql) + .then((it) => deserializeList(it)); } Future> updateOne(QueryExecutor executor) { diff --git a/packages/orm/angel_orm/lib/src/query_base.dart b/packages/orm/angel_orm/lib/src/query_base.dart index e6b3aab0..a7e8644a 100644 --- a/packages/orm/angel_orm/lib/src/query_base.dart +++ b/packages/orm/angel_orm/lib/src/query_base.dart @@ -57,8 +57,8 @@ abstract class QueryBase { Future> get(QueryExecutor executor) async { var sql = compile({}); - //_log.fine('sql = $sql'); - //_log.fine('substitutionValues = $substitutionValues'); + print('sql = $sql'); + print('substitutionValues = $substitutionValues'); return executor.query(tableName, sql, substitutionValues).then((it) { return deserializeList(it); diff --git a/packages/orm/angel_orm/lib/src/query_values.dart b/packages/orm/angel_orm/lib/src/query_values.dart index 2b265cf4..0748dacb 100644 --- a/packages/orm/angel_orm/lib/src/query_values.dart +++ b/packages/orm/angel_orm/lib/src/query_values.dart @@ -52,6 +52,20 @@ abstract class QueryValues { return b.toString(); } + String compileInsertSelect(Query query, String tableName) { + var data = Map.from(toMap()); + + var b = StringBuffer(); + var i = 0; + + for (var entry in data.entries) { + if (i++ > 0) b.write(' AND '); + b.write('$tableName.${entry.key} = ?'); + } + + return b.toString(); + } + String compileForUpdate(Query query) { var data = toMap(); if (data.isEmpty) { diff --git a/packages/orm/angel_orm/lib/src/util.dart b/packages/orm/angel_orm/lib/src/util.dart index 939b36c8..3a4352c6 100644 --- a/packages/orm/angel_orm/lib/src/util.dart +++ b/packages/orm/angel_orm/lib/src/util.dart @@ -1,3 +1,21 @@ import 'package:charcode/ascii.dart'; bool isAscii(int ch) => ch >= $nul && ch <= $del; + +bool mapToBool(dynamic value) { + if (value is int) { + return value != 0; + } + + return value != null ? value as bool : false; +} + +String mapToText(dynamic value) { + if (value == null) { + return ''; + } + if (value is! String) { + return value.toString(); + } + return value; +} diff --git a/packages/orm/angel_orm_mysql/example/main.dart b/packages/orm/angel_orm_mysql/example/main.dart index 7c371612..e2a8d9fc 100644 --- a/packages/orm/angel_orm_mysql/example/main.dart +++ b/packages/orm/angel_orm_mysql/example/main.dart @@ -22,11 +22,6 @@ void main() async { password: 'Test123*'); var connection = await MySqlConnection.connect(settings); - var results = await connection.query('select name, is_complete from todos'); - //await connection.close(); - - print("End"); - var logger = Logger('orm_mysql'); var executor = MySqlExecutor(connection, logger: logger); diff --git a/packages/orm/angel_orm_mysql/lib/angel3_orm_mysql.dart b/packages/orm/angel_orm_mysql/lib/angel3_orm_mysql.dart index 147afaff..55334902 100644 --- a/packages/orm/angel_orm_mysql/lib/angel3_orm_mysql.dart +++ b/packages/orm/angel_orm_mysql/lib/angel3_orm_mysql.dart @@ -90,16 +90,28 @@ class MySqlExecutor extends QueryExecutor { var params = substitutionValues.values.toList(); - logger?.fine('Query: $query'); - logger?.fine('Values: $params'); - logger?.fine('Returning Query: $returningQuery'); + logger?.warning('Query: $query'); + logger?.warning('Values: $params'); + logger?.warning('Returning Query: $returningQuery'); if (returningQuery.isNotEmpty) { // Handle insert, update and delete // Retrieve back the inserted record - var result = await _connection.query(query, params); - query = '$returningQuery where id = ?'; - params = [result.insertId]; + if (query.startsWith("INSERT")) { + var result = await _connection.query(query, params); + + query = returningQuery; + logger?.warning('Result.insertId: ${result.insertId}'); + + // No primary key + //if (result.insertId != 0) { + // params = [result.insertId]; + //} + } else if (query.startsWith("UPDATE")) { + await _connection.query(query, params); + query = returningQuery; + params = []; + } } // Handle select diff --git a/packages/orm/angel_orm_mysql/test/common.dart b/packages/orm/angel_orm_mysql/test/common.dart index e8afba19..7a2111f7 100644 --- a/packages/orm/angel_orm_mysql/test/common.dart +++ b/packages/orm/angel_orm_mysql/test/common.dart @@ -17,12 +17,21 @@ Future connectToMySql(Iterable schemas) async { db: 'orm_test', host: "localhost", user: Platform.environment['MYSQL_USERNAME'] ?? 'Test', - password: Platform.environment['MYSQL_PASSWORD'] ?? 'Test123*'); + password: Platform.environment['MYSQL_PASSWORD'] ?? 'Test123*', + timeout: Duration(minutes: 10)); var connection = await MySqlConnection.connect(settings); var logger = Logger('orm_mysql'); for (var s in schemas) { - await connection.query(await File('test/migrations/$s.sql').readAsString()); + // MySQL driver does not support multiple sql queries + var data = await File('test/migrations/$s.sql').readAsString(); + var queries = data.split(";"); + for (var q in queries) { + //print("Table: [$q]"); + if (q.trim().isNotEmpty) { + await connection.query(q); + } + } } return MySqlExecutor(connection, logger: logger); diff --git a/packages/orm/angel_orm_mysql/test/migrations/numba.sql b/packages/orm/angel_orm_mysql/test/migrations/numba.sql index 32fbc339..41fe7c4d 100644 --- a/packages/orm/angel_orm_mysql/test/migrations/numba.sql +++ b/packages/orm/angel_orm_mysql/test/migrations/numba.sql @@ -1,5 +1,5 @@ CREATE TEMPORARY TABLE numbas ( - i int, + i int NOT NULL UNIQUE, parent int, created_at TIMESTAMP, updated_at TIMESTAMP, diff --git a/packages/orm/angel_orm_mysql/test/migrations/unorthodox.sql b/packages/orm/angel_orm_mysql/test/migrations/unorthodox.sql index fc02b49f..24eaa397 100644 --- a/packages/orm/angel_orm_mysql/test/migrations/unorthodox.sql +++ b/packages/orm/angel_orm_mysql/test/migrations/unorthodox.sql @@ -1,4 +1,4 @@ CREATE TEMPORARY TABLE unorthodoxes ( - name varchar(255), + name varchar(255) NOT NULL UNIQUE, PRIMARY KEY(name) ); \ No newline at end of file diff --git a/packages/orm/angel_orm_mysql/test/orm_debug.dart b/packages/orm/angel_orm_mysql/test/orm_debug.dart new file mode 100644 index 00000000..96b21778 --- /dev/null +++ b/packages/orm/angel_orm_mysql/test/orm_debug.dart @@ -0,0 +1,21 @@ +import 'package:angel3_orm_test/angel3_orm_test.dart'; +import 'package:logging/logging.dart'; + +import 'common.dart'; + +void main() async { + //hierarchicalLoggingEnabled = true; + Logger.root + ..level = Level.INFO + ..onRecord.listen(print); + //Logger.root.onRecord.listen((rec) { + // print(rec); + // if (rec.error != null) print(rec.error); + // if (rec.stackTrace != null) print(rec.stackTrace); + //}); + + belongsToTests(my(['author', 'book']), close: closeMy); + + //edgeCaseTests(my(['unorthodox', 'weird_join', 'song', 'numba']), + // close: closeMy); +} diff --git a/packages/orm/angel_orm_postgres/test/orm_debug.dart b/packages/orm/angel_orm_postgres/test/orm_debug.dart new file mode 100644 index 00000000..eac9395f --- /dev/null +++ b/packages/orm/angel_orm_postgres/test/orm_debug.dart @@ -0,0 +1,18 @@ +import 'package:angel3_orm_test/angel3_orm_test.dart'; +import 'package:logging/logging.dart'; + +import 'common.dart'; + +void main() async { + hierarchicalLoggingEnabled = true; + Logger.root + ..level = Level.ALL + ..onRecord.listen(print); + //Logger.root.onRecord.listen((rec) { + // print(rec); + // if (rec.error != null) print(rec.error); + // if (rec.stackTrace != null) print(rec.stackTrace); + //}); + + belongsToTests(pg(['author', 'book']), close: closePg); +} diff --git a/packages/orm/angel_orm_test/lib/src/edge_case_test.dart b/packages/orm/angel_orm_test/lib/src/edge_case_test.dart index 11ab1b2f..2ed01d4a 100644 --- a/packages/orm/angel_orm_test/lib/src/edge_case_test.dart +++ b/packages/orm/angel_orm_test/lib/src/edge_case_test.dart @@ -5,18 +5,20 @@ import 'models/unorthodox.dart'; void edgeCaseTests(FutureOr Function() createExecutor, {FutureOr Function(QueryExecutor)? close}) { - late QueryExecutor executor; + QueryExecutor? executor; close ??= (_) => null; setUp(() async { executor = await createExecutor(); }); - tearDown(() => close!(executor)); + tearDown(() async { + //await close!(executor!); + }); test('can create object with no id', () async { var query = UnorthodoxQuery()..values.name = 'World'; - var modelOpt = await query.insert(executor); + var modelOpt = await query.insert(executor!); expect(modelOpt.isPresent, true); modelOpt.ifPresent((model) { expect(model, Unorthodox(name: 'World')); @@ -30,7 +32,7 @@ void edgeCaseTests(FutureOr Function() createExecutor, //if (unorthodox == null) { var query = UnorthodoxQuery()..values.name = 'Hey'; - var unorthodoxOpt = await query.insert(executor); + var unorthodoxOpt = await query.insert(executor!); unorthodoxOpt.ifPresent((value) { unorthodox = value; }); @@ -39,7 +41,7 @@ void edgeCaseTests(FutureOr Function() createExecutor, test('belongs to', () async { var query = WeirdJoinQuery()..values.joinName = unorthodox!.name; - var modelOpt = await query.insert(executor); + var modelOpt = await query.insert(executor!); expect(modelOpt.isPresent, true); modelOpt.ifPresent((model) { //print(model.toJson()); @@ -55,7 +57,7 @@ void edgeCaseTests(FutureOr Function() createExecutor, setUp(() async { var wjQuery = WeirdJoinQuery()..values.joinName = unorthodox!.name; - var weirdJoinOpt = await wjQuery.insert(executor); + var weirdJoinOpt = await wjQuery.insert(executor!); //weirdJoin = (await wjQuery.insert(executor)).value; weirdJoinOpt.ifPresent((value1) async { weirdJoin = value1; @@ -63,7 +65,7 @@ void edgeCaseTests(FutureOr Function() createExecutor, ..values.weirdJoinId = value1.id ..values.title = 'Girl Blue'; - var girlBlueOpt = await gbQuery.insert(executor); + var girlBlueOpt = await gbQuery.insert(executor!); girlBlueOpt.ifPresent((value2) { girlBlue = value2; }); @@ -72,7 +74,7 @@ void edgeCaseTests(FutureOr Function() createExecutor, test('has one', () async { var query = WeirdJoinQuery()..where!.id.equals(weirdJoin!.id!); - var wjOpt = await query.getOne(executor); + var wjOpt = await query.getOne(executor!); expect(wjOpt.isPresent, true); wjOpt.ifPresent((wj) { //print(wj.toJson()); @@ -87,7 +89,7 @@ void edgeCaseTests(FutureOr Function() createExecutor, var query = NumbaQuery() ..values.parent = weirdJoin!.id ..values.i = i; - var modelObj = await query.insert(executor); + var modelObj = await query.insert(executor!); expect(modelObj.isPresent, true); modelObj.ifPresent((model) { numbas.add(model); @@ -95,7 +97,7 @@ void edgeCaseTests(FutureOr Function() createExecutor, } var query = WeirdJoinQuery()..where!.id.equals(weirdJoin!.id!); - var wjObj = await query.getOne(executor); + var wjObj = await query.getOne(executor!); expect(wjObj.isPresent, true); wjObj.ifPresent((wj) { //print(wj.toJson()); @@ -106,14 +108,14 @@ void edgeCaseTests(FutureOr Function() createExecutor, test('many to many', () async { var fooQuery = FooQuery()..values.bar = 'baz'; var fooBar = - await fooQuery.insert(executor).then((foo) => foo.value.bar); + await fooQuery.insert(executor!).then((foo) => foo.value.bar); var pivotQuery = FooPivotQuery() ..values.weirdJoinId = weirdJoin!.id ..values.fooBar = fooBar; - await pivotQuery.insert(executor); + await pivotQuery.insert(executor!); fooQuery = FooQuery()..where!.bar.equals('baz'); - var fooOpt = await fooQuery.getOne(executor); + var fooOpt = await fooQuery.getOne(executor!); expect(fooOpt.isPresent, true); fooOpt.ifPresent((foo) { //print(foo.toJson()); diff --git a/packages/orm/angel_orm_test/lib/src/models/car.g.dart b/packages/orm/angel_orm_test/lib/src/models/car.g.dart index e7329ddc..2287dd49 100644 --- a/packages/orm/angel_orm_test/lib/src/models/car.g.dart +++ b/packages/orm/angel_orm_test/lib/src/models/car.g.dart @@ -94,10 +94,9 @@ class CarQuery extends Query { createdAt: fields.contains('created_at') ? (row[1] as DateTime?) : null, updatedAt: fields.contains('updated_at') ? (row[2] as DateTime?) : null, make: fields.contains('make') ? (row[3] as String?) : null, - description: - fields.contains('description') ? (row[4] as String?) : null, + description: fields.contains('description') ? mapToText(row[4]) : null, familyFriendly: - fields.contains('family_friendly') ? (row[5] as bool?) : null, + fields.contains('family_friendly') ? mapToBool(row[5]) : null, recalledAt: fields.contains('recalled_at') ? (row[6] as DateTime?) : null); return Optional.of(model);