Fixed insert into table without primary key

This commit is contained in:
thomashii 2022-02-06 15:27:19 +08:00
parent cd31c040ba
commit f28ba22bb4
14 changed files with 151 additions and 62 deletions

View file

@ -238,7 +238,7 @@ class StringSqlExpressionBuilder extends SqlExpressionBuilder<String> {
void like(String pattern, {String Function(String)? sanitize}) { void like(String pattern, {String Function(String)? sanitize}) {
sanitize ??= (s) => pattern; sanitize ??= (s) => pattern;
_raw = 'LIKE \'' + sanitize('@$substitution') + '\''; _raw = 'LIKE \'' + sanitize('@$substitution') + '\'';
query.substitutionValues[substitution] = pattern; //query.substitutionValues[substitution] = pattern;
_hasValue = true; _hasValue = true;
_value = null; _value = null;
} }

View file

@ -2,13 +2,6 @@ import 'dart:async';
import 'package:angel3_orm/angel3_orm.dart'; import 'package:angel3_orm/angel3_orm.dart';
import 'package:logging/logging.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'; import 'package:optional/optional.dart';
/// A SQL `SELECT` query builder. /// A SQL `SELECT` query builder.
@ -71,7 +64,7 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
nn++; nn++;
_names[name] = nn; _names[name] = nn;
} else { } else {
_names[name] = 1; _names[name] = 0; //1;
} }
return n == 0 ? name : '$name$n'; return n == 0 ? name : '$name$n';
} }
@ -381,17 +374,19 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
throw StateError('No values have been specified for update.'); throw StateError('No values have been specified for update.');
} else { } else {
var sql = compile({}); var sql = compile({});
var returningSql = sql; var returningSql = '';
if (executor.dialect is PostgreSQLDialect) { if (executor.dialect is PostgreSQLDialect) {
var returning = fields.map(adornWithTableName).join(', '); var returning = fields.map(adornWithTableName).join(', ');
sql = 'WITH $tableName as ($insertion RETURNING $returning) ' + sql; sql = 'WITH $tableName as ($insertion RETURNING $returning) ' + sql;
} else if (executor.dialect is MySQLDialect) { } else if (executor.dialect is MySQLDialect) {
var returningSelect = values?.compileInsertSelect(this, tableName);
returningSql = '$sql where $returningSelect';
sql = '$insertion'; sql = '$insertion';
} else { } else {
_log.fine("Unsupported database dialect."); throw ArgumentError("Unsupported database dialect.");
} }
_log.fine("Insert Query = $sql"); _log.warning("Insert Query = $sql");
return executor return executor
.query(tableName, sql, substitutionValues, .query(tableName, sql, substitutionValues,
@ -409,7 +404,7 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
if (valuesClause == '') { if (valuesClause == '') {
throw StateError('No values have been specified for update.'); throw StateError('No values have been specified for update.');
} else { }
updateSql.write(' $valuesClause'); updateSql.write(' $valuesClause');
var whereClause = where?.compile(); var whereClause = where?.compile();
if (whereClause?.isNotEmpty == true) { if (whereClause?.isNotEmpty == true) {
@ -419,15 +414,21 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
var returning = fields.map(adornWithTableName).join(', '); var returning = fields.map(adornWithTableName).join(', ');
var sql = compile({}); var sql = compile({});
var returningSql = '';
if (executor.dialect is PostgreSQLDialect) {
sql = 'WITH $tableName as ($updateSql RETURNING $returning) ' + sql; 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"); //_log.fine("Update Query = $sql");
return executor return executor
.query(tableName, sql, substitutionValues) .query(tableName, sql, substitutionValues, returningQuery: returningSql)
.then((it) => deserializeList(it)); .then((it) => deserializeList(it));
} }
}
Future<Optional<T>> updateOne(QueryExecutor executor) { Future<Optional<T>> updateOne(QueryExecutor executor) {
return update(executor).then( return update(executor).then(

View file

@ -57,8 +57,8 @@ abstract class QueryBase<T> {
Future<List<T>> get(QueryExecutor executor) async { Future<List<T>> get(QueryExecutor executor) async {
var sql = compile({}); var sql = compile({});
//_log.fine('sql = $sql'); print('sql = $sql');
//_log.fine('substitutionValues = $substitutionValues'); print('substitutionValues = $substitutionValues');
return executor.query(tableName, sql, substitutionValues).then((it) { return executor.query(tableName, sql, substitutionValues).then((it) {
return deserializeList(it); return deserializeList(it);

View file

@ -52,6 +52,20 @@ abstract class QueryValues {
return b.toString(); return b.toString();
} }
String compileInsertSelect(Query query, String tableName) {
var data = Map<String, dynamic>.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) { String compileForUpdate(Query query) {
var data = toMap(); var data = toMap();
if (data.isEmpty) { if (data.isEmpty) {

View file

@ -1,3 +1,21 @@
import 'package:charcode/ascii.dart'; import 'package:charcode/ascii.dart';
bool isAscii(int ch) => ch >= $nul && ch <= $del; 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;
}

View file

@ -22,11 +22,6 @@ void main() async {
password: 'Test123*'); password: 'Test123*');
var connection = await MySqlConnection.connect(settings); 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 logger = Logger('orm_mysql');
var executor = MySqlExecutor(connection, logger: logger); var executor = MySqlExecutor(connection, logger: logger);

View file

@ -90,16 +90,28 @@ class MySqlExecutor extends QueryExecutor {
var params = substitutionValues.values.toList(); var params = substitutionValues.values.toList();
logger?.fine('Query: $query'); logger?.warning('Query: $query');
logger?.fine('Values: $params'); logger?.warning('Values: $params');
logger?.fine('Returning Query: $returningQuery'); logger?.warning('Returning Query: $returningQuery');
if (returningQuery.isNotEmpty) { if (returningQuery.isNotEmpty) {
// Handle insert, update and delete // Handle insert, update and delete
// Retrieve back the inserted record // Retrieve back the inserted record
if (query.startsWith("INSERT")) {
var result = await _connection.query(query, params); var result = await _connection.query(query, params);
query = '$returningQuery where id = ?';
params = [result.insertId]; 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 // Handle select

View file

@ -17,12 +17,21 @@ Future<MySqlExecutor> connectToMySql(Iterable<String> schemas) async {
db: 'orm_test', db: 'orm_test',
host: "localhost", host: "localhost",
user: Platform.environment['MYSQL_USERNAME'] ?? 'Test', 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 connection = await MySqlConnection.connect(settings);
var logger = Logger('orm_mysql'); var logger = Logger('orm_mysql');
for (var s in schemas) { 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); return MySqlExecutor(connection, logger: logger);

View file

@ -1,5 +1,5 @@
CREATE TEMPORARY TABLE numbas ( CREATE TEMPORARY TABLE numbas (
i int, i int NOT NULL UNIQUE,
parent int, parent int,
created_at TIMESTAMP, created_at TIMESTAMP,
updated_at TIMESTAMP, updated_at TIMESTAMP,

View file

@ -1,4 +1,4 @@
CREATE TEMPORARY TABLE unorthodoxes ( CREATE TEMPORARY TABLE unorthodoxes (
name varchar(255), name varchar(255) NOT NULL UNIQUE,
PRIMARY KEY(name) PRIMARY KEY(name)
); );

View file

@ -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);
}

View file

@ -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);
}

View file

@ -5,18 +5,20 @@ import 'models/unorthodox.dart';
void edgeCaseTests(FutureOr<QueryExecutor> Function() createExecutor, void edgeCaseTests(FutureOr<QueryExecutor> Function() createExecutor,
{FutureOr<void> Function(QueryExecutor)? close}) { {FutureOr<void> Function(QueryExecutor)? close}) {
late QueryExecutor executor; QueryExecutor? executor;
close ??= (_) => null; close ??= (_) => null;
setUp(() async { setUp(() async {
executor = await createExecutor(); executor = await createExecutor();
}); });
tearDown(() => close!(executor)); tearDown(() async {
//await close!(executor!);
});
test('can create object with no id', () async { test('can create object with no id', () async {
var query = UnorthodoxQuery()..values.name = 'World'; var query = UnorthodoxQuery()..values.name = 'World';
var modelOpt = await query.insert(executor); var modelOpt = await query.insert(executor!);
expect(modelOpt.isPresent, true); expect(modelOpt.isPresent, true);
modelOpt.ifPresent((model) { modelOpt.ifPresent((model) {
expect(model, Unorthodox(name: 'World')); expect(model, Unorthodox(name: 'World'));
@ -30,7 +32,7 @@ void edgeCaseTests(FutureOr<QueryExecutor> Function() createExecutor,
//if (unorthodox == null) { //if (unorthodox == null) {
var query = UnorthodoxQuery()..values.name = 'Hey'; var query = UnorthodoxQuery()..values.name = 'Hey';
var unorthodoxOpt = await query.insert(executor); var unorthodoxOpt = await query.insert(executor!);
unorthodoxOpt.ifPresent((value) { unorthodoxOpt.ifPresent((value) {
unorthodox = value; unorthodox = value;
}); });
@ -39,7 +41,7 @@ void edgeCaseTests(FutureOr<QueryExecutor> Function() createExecutor,
test('belongs to', () async { test('belongs to', () async {
var query = WeirdJoinQuery()..values.joinName = unorthodox!.name; var query = WeirdJoinQuery()..values.joinName = unorthodox!.name;
var modelOpt = await query.insert(executor); var modelOpt = await query.insert(executor!);
expect(modelOpt.isPresent, true); expect(modelOpt.isPresent, true);
modelOpt.ifPresent((model) { modelOpt.ifPresent((model) {
//print(model.toJson()); //print(model.toJson());
@ -55,7 +57,7 @@ void edgeCaseTests(FutureOr<QueryExecutor> Function() createExecutor,
setUp(() async { setUp(() async {
var wjQuery = WeirdJoinQuery()..values.joinName = unorthodox!.name; 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; //weirdJoin = (await wjQuery.insert(executor)).value;
weirdJoinOpt.ifPresent((value1) async { weirdJoinOpt.ifPresent((value1) async {
weirdJoin = value1; weirdJoin = value1;
@ -63,7 +65,7 @@ void edgeCaseTests(FutureOr<QueryExecutor> Function() createExecutor,
..values.weirdJoinId = value1.id ..values.weirdJoinId = value1.id
..values.title = 'Girl Blue'; ..values.title = 'Girl Blue';
var girlBlueOpt = await gbQuery.insert(executor); var girlBlueOpt = await gbQuery.insert(executor!);
girlBlueOpt.ifPresent((value2) { girlBlueOpt.ifPresent((value2) {
girlBlue = value2; girlBlue = value2;
}); });
@ -72,7 +74,7 @@ void edgeCaseTests(FutureOr<QueryExecutor> Function() createExecutor,
test('has one', () async { test('has one', () async {
var query = WeirdJoinQuery()..where!.id.equals(weirdJoin!.id!); var query = WeirdJoinQuery()..where!.id.equals(weirdJoin!.id!);
var wjOpt = await query.getOne(executor); var wjOpt = await query.getOne(executor!);
expect(wjOpt.isPresent, true); expect(wjOpt.isPresent, true);
wjOpt.ifPresent((wj) { wjOpt.ifPresent((wj) {
//print(wj.toJson()); //print(wj.toJson());
@ -87,7 +89,7 @@ void edgeCaseTests(FutureOr<QueryExecutor> Function() createExecutor,
var query = NumbaQuery() var query = NumbaQuery()
..values.parent = weirdJoin!.id ..values.parent = weirdJoin!.id
..values.i = i; ..values.i = i;
var modelObj = await query.insert(executor); var modelObj = await query.insert(executor!);
expect(modelObj.isPresent, true); expect(modelObj.isPresent, true);
modelObj.ifPresent((model) { modelObj.ifPresent((model) {
numbas.add(model); numbas.add(model);
@ -95,7 +97,7 @@ void edgeCaseTests(FutureOr<QueryExecutor> Function() createExecutor,
} }
var query = WeirdJoinQuery()..where!.id.equals(weirdJoin!.id!); var query = WeirdJoinQuery()..where!.id.equals(weirdJoin!.id!);
var wjObj = await query.getOne(executor); var wjObj = await query.getOne(executor!);
expect(wjObj.isPresent, true); expect(wjObj.isPresent, true);
wjObj.ifPresent((wj) { wjObj.ifPresent((wj) {
//print(wj.toJson()); //print(wj.toJson());
@ -106,14 +108,14 @@ void edgeCaseTests(FutureOr<QueryExecutor> Function() createExecutor,
test('many to many', () async { test('many to many', () async {
var fooQuery = FooQuery()..values.bar = 'baz'; var fooQuery = FooQuery()..values.bar = 'baz';
var fooBar = var fooBar =
await fooQuery.insert(executor).then((foo) => foo.value.bar); await fooQuery.insert(executor!).then((foo) => foo.value.bar);
var pivotQuery = FooPivotQuery() var pivotQuery = FooPivotQuery()
..values.weirdJoinId = weirdJoin!.id ..values.weirdJoinId = weirdJoin!.id
..values.fooBar = fooBar; ..values.fooBar = fooBar;
await pivotQuery.insert(executor); await pivotQuery.insert(executor!);
fooQuery = FooQuery()..where!.bar.equals('baz'); fooQuery = FooQuery()..where!.bar.equals('baz');
var fooOpt = await fooQuery.getOne(executor); var fooOpt = await fooQuery.getOne(executor!);
expect(fooOpt.isPresent, true); expect(fooOpt.isPresent, true);
fooOpt.ifPresent((foo) { fooOpt.ifPresent((foo) {
//print(foo.toJson()); //print(foo.toJson());

View file

@ -94,10 +94,9 @@ class CarQuery extends Query<Car, CarQueryWhere> {
createdAt: fields.contains('created_at') ? (row[1] as DateTime?) : null, createdAt: fields.contains('created_at') ? (row[1] as DateTime?) : null,
updatedAt: fields.contains('updated_at') ? (row[2] as DateTime?) : null, updatedAt: fields.contains('updated_at') ? (row[2] as DateTime?) : null,
make: fields.contains('make') ? (row[3] as String?) : null, make: fields.contains('make') ? (row[3] as String?) : null,
description: description: fields.contains('description') ? mapToText(row[4]) : null,
fields.contains('description') ? (row[4] as String?) : null,
familyFriendly: familyFriendly:
fields.contains('family_friendly') ? (row[5] as bool?) : null, fields.contains('family_friendly') ? mapToBool(row[5]) : null,
recalledAt: recalledAt:
fields.contains('recalled_at') ? (row[6] as DateTime?) : null); fields.contains('recalled_at') ? (row[6] as DateTime?) : null);
return Optional.of(model); return Optional.of(model);