diff --git a/CHANGELOG.md b/CHANGELOG.md index 63a0832d..72291482 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,57 @@ +## 1.14.0 + +- New `DBSQLiteAdapter`: an embedded SQLite DB adapter, backed by the + [`sqlite3`][sqlite3_pkg] package. + + ```dart + import 'package:bones_api/bones_api_db_sqlite.dart'; + + var adapter = DBSQLiteAdapter('/var/lib/myapp/db.sqlite', + generateTables: true); + + // Or an in-memory database: + var memoryAdapter = DBSQLiteAdapter(':memory:', generateTables: true); + ``` + + - Registered as `sqlite`, `sqlite3`, `sql.sqlite` and `sql.sqlite3`, so a + config block `db: { sqlite: {...} }` resolves it. + - `fromConfig` accepts `path`/`file`/`database`/`db` for the database file, + and `memory: true` (or the path `:memory:`) for an in-memory database, plus + the usual `generateTables`/`checkTables`/`populate`/`log.sql` keys. + Irrelevant keys (`host`, `port`, `username`, `password`) are accepted and + ignored, so a config can be pointed at SQLite without being rewritten. + - **No server and no native library to install**: the `sqlite3` package + bundles SQLite (3.53.4) through Dart's build hooks. + - Runs the same entity test-suite as the PostgreSQL and MySQL adapters, and + needs no `Docker` container to do it. New `APITestConfigSQLite`, exported by + `package:bones_api/bones_api_test_sqlite.dart`. + + - Notes on the SQLite dialect: + - An auto-assigning ID is declared `INTEGER PRIMARY KEY AUTOINCREMENT`: + SQLite has no `SERIAL`/`AUTO_INCREMENT`, only a column declared exactly + `INTEGER PRIMARY KEY` aliases the `rowid`, and without `AUTOINCREMENT` + SQLite reuses the ID of a deleted row. + - `ENUM` is emulated with a `VARCHAR CHECK (col IN (...))` constraint. + - Since `sqlite3` is a **synchronous** driver, and SQLite allows a single + writer, the adapter uses one native handle shared by every pooled + connection: a second handle blocking on a lock would stall the isolate + holding it, and offers nothing to gain when there is no I/O to overlap. + Nested transactions use `SAVEPOINT`. + +- New `SQLDialect.returningAcceptsTableWildcard` (default `true`, so the + PostgreSQL/MySQL/memory dialects are unchanged). SQLite rejects the + table-qualified wildcard that `DELETE ... RETURNING` emits + (*"RETURNING may not use TABLE.\* wildcards"*) and needs a bare + `RETURNING *`. + +- **Breaking**: the minimum Dart SDK is now **3.10.0** (was 3.7.0), required by + `sqlite3` and its build hooks. + +- Dependencies: + - Added `sqlite3: ^3.5.1` + +[sqlite3_pkg]: https://pub.dev/packages/sqlite3 + ## 1.13.0 - New `EntityPagination.onEvent`: an optional hook notified of what is being diff --git a/README.md b/README.md index bb8cde41..293707e2 100644 --- a/README.md +++ b/README.md @@ -539,15 +539,34 @@ postgres: To use a SQL database with your `EntityRepository` you need a `SQLAdapter`: -- `PostgreSQLAdapter`: a [PostgreSQL][postgres] adapter. -- `MySQLAdapter`: A [MySQL][mysql] adapter. -- `MemorySQLAdapter`: a portable `SQLAdapter` that stores entities in memory. +- `DBPostgreSQLAdapter`: a [PostgreSQL][postgres] adapter. + Import: `package:bones_api/bones_api_db_postgre.dart` +- `DBMySQLAdapter`: a [MySQL][mysql] adapter. + Import: `package:bones_api/bones_api_db_mysql.dart` +- `DBSQLiteAdapter`: an embedded [SQLite][sqlite] adapter, for a database file + or an in-memory database. Needs no server and no native library: the + [`sqlite3`][sqlite3_pkg] package bundles SQLite itself. + Import: `package:bones_api/bones_api_db_sqlite.dart` +- `DBSQLMemoryAdapter`: a portable `SQLAdapter` that stores entities in memory. The `SQLAdapter` is responsible to connect to the database, manage the connection pool and also to adjust the generated SQLs to the correct dialect. +Example of a SQLite configuration: + +```yaml +db: + sqlite: + path: /var/lib/myapp/db.sqlite + generateTables: true +``` + +Use `memory: true` (or `path: ':memory:'`) for an in-memory database. + [postgres]: https://www.postgresql.org/ [mysql]: https://www.mysql.com/ +[sqlite]: https://www.sqlite.org/ +[sqlite3_pkg]: https://pub.dev/packages/sqlite3 ## Bones_UI diff --git a/bin/bones_api.dart b/bin/bones_api.dart index fdfc6df4..b4b693e6 100644 --- a/bin/bones_api.dart +++ b/bin/bones_api.dart @@ -26,13 +26,12 @@ void main(List args) async { await commandInfo.configure(); await commandCreate.configure(); - var commandRunner = - CommandRunner('bones_api', '$cliTitle - CLI Tool') - ..addCommand(MyCommandServe()) - ..addCommand(MyCommandConsole()) - ..addCommand(MyCommandInspect()) - ..addCommand(commandInfo) - ..addCommand(commandCreate); + var commandRunner = CommandRunner('bones_api', '$cliTitle - CLI Tool') + ..addCommand(MyCommandServe()) + ..addCommand(MyCommandConsole()) + ..addCommand(MyCommandInspect()) + ..addCommand(commandInfo) + ..addCommand(commandCreate); commandRunner.argParser.addFlag( 'version', @@ -248,19 +247,17 @@ class MyCommandServe extends CommandSourceFileBase { var val = argResults!['domain']; if (val == null) return {}; - var values = - (val is List ? val : [val]) - .map((e) => e != null ? '$e'.trim() : '') - .where((e) => e.isNotEmpty) - .toList(); + var values = (val is List ? val : [val]) + .map((e) => e != null ? '$e'.trim() : '') + .where((e) => e.isNotEmpty) + .toList(); - var entries = - values.map((e) { - var parts = e.split('='); - var domain = parts[0].trim(); - var path = parts.length > 1 ? parts[1].trim() : ''; - return MapEntry(domain, path); - }).toList(); + var entries = values.map((e) { + var parts = e.split('='); + var domain = parts[0].trim(); + var path = parts.length > 1 ? parts[1].trim() : ''; + return MapEntry(domain, path); + }).toList(); return Map.fromEntries(entries); } @@ -635,7 +632,8 @@ class MyCommandServe extends CommandSourceFileBase { String projectLibraryName, String apiRootClass, ) { - var script = ''' + var script = + ''' import 'package:bones_api/bones_api_server.dart'; import 'package:bones_api/bones_api_dart_spawner.dart'; @@ -852,7 +850,8 @@ class MyCommandConsole extends CommandSourceFileBase { String projectLibraryName, String apiRootClass, ) { - var script = ''' + var script = + ''' import 'dart:async'; import 'dart:convert'; import 'dart:io'; diff --git a/dart_test.yaml b/dart_test.yaml index 4ffe168a..002f3e6e 100644 --- a/dart_test.yaml +++ b/dart_test.yaml @@ -17,6 +17,9 @@ tags: # Tests that uses `MySQL`. mysql: timeout: 180s + # Tests that uses `SQLite` (embedded: no container needed). + sqlite: + timeout: 180s # Slow tests. slow: timeout: 180s diff --git a/example/bones_api_example.dart b/example/bones_api_example.dart index 57ef77fd..1af9702d 100644 --- a/example/bones_api_example.dart +++ b/example/bones_api_example.dart @@ -43,7 +43,8 @@ class MyBTCModule extends APIModule { // The requested path: var path = request.path; - var body = ''' + var body = + '''

404


PATH: $path

diff --git a/lib/bones_api_db_sqlite.dart b/lib/bones_api_db_sqlite.dart new file mode 100644 index 00000000..d0471a39 --- /dev/null +++ b/lib/bones_api_db_sqlite.dart @@ -0,0 +1,4 @@ +/// Bones_API DB Adapter for SQLite. +library; + +export 'src/bones_api_entity_db_sqlite.dart'; diff --git a/lib/bones_api_test_sqlite.dart b/lib/bones_api_test_sqlite.dart new file mode 100644 index 00000000..c7e85eb3 --- /dev/null +++ b/lib/bones_api_test_sqlite.dart @@ -0,0 +1,5 @@ +/// Bones_API Test SQLite Library. +library; + +export 'bones_api_test.dart'; +export 'src/bones_api_test_utils_sqlite.dart'; diff --git a/lib/src/bones_api_authentication.dart b/lib/src/bones_api_authentication.dart index b315e76a..b95cea85 100644 --- a/lib/src/bones_api_authentication.dart +++ b/lib/src/bones_api_authentication.dart @@ -33,10 +33,9 @@ class APICredential { this.refreshToken, String? passwordHash, APIPasswordHashAlgorithm? hashAlgorithm, - }) : password = - passwordHash != null - ? APIPassword(passwordHash, hashAlgorithm: hashAlgorithm) - : null; + }) : password = passwordHash != null + ? APIPassword(passwordHash, hashAlgorithm: hashAlgorithm) + : null; APICredential._( this.username, @@ -201,8 +200,8 @@ abstract class APIPasswordHashAlgorithm { /// Ensures that [passwordOrHash] is hashed with this algorithm. String ensureHashedPassword(String passwordOrHash) => isHashedPassword(passwordOrHash) - ? passwordOrHash - : hashPassword(passwordOrHash); + ? passwordOrHash + : hashPassword(passwordOrHash); @override String toString() { @@ -480,10 +479,9 @@ class APIToken implements Comparable { }) : token = token ?? generateToken(512, variableLength: 32, prefix: 'TK'), issueTime = issueTime ?? DateTime.now(), duration = duration ?? Duration(hours: 3), - refreshToken = - refreshToken == null && withRefreshToken - ? generateToken(640, variableLength: 64, prefix: 'RTK') - : refreshToken; + refreshToken = refreshToken == null && withRefreshToken + ? generateToken(640, variableLength: 64, prefix: 'RTK') + : refreshToken; DateTime get accessTime => _accessTime; diff --git a/lib/src/bones_api_base.dart b/lib/src/bones_api_base.dart index 6771234f..65d8ca7e 100644 --- a/lib/src/bones_api_base.dart +++ b/lib/src/bones_api_base.dart @@ -48,7 +48,7 @@ typedef APILogger = /// Bones API Library class. class BonesAPI { // ignore: constant_identifier_names - static const String VERSION = '1.13.0'; + static const String VERSION = '1.14.0'; static bool _boot = false; @@ -649,12 +649,11 @@ abstract class APIRoot with Initializable, Closable { if (response is Future>) { return response.then( (response) => _callZonedReturn(callZone, request, response), - onError: - (e, s) => _callZonedReturn( - callZone, - request, - _resolveErrorAPIResponse(e, s), - ), + onError: (e, s) => _callZonedReturn( + callZone, + request, + _resolveErrorAPIResponse(e, s), + ), ); } else { return _callZonedReturn(callZone, request, response); @@ -692,10 +691,9 @@ abstract class APIRoot with Initializable, Closable { var original = identical(request.credential, request.originalCredential); if (original) { - var credential = - request.credential = request.credential?.copy( - withUsernameEntity: false, - ); + var credential = request.credential = request.credential?.copy( + withUsernameEntity: false, + ); request.originalCredential = credential; } else { @@ -1018,17 +1016,15 @@ abstract class APIRouteHandler { _entityResolutionRules ??= _entityResolutionRulesIml(); EntityResolutionRules _entityResolutionRulesIml() { - var resolutionRules1 = - rules - .whereType() - .map((e) => e.resolutionRules) - .toList(); - - var resolutionRules2 = - rules - .whereType() - .expand((e) => e.entityResolutionRules) - .toList(); + var resolutionRules1 = rules + .whereType() + .map((e) => e.resolutionRules) + .toList(); + + var resolutionRules2 = rules + .whereType() + .expand((e) => e.entityResolutionRules) + .toList(); if (resolutionRules1.isEmpty && resolutionRules2.isEmpty) { return EntityResolutionRules.innocuous; @@ -1050,17 +1046,15 @@ abstract class APIRouteHandler { _entityAccessRules ??= _entityAccessRulesImpl(); EntityAccessRules _entityAccessRulesImpl() { - var accessRules1 = - rules - .whereType() - .map((e) => e.accessRules) - .toList(); - - var accessRules2 = - rules - .whereType() - .expand((e) => e.entityAccessRules) - .toList(); + var accessRules1 = rules + .whereType() + .map((e) => e.accessRules) + .toList(); + + var accessRules2 = rules + .whereType() + .expand((e) => e.entityAccessRules) + .toList(); if (accessRules1.isEmpty && accessRules2.isEmpty) { return EntityAccessRules.innocuous; @@ -1193,10 +1187,9 @@ class APIRouteInfo { /// Returns the [Uri] of the route. Uri get uri { - var baseUri = - apiRequest != null - ? Uri.tryParse(apiRequest!.origin) ?? Uri.base - : Uri.base; + var baseUri = apiRequest != null + ? Uri.tryParse(apiRequest!.origin) ?? Uri.base + : Uri.base; var module = routeHandler.module; var path = '${module.name}/$name'; @@ -1440,13 +1433,12 @@ abstract class APIMetricSet { Duration? duration, String? description, int? n, - }) => - metrics[name] = APIMetric( - name, - duration: duration, - description: description, - n: n, - ); + }) => metrics[name] = APIMetric( + name, + duration: duration, + description: description, + n: n, + ); /// Returns a metric. APIMetric? getMetric(String name) => metrics[name]; @@ -1650,10 +1642,9 @@ class APIRequest extends APIMetricSet with APIPayload { requestedUri = requestedUri ?? Uri( - host: - requesterSource == APIRequesterSource.local - ? 'localhost' - : null, + host: requesterSource == APIRequesterSource.local + ? 'localhost' + : null, path: path, queryParameters: parameters?.map( (key, value) => MapEntry( @@ -1737,11 +1728,10 @@ class APIRequest extends APIMetricSet with APIPayload { } var methodVal = arguments.parameters.remove('method'); - var method = - methodVal != null - ? parseAPIRequestMethod(methodVal.toString().toLowerCase()) ?? - APIRequestMethod.GET - : APIRequestMethod.GET; + var method = methodVal != null + ? parseAPIRequestMethod(methodVal.toString().toLowerCase()) ?? + APIRequestMethod.GET + : APIRequestMethod.GET; var payload = arguments.parameters.remove('payload'); @@ -2295,10 +2285,9 @@ class APIRequest extends APIMetricSet with APIPayload { @override String toString({bool withHeaders = true, bool withPayload = true}) { var headersStr = withHeaders ? ', headers: $headers' : ''; - var payloadStr = - withPayload && hasPayload - ? ', payloadLength: $payloadLength, payloadMimeType: $payloadMimeType' - : ''; + var payloadStr = withPayload && hasPayload + ? ', payloadLength: $payloadLength, payloadMimeType: $payloadMimeType' + : ''; return 'APIRequest#$id{ method: ${method.name}, ' 'path: $path, ' @@ -2874,10 +2863,9 @@ class APIResponse extends APIMetricSet with APIPayload { }) { return APIResponse( status ?? this.status, - payload: - nullPayload - ? null - : (payload ?? (payloadDynamic == null ? this.payload : null)), + payload: nullPayload + ? null + : (payload ?? (payloadDynamic == null ? this.payload : null)), payloadDynamic: nullPayload ? null : payloadDynamic, payloadMimeType: mimeType ?? payloadMimeType, payloadFileExtension: payloadFileExtension, @@ -3333,8 +3321,9 @@ class APIResponse extends APIMetricSet with APIPayload { headers["Access-Control-Allow-Methods"] = allowMethods?.join(',') ?? 'GET,HEAD,PUT,POST,PATCH,DELETE,OPTIONS'; - headers["Access-Control-Allow-Credentials"] = - allowCredentials ? 'true' : 'false'; + headers["Access-Control-Allow-Credentials"] = allowCredentials + ? 'true' + : 'false'; if (localhost) { headers["Access-Control-Allow-Headers"] = diff --git a/lib/src/bones_api_condition.dart b/lib/src/bones_api_condition.dart index 4bd57d71..02cb8fa1 100644 --- a/lib/src/bones_api_condition.dart +++ b/lib/src/bones_api_condition.dart @@ -44,10 +44,9 @@ abstract class ConditionElement { List.unmodifiable([]); void _setParameters(Iterable parameters) { - _parameters = - parameters.isEmpty - ? _emptyParameters - : List.unmodifiable(parameters); + _parameters = parameters.isEmpty + ? _emptyParameters + : List.unmodifiable(parameters); } } @@ -114,17 +113,15 @@ class ConditionParameter extends ConditionElement { } if (parameters is Map) { - namedParameters ??= - parameters is Map - ? parameters - : parameters.map( - (key, value) => MapEntry('$key', value), - ); + namedParameters ??= parameters is Map + ? parameters + : parameters.map( + (key, value) => MapEntry('$key', value), + ); } else if (parameters is Iterable) { - positionalParameters ??= - parameters is List - ? parameters - : parameters.toList(growable: false); + positionalParameters ??= parameters is List + ? parameters + : parameters.toList(growable: false); } } @@ -192,52 +189,48 @@ class ConditionParameter extends ConditionElement { namedParameters: namedParameters, ); - var otherValue = - value is ConditionParameter - ? value.getValue( - parameters: parameters, - positionalParameters: positionalParameters, - namedParameters: namedParameters, - ) - : value; + var otherValue = value is ConditionParameter + ? value.getValue( + parameters: parameters, + positionalParameters: positionalParameters, + namedParameters: namedParameters, + ) + : value; if (myValue is Iterable) { if (otherValue is Iterable) { var equals = isEqualsIterableDeep( myValue, otherValue, - valueEquality: - (a, b) => EntityHandler.equalsValuesBasic( - a, - b, - entityHandler: entityHandler, - ), + valueEquality: (a, b) => EntityHandler.equalsValuesBasic( + a, + b, + entityHandler: entityHandler, + ), ); return equals; } else { - var contains = - myValue - .where( - (v) => EntityHandler.equalsValuesBasic( - v, - otherValue, - entityHandler: entityHandler, - ), - ) - .isNotEmpty; + var contains = myValue + .where( + (v) => EntityHandler.equalsValuesBasic( + v, + otherValue, + entityHandler: entityHandler, + ), + ) + .isNotEmpty; return contains; } } else if (otherValue is Iterable) { - var contains = - otherValue - .where( - (v) => EntityHandler.equalsValuesBasic( - v, - myValue, - entityHandler: entityHandler, - ), - ) - .isNotEmpty; + var contains = otherValue + .where( + (v) => EntityHandler.equalsValuesBasic( + v, + myValue, + entityHandler: entityHandler, + ), + ) + .isNotEmpty; return contains; } @@ -261,14 +254,13 @@ class ConditionParameter extends ConditionElement { namedParameters: namedParameters, ); - var otherValue = - value is ConditionParameter - ? value.getValue( - parameters: parameters, - positionalParameters: positionalParameters, - namedParameters: namedParameters, - ) - : value; + var otherValue = value is ConditionParameter + ? value.getValue( + parameters: parameters, + positionalParameters: positionalParameters, + namedParameters: namedParameters, + ) + : value; return EntityHandler.graterThanValue( myValue, @@ -290,14 +282,13 @@ class ConditionParameter extends ConditionElement { namedParameters: namedParameters, ); - var otherValue = - value is ConditionParameter - ? value.getValue( - parameters: parameters, - positionalParameters: positionalParameters, - namedParameters: namedParameters, - ) - : value; + var otherValue = value is ConditionParameter + ? value.getValue( + parameters: parameters, + positionalParameters: positionalParameters, + namedParameters: namedParameters, + ) + : value; return EntityHandler.graterThanOrEqualValue( myValue, @@ -319,14 +310,13 @@ class ConditionParameter extends ConditionElement { namedParameters: namedParameters, ); - var otherValue = - value is ConditionParameter - ? value.getValue( - parameters: parameters, - positionalParameters: positionalParameters, - namedParameters: namedParameters, - ) - : value; + var otherValue = value is ConditionParameter + ? value.getValue( + parameters: parameters, + positionalParameters: positionalParameters, + namedParameters: namedParameters, + ) + : value; return EntityHandler.lessThanValue( myValue, @@ -348,14 +338,13 @@ class ConditionParameter extends ConditionElement { namedParameters: namedParameters, ); - var otherValue = - value is ConditionParameter - ? value.getValue( - parameters: parameters, - positionalParameters: positionalParameters, - namedParameters: namedParameters, - ) - : value; + var otherValue = value is ConditionParameter + ? value.getValue( + parameters: parameters, + positionalParameters: positionalParameters, + namedParameters: namedParameters, + ) + : value; return EntityHandler.lessThanOrEqualValue( myValue, @@ -380,14 +369,13 @@ class ConditionParameter extends ConditionElement { if (myValue is List) { for (var v1 in myValue) { for (var v2 in values) { - var otherValue = - v2 is ConditionParameter - ? v2.getValue( - parameters: parameters, - positionalParameters: positionalParameters, - namedParameters: namedParameters, - ) - : v2; + var otherValue = v2 is ConditionParameter + ? v2.getValue( + parameters: parameters, + positionalParameters: positionalParameters, + namedParameters: namedParameters, + ) + : v2; var match = EntityHandler.equalsValuesBasic( v1, @@ -401,14 +389,13 @@ class ConditionParameter extends ConditionElement { return false; } else { for (var v2 in values) { - var otherValue = - v2 is ConditionParameter - ? v2.getValue( - parameters: parameters, - positionalParameters: positionalParameters, - namedParameters: namedParameters, - ) - : v2; + var otherValue = v2 is ConditionParameter + ? v2.getValue( + parameters: parameters, + positionalParameters: positionalParameters, + namedParameters: namedParameters, + ) + : v2; var match = EntityHandler.equalsValuesBasic( myValue, @@ -792,8 +779,9 @@ abstract class GroupCondition extends Condition { final List conditions; GroupCondition(Iterable conditions) - : conditions = - conditions is List ? conditions : conditions.toList(), + : conditions = conditions is List + ? conditions + : conditions.toList(), super._() { for (var c in conditions) { c._parent = this; @@ -1139,8 +1127,9 @@ class ConditionIdIN extends Condition { var idsValues = this.idsValues; - var params = - idsValues.map((e) => e is ConditionParameter ? e : null).nonNulls; + var params = idsValues + .map((e) => e is ConditionParameter ? e : null) + .nonNulls; _setParameters(params); _markResolved(); @@ -1193,11 +1182,11 @@ class ConditionIdIN extends Condition { return idsValues.any((p) { return p is ConditionParameter ? p.matches( - id, - parameters: parameters, - positionalParameters: positionalParameters, - namedParameters: namedParameters, - ) + id, + parameters: parameters, + positionalParameters: positionalParameters, + namedParameters: namedParameters, + ) : id == p; }); } @@ -1219,31 +1208,30 @@ abstract class ConditionKey { throw ArgumentError('Empty key'); } - var list = - _keyRegExp.allMatches(keys).map((m) { - var s1 = m.group(1); - if (s1 != null) { - return ConditionKeyField(s1); - } + var list = _keyRegExp.allMatches(keys).map((m) { + var s1 = m.group(1); + if (s1 != null) { + return ConditionKeyField(s1); + } - var s2 = m.group(2); - if (s2 != null) { - return ConditionKeyField(s2); - } + var s2 = m.group(2); + if (s2 != null) { + return ConditionKeyField(s2); + } - var field = m.group(3); - if (field != null) { - return ConditionKeyField(field); - } + var field = m.group(3); + if (field != null) { + return ConditionKeyField(field); + } - var index = m.group(4); - if (index != null) { - var idx = int.parse(index); - return ConditionKeyIndex(idx); - } + var index = m.group(4); + if (index != null) { + var idx = int.parse(index); + return ConditionKeyIndex(idx); + } - throw StateError('Invalid match: $m'); - }).toList(); + throw StateError('Invalid match: $m'); + }).toList(); return list; } @@ -1391,20 +1379,19 @@ abstract class KeyCondition extends Condition { if (value is Map) { keyValue = value[keyName]; } else if (value is Iterable) { - keyValue = - value.map((e) { - if (e is Map) { - return e[keyName]; - } else { - var handler = entityHandler?.getEntityHandler(obj: e); - if (handler != null) { - var v = handler.getField(e, keyName); - return v; - } else { - return e; - } - } - }).toList(); + keyValue = value.map((e) { + if (e is Map) { + return e[keyName]; + } else { + var handler = entityHandler?.getEntityHandler(obj: e); + if (handler != null) { + var v = handler.getField(e, keyName); + return v; + } else { + return e; + } + } + }).toList(); } else { throw StateError( "Can't access key[$keyName] for type: ${(value as Object?).runtimeTypeNameUnsafe}", diff --git a/lib/src/bones_api_condition_encoder.dart b/lib/src/bones_api_condition_encoder.dart index 25f42c03..81f9984d 100644 --- a/lib/src/bones_api_condition_encoder.dart +++ b/lib/src/bones_api_condition_encoder.dart @@ -175,14 +175,14 @@ class TableRelationshipReference { String? targetRelationshipFieldIndex, }) : sourceRelationshipFieldIndex = sourceRelationshipFieldIndex != null && - sourceRelationshipFieldIndex.isNotEmpty - ? sourceRelationshipFieldIndex - : null, + sourceRelationshipFieldIndex.isNotEmpty + ? sourceRelationshipFieldIndex + : null, targetRelationshipFieldIndex = targetRelationshipFieldIndex != null && - targetRelationshipFieldIndex.isNotEmpty - ? targetRelationshipFieldIndex - : null; + targetRelationshipFieldIndex.isNotEmpty + ? targetRelationshipFieldIndex + : null; /// Returns a copy as [TableRelationshipReference], /// with entity types [sourceFieldEntityType] and [targetFieldEntityType]. @@ -364,12 +364,11 @@ class TableScheme with FieldsFromMap { if (fields != null) { var fieldsSimple = fields.map(fieldToSimpleKey).toList(); - fieldsNames = - fieldsNames - .mapIndexed((i, e) => MapEntry(e, _fieldsNamesSimple[i])) - .where((e) => fieldsSimple.contains(e.value)) - .map((e) => e.key) - .toList(); + fieldsNames = fieldsNames + .mapIndexed((i, e) => MapEntry(e, _fieldsNamesSimple[i])) + .where((e) => fieldsSimple.contains(e.value)) + .map((e) => e.key) + .toList(); } return getFieldsValuesFromMap( @@ -458,14 +457,13 @@ class TableScheme with FieldsFromMap { sourceTable, ); - rels = - l - .where( - (rel) => - StringUtils.toLowerCaseSimpleCached(rel.sourceTable) == - sourceTableSimple, - ) - .toList(); + rels = l + .where( + (rel) => + StringUtils.toLowerCaseSimpleCached(rel.sourceTable) == + sourceTableSimple, + ) + .toList(); if (rels.length == 1) return rels.first; } @@ -476,14 +474,13 @@ class TableScheme with FieldsFromMap { var sourceFieldSimpleUnderscored = StringUtils.toLowerCaseSimpleUnderscored(sourceField); - var rels1 = - rels - .where( - (rel) => StringUtils.toLowerCaseSimpleUnderscored( - rel.relationshipTable, - ).contains(sourceFieldSimpleUnderscored), - ) - .toList(); + var rels1 = rels + .where( + (rel) => StringUtils.toLowerCaseSimpleUnderscored( + rel.relationshipTable, + ).contains(sourceFieldSimpleUnderscored), + ) + .toList(); if (rels1.length == 1) return rels1.first; @@ -492,25 +489,21 @@ class TableScheme with FieldsFromMap { ); if (rels1.isEmpty) { - rels1 = - rels - .where( - (rel) => StringUtils.toLowerCaseSimpleCached( - rel.relationshipTable, - ).contains(sourceFieldSimple), - ) - .toList(); + rels1 = rels + .where( + (rel) => StringUtils.toLowerCaseSimpleCached( + rel.relationshipTable, + ).contains(sourceFieldSimple), + ) + .toList(); } var relsNames = StringUtils.trimEqualitiesMap( rels1.map((e) => e.relationshipTable).toList(), delimiter: '_', normalizer: (s) => StringUtils.toLowerCaseSimpleUnderscored(s), - validator: - (s) => - !StringUtils.toLowerCaseSimpleCached( - s, - ).contains(sourceFieldSimple), + validator: (s) => + !StringUtils.toLowerCaseSimpleCached(s).contains(sourceFieldSimple), ); var relsNamesSimple = relsNames.map( @@ -518,19 +511,17 @@ class TableScheme with FieldsFromMap { MapEntry(key, StringUtils.toLowerCaseSimpleCached(value)), ); - var rels2 = - rels1.where((rel) { - var f = relsNamesSimple[rel.relationshipTable]!; - return f.contains(sourceFieldSimple); - }).toList(); + var rels2 = rels1.where((rel) { + var f = relsNamesSimple[rel.relationshipTable]!; + return f.contains(sourceFieldSimple); + }).toList(); if (rels2.length == 1) return rels2.first; - var rels3 = - rels1.where((rel) { - var f = relsNamesSimple[rel.relationshipTable]!; - return f == sourceFieldSimple; - }).toList(); + var rels3 = rels1.where((rel) { + var f = relsNamesSimple[rel.relationshipTable]!; + return f == sourceFieldSimple; + }).toList(); if (rels3.length == 1) return rels3.first; @@ -615,8 +606,9 @@ class TableScheme with FieldsFromMap { @override String toString() { - var constraintNotPrimary = - constraints.where((e) => e is! TablePrimaryKeyConstraint).toList(); + var constraintNotPrimary = constraints + .where((e) => e is! TablePrimaryKeyConstraint) + .toList(); return 'TableScheme{name: $name, ' 'idFieldName: $idFieldName, ' @@ -1139,10 +1131,9 @@ class EncodingContext { } for (var i = 0; i < 1000; ++i) { - var k = - i == 0 - ? '$parameterPrefix$suggestedKey' - : '$parameterPrefix$suggestedKey$i'; + var k = i == 0 + ? '$parameterPrefix$suggestedKey' + : '$parameterPrefix$suggestedKey$i'; if (!namedParameters.containsKey(k)) { encodingParameters[k] = value; @@ -1390,26 +1381,22 @@ abstract class ConditionEncoder { valueType, true, ).resolveMapped((values) { - var list = - values is List - ? values - : (values is Iterable - ? values.toList(growable: false) - : [values]); - - var placeHolders = - list.mapIndexed((i, v) { - var k = parameterPlaceholderIndexKey(valueKey, i); - context.parametersPlaceholders[k] ??= v; - var placeholder = parameterPlaceholder(k); - return EncodingPlaceholderIndex( - valueKey, - valueType, - placeholder, - i, - encodeEncodingPlaceholderIndex, - ); - }).toList(); + var list = values is List + ? values + : (values is Iterable ? values.toList(growable: false) : [values]); + + var placeHolders = list.mapIndexed((i, v) { + var k = parameterPlaceholderIndexKey(valueKey, i); + context.parametersPlaceholders[k] ??= v; + var placeholder = parameterPlaceholder(k); + return EncodingPlaceholderIndex( + valueKey, + valueType, + placeholder, + i, + encodeEncodingPlaceholderIndex, + ); + }).toList(); return EncodingValueList( valueKey, @@ -1480,11 +1467,10 @@ abstract class ConditionEncoder { if (value == null) { return []; } else if (value is Iterable) { - var list = - value - .map((v) => _resolveValueToTypeCompatibleImpl(v, valueType)) - .toList(growable: false) - .resolveAll(); + var list = value + .map((v) => _resolveValueToTypeCompatibleImpl(v, valueType)) + .toList(growable: false) + .resolveAll(); return list; } else { var list = _resolveValueToTypeCompatibleImpl( diff --git a/lib/src/bones_api_condition_parser.dart b/lib/src/bones_api_condition_parser.dart index b1e6325e..fcbb76a7 100644 --- a/lib/src/bones_api_condition_parser.dart +++ b/lib/src/bones_api_condition_parser.dart @@ -63,10 +63,9 @@ abstract class JsonGrammarLexer extends GrammarDefinition { Parser pair2() => keyToken2() & char(':').trim() & ref0(jsonValue2); - Parser keyToken() => - stringPrimitive().map((l) { - return (l[1] as List).join(); - }).trim(); + Parser keyToken() => stringPrimitive().map((l) { + return (l[1] as List).join(); + }).trim(); Parser keyToken2() => (wordPrimitive().flatten() | stringPrimitive2()).map((l) { @@ -130,11 +129,10 @@ abstract class JsonGrammarLexer extends GrammarDefinition { Parser stringPrimitiveExtra() => char("'") & ref0(characterPrimitive2).star() & char("'"); - Parser stringPrimitive2() => - >[ - stringPrimitive(), - stringPrimitiveExtra(), - ].toChoiceParser(); + Parser stringPrimitive2() => >[ + stringPrimitive(), + stringPrimitiveExtra(), + ].toChoiceParser(); } /// JSON grammar definition. @@ -207,19 +205,17 @@ class JsonGrammarDefinition extends JsonGrammarLexer { // ignore: prefer_void_to_null Parser nullValue() => nullToken().map((each) => null); - Parser stringValue() => - stringPrimitive().map((l) { - var l_1 = l[1]; - if (l_1 is! List) return l_1?.toString() ?? ''; - return l_1.join(); - }).trim(); + Parser stringValue() => stringPrimitive().map((l) { + var l_1 = l[1]; + if (l_1 is! List) return l_1?.toString() ?? ''; + return l_1.join(); + }).trim(); - Parser stringValue2() => - stringPrimitive2().map((l) { - var l_1 = l[1]; - if (l_1 is! List) return l_1?.toString() ?? ''; - return l_1.join(); - }).trim(); + Parser stringValue2() => stringPrimitive2().map((l) { + var l_1 = l[1]; + if (l_1 is! List) return l_1?.toString() ?? ''; + return l_1.join(); + }).trim(); Parser numberValue() => numberToken().map((each) => num.parse(each)); } @@ -241,12 +237,13 @@ class ConditionGrammarDefinition extends JsonGrammarDefinition { return v as Condition; }); - Parser conditionParenthesis() => (char('(').trim() & - (conditionGroup() | conditionMatch()) & - char(')').trim()) - .map((l) { - return l[1] as Condition; - }); + Parser conditionParenthesis() => + (char('(').trim() & + (conditionGroup() | conditionMatch()) & + char(')').trim()) + .map((l) { + return l[1] as Condition; + }); Parser conditionGroup() => (conditionGroupAND() | conditionGroupOR()).map((v) => v as Condition); @@ -256,10 +253,9 @@ class ConditionGrammarDefinition extends JsonGrammarDefinition { (string('&&').trim() & ref0(conditionParenthesisOrValue)).plus()) .map((l) { var v1 = l[0]; - var v2 = - (l[1] as List) - .expand((e) => e is List ? e : [e]) - .whereType(); + var v2 = (l[1] as List) + .expand((e) => e is List ? e : [e]) + .whereType(); return GroupConditionAND([v1, ...v2]); }); @@ -268,10 +264,9 @@ class ConditionGrammarDefinition extends JsonGrammarDefinition { (string('||').trim() & ref0(conditionParenthesisOrValue)).plus()) .map((l) { var v1 = l[0]; - var v2 = - (l[1] as List) - .expand((e) => e is List ? e : [e]) - .whereType(); + var v2 = (l[1] as List) + .expand((e) => e is List ? e : [e]) + .whereType(); return GroupConditionOR([v1, ...v2]); }); @@ -318,13 +313,14 @@ class ConditionGrammarDefinition extends JsonGrammarDefinition { Parser conditionKey() => (conditionKeyIndex() | conditionKeyField()).cast(); - Parser conditionKeyIndex() => (char('[').trim() & - (char('-').optional() & digit().plus()).flatten() & - char(']').trim()) - .map((l) { - var idx = int.parse(l[1]); - return ConditionKeyIndex(idx); - }); + Parser conditionKeyIndex() => + (char('[').trim() & + (char('-').optional() & digit().plus()).flatten() & + char(']').trim()) + .map((l) { + var idx = int.parse(l[1]); + return ConditionKeyIndex(idx); + }); Parser conditionKeyField() => (wordPrimitive().flatten() | stringPrimitive2()).trim().map((l) { @@ -360,8 +356,8 @@ class ConditionGrammarDefinition extends JsonGrammarDefinition { Parser conditionParameter() => (conditionParameterIndex() | - conditionParameterKey() | - conditionParameterPositional()); + conditionParameterKey() | + conditionParameterPositional()); Parser conditionParameterIndex() => (char('?') & (char('#') & digit().star().flatten())).trim().map((l) { diff --git a/lib/src/bones_api_condition_sql.dart b/lib/src/bones_api_condition_sql.dart index 1c4414ed..bc1e7d95 100644 --- a/lib/src/bones_api_condition_sql.dart +++ b/lib/src/bones_api_condition_sql.dart @@ -340,10 +340,9 @@ class ConditionSQLEncoder extends ConditionEncoder { var fieldName = key0.name; var tableFieldName = tableScheme.resolveTableFieldName(fieldName); - var tableFieldType = - tableFieldName != null - ? tableScheme.fieldsTypes[tableFieldName] - : null; + var tableFieldType = tableFieldName != null + ? tableScheme.fieldsTypes[tableFieldName] + : null; if (tableFieldType != null) { return MapEntry(tableFieldType, '$q$tableAlias$q.$q$tableFieldName$q'); @@ -629,25 +628,24 @@ class ConditionSQLEncoder extends ConditionEncoder { ); } else if (value is List && value.whereType().isNotEmpty) { - var parametersValues = - value.map((v) { - if (v is ConditionParameter) { - return conditionParameterToParameterValue( - v, - context, - fieldKey, - fieldType, - valueAsList: valueAsList, - ); - } else { - return _valueToParameterValueImpl( - v, - fieldType, - key: fieldKey, - valueAsList: valueAsList, - ); - } - }).resolveAll(); + var parametersValues = value.map((v) { + if (v is ConditionParameter) { + return conditionParameterToParameterValue( + v, + context, + fieldKey, + fieldType, + valueAsList: valueAsList, + ); + } else { + return _valueToParameterValueImpl( + v, + fieldType, + key: fieldKey, + valueAsList: valueAsList, + ); + } + }).resolveAll(); return parametersValues.resolveMapped( (values) => EncodingValueList( @@ -741,10 +739,9 @@ class ConditionSQLEncoder extends ConditionEncoder { }) { key ??= '?'; - var list = - value is Iterable - ? value.map((v) => valueToSQLPlain(v, type, key: key)).toList() - : [valueToSQLPlain(value, type, key: key)]; + var list = value is Iterable + ? value.map((v) => valueToSQLPlain(v, type, key: key)).toList() + : [valueToSQLPlain(value, type, key: key)]; return EncodingValueList(key, type, list, encodeEncodingValueList); } diff --git a/lib/src/bones_api_config.dart b/lib/src/bones_api_config.dart index 033b8ca9..0fa2e164 100644 --- a/lib/src/bones_api_config.dart +++ b/lib/src/bones_api_config.dart @@ -113,8 +113,9 @@ class APIConfig { var valueResolved = value.replaceAllMapped(_regexpValueVariable, (m) { var k = m[1]; - var v = - k != null ? _getVariable(k, !equalsIgnoreAsciiCase(k, key)) : m[0]; + var v = k != null + ? _getVariable(k, !equalsIgnoreAsciiCase(k, key)) + : m[0]; return v ?? ''; }); @@ -542,18 +543,17 @@ class APIConfig { ) { var lines = properties.split(_regexpLineBreak); - var l = - lines.expand((l) { - if (_regexpKeyLine.hasMatch(l)) { - var idx = l.indexOf(_regexpKeyDelimiter); - var k = l.substring(0, idx).trim(); - var v = l.substring(idx + 1); - var val = _parseJsonValue(v); - return >[MapEntry(k, val)]; - } else { - return >[]; - } - }).toList(); + var l = lines.expand((l) { + if (_regexpKeyLine.hasMatch(l)) { + var idx = l.indexOf(_regexpKeyDelimiter); + var k = l.substring(0, idx).trim(); + var v = l.substring(idx + 1); + var val = _parseJsonValue(v); + return >[MapEntry(k, val)]; + } else { + return >[]; + } + }).toList(); return l; } diff --git a/lib/src/bones_api_db_module.dart b/lib/src/bones_api_db_module.dart index 758b59e5..daf1622a 100644 --- a/lib/src/bones_api_db_module.dart +++ b/lib/src/bones_api_db_module.dart @@ -84,10 +84,9 @@ class APIDBModule extends APIModule { var table = pathParams.removeAt(0); - var eager = - pathParams.any((p) => equalsIgnoreAsciiCase(p, 'eager')) - ? true - : null; + var eager = pathParams.any((p) => equalsIgnoreAsciiCase(p, 'eager')) + ? true + : null; var json = _containsKey(pathParams, 'json'); return select(table, request, eager: eager, json: json); @@ -195,8 +194,8 @@ class APIDBModule extends APIModule { List? _entityRepositoryProviders; Future> get entityRepositoryProviders async => - _entityRepositoryProviders ??= - await apiRoot.loadEntityRepositoryProviders(); + _entityRepositoryProviders ??= await apiRoot + .loadEntityRepositoryProviders(); Future> tables({bool json = false}) async { if (onlyOnDevelopment && !development) { @@ -207,21 +206,19 @@ class APIDBModule extends APIModule { var allRepositories = (await entityRepositoryProviders).allRepositories(); - var allRepositoriesEntries = - allRepositories.entries - .sorted((a, b) => a.value.name.compareTo(b.value.name)) - .toList(); + var allRepositoriesEntries = allRepositories.entries + .sorted((a, b) => a.value.name.compareTo(b.value.name)) + .toList(); if (json) { - var map = - allRepositoriesEntries - .map((e) { - var type = e.key; - var repo = e.value; - return MapEntry('$type', repo.name); - }) - .sorted((a, b) => a.key.compareTo(b.key)) - .toMapFromEntries(); + var map = allRepositoriesEntries + .map((e) { + var type = e.key; + var repo = e.value; + return MapEntry('$type', repo.name); + }) + .sorted((a, b) => a.key.compareTo(b.key)) + .toMapFromEntries(); return APIResponse.ok(map, mimeType: 'json'); } @@ -482,12 +479,9 @@ class APIDBModule extends APIModule { var v2 = _resolveValue(v, fieldType); var entityType = fieldType.entityType ?? fieldType.listEntityType?.type; - var fieldEntityRepository = - entityType != null - ? entityRepositoryProviders.getEntityRepositoryByType( - entityType, - ) - : null; + var fieldEntityRepository = entityType != null + ? entityRepositoryProviders.getEntityRepositoryByType(entityType) + : null; if (fieldEntityRepository != null) { var repoName = fieldEntityRepository.name; @@ -838,15 +832,13 @@ class APIDBModule extends APIModule { String typeStr; if (type.isEntityReferenceType) { - typeStr = - type.isValidGenericType - ? type.genericType.toString() - : type.toString(withT: false); + typeStr = type.isValidGenericType + ? type.genericType.toString() + : type.toString(withT: false); } else if (type.isEntityReferenceListType) { - typeStr = - type.isValidGenericType - ? type.genericType.toString() - : type.toString(withT: false); + typeStr = type.isValidGenericType + ? type.genericType.toString() + : type.toString(withT: false); } else if (type.isListEntity) { typeStr = type.genericType.toString(); } else { @@ -969,17 +961,15 @@ class APIDBModule extends APIModule { var allRepositories = (await entityRepositoryProviders).allRepositories(); - var dump = - await allRepositories.values - .map((repo) => MapEntry(repo.name, repo.selectAll())) - .toMapFromEntries() - .resolveAllValues(); + var dump = await allRepositories.values + .map((repo) => MapEntry(repo.name, repo.selectAll())) + .toMapFromEntries() + .resolveAllValues(); - var dumpJson = - dump.entries - .map((e) => MapEntry(e.key, _entitiesToJsonMap(e.value))) - .where((e) => e.value.isNotEmpty) - .toMapFromEntries(); + var dumpJson = dump.entries + .map((e) => MapEntry(e.key, _entitiesToJsonMap(e.value))) + .where((e) => e.value.isNotEmpty) + .toMapFromEntries(); if (zip) { var apiName = apiRoot.name.toLowerCase().trim().replaceAll( @@ -1049,8 +1039,9 @@ class APIDBModule extends APIModule { if (classReflection != null) { var entityHandler = classReflection.entityHandler; - var id = - o is List ? entityHandler.resolveIDs(o) : entityHandler.resolveID(o); + var id = o is List + ? entityHandler.resolveIDs(o) + : entityHandler.resolveID(o); return id; } diff --git a/lib/src/bones_api_entity.dart b/lib/src/bones_api_entity.dart index 43a73afc..0a857dfb 100644 --- a/lib/src/bones_api_entity.dart +++ b/lib/src/bones_api_entity.dart @@ -458,8 +458,10 @@ abstract class EntityHandler with FieldsFromMap, EntityRulesResolver { var id = resolveIDFromMap(value); return id != null ? [id] : []; } else if (value is Iterable) { - var ids = - value.map((e) => resolveID(e)).whereNotNullResolved().toList(); + var ids = value + .map((e) => resolveID(e)) + .whereNotNullResolved() + .toList(); return ids; } else if (value is EntityReference) { var id = value.id; @@ -497,21 +499,20 @@ abstract class EntityHandler with FieldsFromMap, EntityRulesResolver { []; return ids as List; } else if (idType.isString) { - var ids = - value - .toString() - .trim() - .split(RegExp(r'\s+')) - .map((s) => s.trim()) - .map( - (s) => - (s.startsWith('"') && s.endsWith('"')) || - (s.startsWith("'") && s.endsWith("'")) - ? s.substring(1, s.length - 1) - : s, - ) - .where((s) => s.isNotEmpty) - .toList(); + var ids = value + .toString() + .trim() + .split(RegExp(r'\s+')) + .map((s) => s.trim()) + .map( + (s) => + (s.startsWith('"') && s.endsWith('"')) || + (s.startsWith("'") && s.endsWith("'")) + ? s.substring(1, s.length - 1) + : s, + ) + .where((s) => s.isNotEmpty) + .toList(); return ids as List; } @@ -567,9 +568,11 @@ abstract class EntityHandler with FieldsFromMap, EntityRulesResolver { Type? _idType; - Type idType([O? o]) => - _idType ??= - getFieldType(o, idFieldName(o), resolveFiledName: false)!.type; + Type idType([O? o]) => _idType ??= getFieldType( + o, + idFieldName(o), + resolveFiledName: false, + )!.type; String idFieldName([O? o]); @@ -579,11 +582,8 @@ abstract class EntityHandler with FieldsFromMap, EntityRulesResolver { Map? _fieldsWithTypeList; - Map fieldsWithTypeList([O? o]) => - _fieldsWithTypeList ??= fieldsWithType( - (_, fieldType) => fieldType.isList, - o, - ); + Map fieldsWithTypeList([O? o]) => _fieldsWithTypeList ??= + fieldsWithType((_, fieldType) => fieldType.isList, o); Map? _fieldsWithTypeEntity; @@ -1041,15 +1041,14 @@ abstract class EntityHandler with FieldsFromMap, EntityRulesResolver { entityCache: entityCache, ); } else { - resolved = - valEntityHandler != null - ? valEntityHandler.createFromMap( - value, - entityProvider: entityProvider, - entityCache: entityCache, - resolutionRules: resolutionRulesResolved, - ) - : value; + resolved = valEntityHandler != null + ? valEntityHandler.createFromMap( + value, + entityProvider: entityProvider, + entityCache: entityCache, + resolutionRules: resolutionRulesResolved, + ) + : value; } return resolved as FutureOr; @@ -1158,28 +1157,27 @@ abstract class EntityHandler with FieldsFromMap, EntityRulesResolver { var totalEntitiesToResolve = 0; var totalResolvedEntities = 0; - value = - value.map((e) { - if (e.isEntityIDType) { - totalEntitiesToResolve++; + value = value.map((e) { + if (e.isEntityIDType) { + totalEntitiesToResolve++; - var id = valEntityHandler?.resolveID(e) ?? e; + var id = valEntityHandler?.resolveID(e) ?? e; - var entity = entityCache.getCachedEntityByID( - id, - type: elementType.type, - ); + var entity = entityCache.getCachedEntityByID( + id, + type: elementType.type, + ); - if (entity != null) { - totalResolvedEntities++; - return entity; - } else { - return e; - } - } else { - return e; - } - }).toList(); + if (entity != null) { + totalResolvedEntities++; + return entity; + } else { + return e; + } + } else { + return e; + } + }).toList(); if (totalResolvedEntities == totalEntitiesToResolve && value.length == totalResolvedEntities) { @@ -1194,14 +1192,13 @@ abstract class EntityHandler with FieldsFromMap, EntityRulesResolver { if (valEntityHandler != null) { var listFutures = TypeParser.parseList( value, - elementParser: - (e) => valEntityHandler.resolveValueByType( - elementType, - e, - entityProvider: entityProvider, - entityCache: entityCache, - resolutionRules: resolutionRulesResolved, - ), + elementParser: (e) => valEntityHandler.resolveValueByType( + elementType, + e, + entityProvider: entityProvider, + entityCache: entityCache, + resolutionRules: resolutionRulesResolved, + ), ); if (listFutures == null) return null; @@ -1212,14 +1209,13 @@ abstract class EntityHandler with FieldsFromMap, EntityRulesResolver { } else { var listFutures = TypeParser.parseList( value, - elementParser: - (e) => resolveValueByType( - elementType, - e, - entityProvider: entityProvider, - entityCache: entityCache, - resolutionRules: resolutionRulesResolved, - ), + elementParser: (e) => resolveValueByType( + elementType, + e, + entityProvider: entityProvider, + entityCache: entityCache, + resolutionRules: resolutionRulesResolved, + ), ); if (listFutures == null) return null; @@ -1583,14 +1579,12 @@ abstract class EntityHandler with FieldsFromMap, EntityRulesResolver { final reflectionFactory = ReflectionFactory(); - var mapEnumFields = - getFieldsTypes(o).entries - .where( - (e) => - reflectionFactory.getRegisterEnumReflection(e.value.type) != - null, - ) - .toMapFromEntries(); + var mapEnumFields = getFieldsTypes(o).entries + .where( + (e) => + reflectionFactory.getRegisterEnumReflection(e.value.type) != null, + ) + .toMapFromEntries(); return _fieldsEnumTypes = Map.unmodifiable(mapEnumFields); } @@ -1603,23 +1597,22 @@ abstract class EntityHandler with FieldsFromMap, EntityRulesResolver { var enumFields = getFieldsEnumTypes(o); - var mapEntityFields = - getFieldsTypes().entries - .map((e) { - var field = e.key; - var typeInfo = e.value; + var mapEntityFields = getFieldsTypes().entries + .map((e) { + var field = e.key; + var typeInfo = e.value; - if (enumFields.containsKey(field)) return null; + if (enumFields.containsKey(field)) return null; - if (typeInfo.isListEntityOrReference) return null; + if (typeInfo.isListEntityOrReference) return null; - var entityType = typeInfo.entityType; - if (entityType == null) return null; + var entityType = typeInfo.entityType; + if (entityType == null) return null; - return MapEntry(field, typeInfo); - }) - .nonNulls - .toMapFromEntries(); + return MapEntry(field, typeInfo); + }) + .nonNulls + .toMapFromEntries(); return _fieldsEntityTypes = Map.unmodifiable( mapEntityFields, @@ -1634,23 +1627,22 @@ abstract class EntityHandler with FieldsFromMap, EntityRulesResolver { var enumFields = getFieldsEnumTypes(o); - var mapListEntityFields = - getFieldsTypes().entries - .map((e) { - var field = e.key; - var typeInfo = e.value; + var mapListEntityFields = getFieldsTypes().entries + .map((e) { + var field = e.key; + var typeInfo = e.value; - if (enumFields.containsKey(field)) return null; + if (enumFields.containsKey(field)) return null; - if (!typeInfo.isListEntityOrReference) return null; + if (!typeInfo.isListEntityOrReference) return null; - var entityType = typeInfo.entityType; - if (entityType == null) return null; + var entityType = typeInfo.entityType; + if (entityType == null) return null; - return MapEntry(field, typeInfo); - }) - .nonNulls - .toMapFromEntries(); + return MapEntry(field, typeInfo); + }) + .nonNulls + .toMapFromEntries(); return _fieldsListEntityTypes = Map.unmodifiable( mapListEntityFields, @@ -1662,11 +1654,10 @@ abstract class EntityHandler with FieldsFromMap, EntityRulesResolver { Map>? getAllFieldsEntityAnnotations([O? o]) { var fieldsNames = this.fieldsNames(o); - var entries = - fieldsNames.map((f) { - var annotations = getFieldEntityAnnotations(o, f); - return annotations == null ? null : MapEntry(f, annotations); - }).nonNulls; + var entries = fieldsNames.map((f) { + var annotations = getFieldEntityAnnotations(o, f); + return annotations == null ? null : MapEntry(f, annotations); + }).nonNulls; var map = Map>.fromEntries(entries); @@ -2088,8 +2079,9 @@ abstract class EntityHandler with FieldsFromMap, EntityRulesResolver { return DynamicInt.fromInt(value.millisecondsSinceEpoch); } - DynamicNumber? n = - value == null ? def : (Decimal.from(value.toString().trim()) ?? def); + DynamicNumber? n = value == null + ? def + : (Decimal.from(value.toString().trim()) ?? def); if (n == null) { throw ArgumentError( @@ -2187,25 +2179,23 @@ abstract class EntityHandler with FieldsFromMap, EntityRulesResolver { var reflectionFactory = ReflectionFactory(); - EntityHandler? entityHandler1 = - value1.isPrimitiveValue - ? null - : reflectionFactory - .getRegisterClassReflection(value1.runtimeType) - ?.entityHandler ?? - EntityHandlerProvider.globalProvider.getEntityHandler( - obj: value1, - ); - - EntityHandler? entityHandler2 = - value2.isPrimitiveValue - ? null - : reflectionFactory - .getRegisterClassReflection(value2.runtimeType) - ?.entityHandler ?? - EntityHandlerProvider.globalProvider.getEntityHandler( - obj: value2, - ); + EntityHandler? entityHandler1 = value1.isPrimitiveValue + ? null + : reflectionFactory + .getRegisterClassReflection(value1.runtimeType) + ?.entityHandler ?? + EntityHandlerProvider.globalProvider.getEntityHandler( + obj: value1, + ); + + EntityHandler? entityHandler2 = value2.isPrimitiveValue + ? null + : reflectionFactory + .getRegisterClassReflection(value2.runtimeType) + ?.entityHandler ?? + EntityHandlerProvider.globalProvider.getEntityHandler( + obj: value2, + ); if (entityHandler1 != null) { var id1 = entityHandler1.getID(value1); @@ -2283,14 +2273,12 @@ abstract class EntityHandler with FieldsFromMap, EntityRulesResolver { static bool? equalsValuesTime(Object value1, Object value2) { if (value1 is Time || value2 is Time) { - var t1 = - value1 is Time - ? value1.totalMilliseconds - : TypeParser.parseInt(value1); - var t2 = - value2 is Time - ? value2.totalMilliseconds - : TypeParser.parseInt(value2); + var t1 = value1 is Time + ? value1.totalMilliseconds + : TypeParser.parseInt(value1); + var t2 = value2 is Time + ? value2.totalMilliseconds + : TypeParser.parseInt(value2); return t1 == t2; } @@ -2336,8 +2324,8 @@ abstract class EntityHandler with FieldsFromMap, EntityRulesResolver { return isEqualsDeep( value1, value2, - valueEquality: - (a, b) => equalsValuesBasic(a, b, entityHandler: entityHandler), + valueEquality: (a, b) => + equalsValuesBasic(a, b, entityHandler: entityHandler), ); } @@ -2461,25 +2449,26 @@ abstract class EntityHandler with FieldsFromMap, EntityRulesResolver { var msgConstructors = ''; if (constructors != null && constructors.isNotEmpty) { - var allParameters = - constructors.entries.expand((c) => c.value.keys).toSet(); - - var missingParameters = - allParameters.where((p) => !parameters.contains(p)).toList(); - - var list = - constructors.entries.map((e) { - var name = e.key; - var args = e.value.entries - .map((e) { - var argName = e.key; - var argType = e.value.toString(withT: false); - return '$argType $argName'; - }) - .join(', '); - - return '$type${name.isNotEmpty ? '.$name' : ''}($args)'; - }).toList(); + var allParameters = constructors.entries + .expand((c) => c.value.keys) + .toSet(); + + var missingParameters = allParameters + .where((p) => !parameters.contains(p)) + .toList(); + + var list = constructors.entries.map((e) { + var name = e.key; + var args = e.value.entries + .map((e) { + var argName = e.key; + var argType = e.value.toString(withT: false); + return '$argType $argName'; + }) + .join(', '); + + return '$type${name.isNotEmpty ? '.$name' : ''}($args)'; + }).toList(); msgConstructors = '\n Missing parameters: $missingParameters' @@ -2753,8 +2742,9 @@ class GenericEntityHandler extends EntityHandler { if (o != null && _idFieldsName == null) { _idFieldsName = o.idFieldName; - final fieldsNames = - _fieldsNames ??= List.unmodifiable(o.fieldsNames); + final fieldsNames = _fieldsNames ??= List.unmodifiable( + o.fieldsNames, + ); _fieldsTypes ??= Map.unmodifiable( Map.fromEntries( @@ -2924,9 +2914,8 @@ class ClassReflectionEntityHandler extends EntityHandler { @override String get typeName => reflection.className; - ClassReflection get reflection => - _reflection ??= - ReflectionFactory().getRegisterClassReflection(classType)!; + ClassReflection get reflection => _reflection ??= ReflectionFactory() + .getRegisterClassReflection(classType)!; ClassReflection reflectionWithObject([O? o]) => o == null ? reflection : reflection.withObject(o); @@ -3204,13 +3193,9 @@ class ClassReflectionEntityHandler extends EntityHandler { return _constructors ??= Map>.unmodifiable( reflectionWithObject(o).allConstructors().map((c) { var name = c.name; - var args = - c.allParameters - .map( - (p) => - MapEntry(p.jsonName, p.type.typeInfo), - ) - .toMapFromEntries(); + var args = c.allParameters + .map((p) => MapEntry(p.jsonName, p.type.typeInfo)) + .toMapFromEntries(); return MapEntry(name, Map.unmodifiable(args)); }).toMapFromEntries(), ); @@ -3229,9 +3214,8 @@ class ClassReflectionEntityHandler extends EntityHandler { return reflection.createInstanceFromMap( fields, fieldNameResolver: Json.defaultFieldNameResolver, - fieldValueResolver: - (f, v, t) => - Json.defaultFieldValueResolver(f, v, t, jsonDecoder!, provider), + fieldValueResolver: (f, v, t) => + Json.defaultFieldValueResolver(f, v, t, jsonDecoder!, provider), ); } @@ -3659,18 +3643,17 @@ abstract class EntitySource extends EntityAccessor { limit: limit, query: query, onEvent: onEvent, - pageLoader: - (page, limit) => selectByQuery( - query, - parameters: parameters, - positionalParameters: positionalParameters, - namedParameters: namedParameters, - transaction: transaction, - limit: limit, - page: page, - orderByID: orderByID ?? true, - orderDirection: orderDirection, - ).resolveMapped((os) => os.toList()), + pageLoader: (page, limit) => selectByQuery( + query, + parameters: parameters, + positionalParameters: positionalParameters, + namedParameters: namedParameters, + transaction: transaction, + limit: limit, + page: page, + orderByID: orderByID ?? true, + orderDirection: orderDirection, + ).resolveMapped((os) => os.toList()), ); /// {@macro bones_api.paginate} @@ -3688,18 +3671,17 @@ abstract class EntitySource extends EntityAccessor { limit: limit, query: '$matcher', onEvent: onEvent, - pageLoader: - (page, limit) => select( - matcher, - parameters: parameters, - positionalParameters: positionalParameters, - namedParameters: namedParameters, - transaction: transaction, - limit: limit, - page: page, - orderByID: orderByID ?? true, - orderDirection: orderDirection, - ).resolveMapped((os) => os.toList()), + pageLoader: (page, limit) => select( + matcher, + parameters: parameters, + positionalParameters: positionalParameters, + namedParameters: namedParameters, + transaction: transaction, + limit: limit, + page: page, + orderByID: orderByID ?? true, + orderDirection: orderDirection, + ).resolveMapped((os) => os.toList()), ); /// {@macro bones_api.paginate} @@ -3713,14 +3695,13 @@ abstract class EntitySource extends EntityAccessor { limit: limit, query: 'ALL', onEvent: onEvent, - pageLoader: - (page, limit) => selectAll( - transaction: transaction, - limit: limit, - page: page, - orderByID: orderByID ?? true, - orderDirection: orderDirection, - ).resolveMapped((os) => os.toList()), + pageLoader: (page, limit) => selectAll( + transaction: transaction, + limit: limit, + page: page, + orderByID: orderByID ?? true, + orderDirection: orderDirection, + ).resolveMapped((os) => os.toList()), ); FutureOr> selectRelationship( @@ -4296,12 +4277,9 @@ class EntityRepositoryProvider if (name != null) { var nameSimplified = EntityAccessor.simplifiedName(name); - entityRepository = - _entityRepositories.values - .where( - (e) => e.name == name || e.nameSimplified == nameSimplified, - ) - .firstOrNull; + entityRepository = _entityRepositories.values + .where((e) => e.name == name || e.nameSimplified == nameSimplified) + .firstOrNull; if (entityRepository != null && !entityRepository.isClosed) { return entityRepository as EntityRepository; @@ -4704,11 +4682,10 @@ extension _IterableEntityRepositoryProviderExtension var notClosed = _whereNotClosed(this, length, removeClosedProviders); - var entityRepositories = - notClosed - .map((e) => e.getEntityRepositoryByType(type)) - .nonNulls - .toList(); + var entityRepositories = notClosed + .map((e) => e.getEntityRepositoryByType(type)) + .nonNulls + .toList(); return _resolveEntityRepository( entityRepositories, @@ -4741,23 +4718,20 @@ extension _IterableEntityRepositoryProviderExtension } if (entityRepositoryProvider != null) { - var sameProvider = - entityRepositories - .where( - (r) => - r.provider == entityRepositoryProvider && - (entityHandler == null || - r.entityHandler == entityHandler), - ) - .toList(); + var sameProvider = entityRepositories + .where( + (r) => + r.provider == entityRepositoryProvider && + (entityHandler == null || r.entityHandler == entityHandler), + ) + .toList(); if (sameProvider.length == 1) { return sameProvider.first; } } else if (entityHandler != null) { - var sameProvider = - entityRepositories - .where((r) => r.entityHandler == entityHandler) - .toList(); + var sameProvider = entityRepositories + .where((r) => r.entityHandler == entityHandler) + .toList(); if (sameProvider.length == 1) { return sameProvider.first; } @@ -4843,34 +4817,35 @@ extension EntityRepositoryProviderExtension on EntityRepositoryProvider { growable: false, ); - var entriesRepositoriesOrdered = - entries.entries - .map((e) { - var typeName = e.key; - var entities = e.value; - var entityRepository = getEntityRepository(name: typeName); - return MapEntry(typeName, (entities, entityRepository)); - }) - .sorted((a, b) { - var rep1 = a.value.$2; - var rep2 = b.value.$2; - - var idx1 = - rep1 != null ? allRepositoriesBuildOrder.indexOf(rep1) : -1; - var idx2 = - rep2 != null ? allRepositoriesBuildOrder.indexOf(rep2) : -1; - - if (idx1 < 0) { - idx1 = 9999999; - } - - if (idx2 < 0) { - idx2 = 9999999; - } - - return idx1.compareTo(idx2); - }) - .toMapFromEntries(); + var entriesRepositoriesOrdered = entries.entries + .map((e) { + var typeName = e.key; + var entities = e.value; + var entityRepository = getEntityRepository(name: typeName); + return MapEntry(typeName, (entities, entityRepository)); + }) + .sorted((a, b) { + var rep1 = a.value.$2; + var rep2 = b.value.$2; + + var idx1 = rep1 != null + ? allRepositoriesBuildOrder.indexOf(rep1) + : -1; + var idx2 = rep2 != null + ? allRepositoriesBuildOrder.indexOf(rep2) + : -1; + + if (idx1 < 0) { + idx1 = 9999999; + } + + if (idx2 < 0) { + idx2 = 9999999; + } + + return idx1.compareTo(idx2); + }) + .toMapFromEntries(); _log.info('Repositories build order:'); for (var e in entriesRepositoriesOrdered.entries) { @@ -5034,17 +5009,14 @@ extension EntityRepositoryProviderExtension on EntityRepositoryProvider { var key = k.toString(); var values = v is Iterable ? v : [v]; - var entities = - values - .map( - (e) => (e as Map).map( - (key, value) => MapEntry( - key.toString(), - value as dynamic, - ), - ), - ) - .toList(); + var entities = values + .map( + (e) => (e as Map).map( + (key, value) => + MapEntry(key.toString(), value as dynamic), + ), + ) + .toList(); return MapEntry(key, entities); }); @@ -5333,25 +5305,25 @@ abstract class EntityRepository extends EntityAccessor EntityResolutionRules? resolutionRules, ) => Transaction.executeBlock((transaction) { if (entitiesJson.length <= 100) { - var osAsync = - entitiesJson - .map( - (e) => createFromMap( - e, - entityCache: transaction, - entityProvider: provider, - resolutionRules: resolutionRules, - ), - ) - .resolveAll(); + var osAsync = entitiesJson + .map( + (e) => createFromMap( + e, + entityCache: transaction, + entityProvider: provider, + resolutionRules: resolutionRules, + ), + ) + .resolveAll(); return osAsync.resolveMapped((os) { return storeAll(os, transaction: transaction).resolveWithValue(os); }); } - var entitiesJsonBlocks = - entitiesJson.splitBeforeIndexed((i, e) => i % 50 == 0).toList(); + var entitiesJsonBlocks = entitiesJson + .splitBeforeIndexed((i, e) => i % 50 == 0) + .toList(); final entitiesJsonLength = entitiesJson.length; int processCount = 0; @@ -5367,29 +5339,26 @@ abstract class EntityRepository extends EntityAccessor final elapsedTime = DateTime.now().difference(initTime); final estimatedTime = Duration( - milliseconds: - count == 0 - ? 0 - : ((elapsedTime.inMilliseconds / count) * - entitiesJsonLength) - .toInt(), + milliseconds: count == 0 + ? 0 + : ((elapsedTime.inMilliseconds / count) * entitiesJsonLength) + .toInt(), ); _log.info( 'Populating `$name`... $count / $entitiesJsonLength (${progress.toStringAsFixed(2)}%) -- ${elapsedTime.toHumanReadable()} / ${estimatedTime.toHumanReadable()}', ); - var osAsync = - entitiesBlock - .map( - (e) => createFromMap( - e, - entityCache: transaction, - entityProvider: provider, - resolutionRules: resolutionRules, - ), - ) - .resolveAll(); + var osAsync = entitiesBlock + .map( + (e) => createFromMap( + e, + entityCache: transaction, + entityProvider: provider, + resolutionRules: resolutionRules, + ), + ) + .resolveAll(); return osAsync.resolveMapped((os) { return storeAll(os, transaction: transaction).resolveWithValue(os); @@ -5835,19 +5804,18 @@ abstract class EntityRepository extends EntityAccessor limit: limit, onEvent: onEvent, query: query, - pageLoader: - (page, limit) => selectByQuery( - query, - parameters: parameters, - positionalParameters: positionalParameters, - namedParameters: namedParameters, - transaction: transaction, - limit: limit, - page: page, - orderByID: orderByID ?? true, - orderDirection: orderDirection, - resolutionRules: resolutionRules, - ).resolveMapped((os) => os.toList()), + pageLoader: (page, limit) => selectByQuery( + query, + parameters: parameters, + positionalParameters: positionalParameters, + namedParameters: namedParameters, + transaction: transaction, + limit: limit, + page: page, + orderByID: orderByID ?? true, + orderDirection: orderDirection, + resolutionRules: resolutionRules, + ).resolveMapped((os) => os.toList()), ); /// {@macro bones_api.paginate} @@ -5869,19 +5837,18 @@ abstract class EntityRepository extends EntityAccessor limit: limit, onEvent: onEvent, query: '$matcher', - pageLoader: - (page, limit) => select( - matcher, - parameters: parameters, - positionalParameters: positionalParameters, - namedParameters: namedParameters, - transaction: transaction, - limit: limit, - page: page, - orderByID: orderByID ?? true, - orderDirection: orderDirection, - resolutionRules: resolutionRules, - ).resolveMapped((os) => os.toList()), + pageLoader: (page, limit) => select( + matcher, + parameters: parameters, + positionalParameters: positionalParameters, + namedParameters: namedParameters, + transaction: transaction, + limit: limit, + page: page, + orderByID: orderByID ?? true, + orderDirection: orderDirection, + resolutionRules: resolutionRules, + ).resolveMapped((os) => os.toList()), ); /// {@macro bones_api.paginate} @@ -5899,15 +5866,14 @@ abstract class EntityRepository extends EntityAccessor limit: limit, onEvent: onEvent, query: 'ALL', - pageLoader: - (page, limit) => selectAll( - transaction: transaction, - limit: limit, - page: page, - orderByID: orderByID ?? true, - orderDirection: orderDirection, - resolutionRules: resolutionRules, - ).resolveMapped((os) => os.toList()), + pageLoader: (page, limit) => selectAll( + transaction: transaction, + limit: limit, + page: page, + orderByID: orderByID ?? true, + orderDirection: orderDirection, + resolutionRules: resolutionRules, + ).resolveMapped((os) => os.toList()), ); @override @@ -6464,8 +6430,8 @@ class Transaction extends JsonEntityCacheSimple implements EntityProvider { Future _waitAllExecutedImpl() async { while (_executedOperations.length < _operations.length) { - var completer = - _waitingExecutedOperation ??= Completer(); + var completer = _waitingExecutedOperation ??= + Completer(); await completer.future; } return true; @@ -6511,8 +6477,8 @@ class Transaction extends JsonEntityCacheSimple implements EntityProvider { return false; } - var completer = - _waitingExecutedOperation ??= Completer(); + var completer = _waitingExecutedOperation ??= + Completer(); var executedOp = await completer.future.timeout( remainingTime, @@ -6828,12 +6794,11 @@ class Transaction extends JsonEntityCacheSimple implements EntityProvider { if (onError != null) { return zone.asyncTry( block, - onError: - (e, s) => _executeCommit( - zone, - e, - s, - ).resolveMapped((val) => onError(this, e, s)), + onError: (e, s) => _executeCommit( + zone, + e, + s, + ).resolveMapped((val) => onError(this, e, s)), onFinally: () => _executeCommit(zone), ); } else { @@ -6952,16 +6917,15 @@ class Transaction extends JsonEntityCacheSimple implements EntityProvider { }) { final previousError = _error; - final errorResolved = - errorResolver != null - ? errorResolver( - error, - stackTrace, - debugInfo != null ? [operation, debugInfo] : operation, - previousError, - ) ?? - error - : error; + final errorResolved = errorResolver != null + ? errorResolver( + error, + stackTrace, + debugInfo != null ? [operation, debugInfo] : operation, + previousError, + ) ?? + error + : error; _errorsTransactions[errorResolved] = this; @@ -7289,13 +7253,14 @@ class TransactionOperationSubTransaction extends TransactionOperation { 'TransactionOperationSubTransaction$_executedStatus[#$id@#${transaction.id}:subTransaction@$repositoryName]->$durationMsInfo'; @override - String toString({Set? processedObjects}) => - RecursiveToString.recursiveToString( - processedObjects, - this, - () => - 'TransactionOperationSubTransaction$_executedStatus[#$id@#${transaction.id}:subTransaction@$repositoryName]->${subTransaction.toString(compact: true)}$durationMsInfo', - )!; + String toString({ + Set? processedObjects, + }) => RecursiveToString.recursiveToString( + processedObjects, + this, + () => + 'TransactionOperationSubTransaction$_executedStatus[#$id@#${transaction.id}:subTransaction@$repositoryName]->${subTransaction.toString(compact: true)}$durationMsInfo', + )!; } class TransactionOperationSelect extends TransactionOperation { @@ -7320,13 +7285,14 @@ class TransactionOperationSelect extends TransactionOperation { 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:select@$repositoryName]$durationMsInfo'; @override - String toString({Set? processedObjects}) => - RecursiveToString.recursiveToString( - processedObjects, - this, - () => - 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:select@$repositoryName]{matcher: $matcher$_commandToString}$durationMsInfo', - )!; + String toString({ + Set? processedObjects, + }) => RecursiveToString.recursiveToString( + processedObjects, + this, + () => + 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:select@$repositoryName]{matcher: $matcher$_commandToString}$durationMsInfo', + )!; } class TransactionOperationCount extends TransactionOperation { @@ -7345,13 +7311,14 @@ class TransactionOperationCount extends TransactionOperation { 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:count@$repositoryName]$durationMsInfo'; @override - String toString({Set? processedObjects}) => - RecursiveToString.recursiveToString( - processedObjects, - this, - () => - 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:count@$repositoryName]{matcher: $matcher$_commandToString}$durationMsInfo', - )!; + String toString({ + Set? processedObjects, + }) => RecursiveToString.recursiveToString( + processedObjects, + this, + () => + 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:count@$repositoryName]{matcher: $matcher$_commandToString}$durationMsInfo', + )!; } abstract class TransactionOperationWithEntity extends TransactionOperation { @@ -7402,13 +7369,14 @@ class TransactionOperationStore extends TransactionOperationSaveEntity { 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:store@$repositoryName]$durationMsInfo'; @override - String toString({Set? processedObjects}) => - RecursiveToString.recursiveToString( - processedObjects, - this, - () => - 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:store@$repositoryName]{entity: $entity$_commandToString}$durationMsInfo', - )!; + String toString({ + Set? processedObjects, + }) => RecursiveToString.recursiveToString( + processedObjects, + this, + () => + 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:store@$repositoryName]{entity: $entity$_commandToString}$durationMsInfo', + )!; } class TransactionOperationUpdate extends TransactionOperationSaveEntity { @@ -7432,13 +7400,14 @@ class TransactionOperationUpdate extends TransactionOperationSaveEntity { 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:update@$repositoryName]$durationMsInfo'; @override - String toString({Set? processedObjects}) => - RecursiveToString.recursiveToString( - processedObjects, - this, - () => - 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:update@$repositoryName]{entity: $entity$_commandToString}$durationMsInfo', - )!; + String toString({ + Set? processedObjects, + }) => RecursiveToString.recursiveToString( + processedObjects, + this, + () => + 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:update@$repositoryName]{entity: $entity$_commandToString}$durationMsInfo', + )!; } class TransactionOperationStoreRelationship @@ -7465,13 +7434,14 @@ class TransactionOperationStoreRelationship 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:storeRelationship@$repositoryName]$durationMsInfo'; @override - String toString({Set? processedObjects}) => - RecursiveToString.recursiveToString( - processedObjects, - this, - () => - 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:storeRelationship@$repositoryName]{entity: $entity, other: $others$_commandToString}$durationMsInfo', - )!; + String toString({ + Set? processedObjects, + }) => RecursiveToString.recursiveToString( + processedObjects, + this, + () => + 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:storeRelationship@$repositoryName]{entity: $entity, other: $others$_commandToString}$durationMsInfo', + )!; } class TransactionOperationConstrainRelationship @@ -7498,13 +7468,14 @@ class TransactionOperationConstrainRelationship 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:constrainRelationship@$repositoryName]$durationMsInfo'; @override - String toString({Set? processedObjects}) => - RecursiveToString.recursiveToString( - processedObjects, - this, - () => - 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:constrainRelationship@$repositoryName]{entity: $entity, other: $others$_commandToString}$durationMsInfo', - )!; + String toString({ + Set? processedObjects, + }) => RecursiveToString.recursiveToString( + processedObjects, + this, + () => + 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:constrainRelationship@$repositoryName]{entity: $entity, other: $others$_commandToString}$durationMsInfo', + )!; } class TransactionOperationSelectRelationship @@ -7528,13 +7499,14 @@ class TransactionOperationSelectRelationship 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:selectRelationship@$repositoryName]$durationMsInfo'; @override - String toString({Set? processedObjects}) => - RecursiveToString.recursiveToString( - processedObjects, - this, - () => - 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:selectRelationship@$repositoryName]{entity: $entity$_commandToString}$durationMsInfo', - )!; + String toString({ + Set? processedObjects, + }) => RecursiveToString.recursiveToString( + processedObjects, + this, + () => + 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:selectRelationship@$repositoryName]{entity: $entity$_commandToString}$durationMsInfo', + )!; } class TransactionOperationSelectRelationships extends TransactionOperation { @@ -7560,13 +7532,14 @@ class TransactionOperationSelectRelationships extends TransactionOperation { 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:selectRelationships@$repositoryName->$valueRepositoryName]$durationMsInfo'; @override - String toString({Set? processedObjects}) => - RecursiveToString.recursiveToString( - processedObjects, - this, - () => - 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:selectRelationships@$repositoryName->$valueRepositoryName]{entities: $entities$_commandToString}$durationMsInfo', - )!; + String toString({ + Set? processedObjects, + }) => RecursiveToString.recursiveToString( + processedObjects, + this, + () => + 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:selectRelationships@$repositoryName->$valueRepositoryName]{entities: $entities$_commandToString}$durationMsInfo', + )!; } class TransactionOperationDelete extends TransactionOperation { @@ -7591,13 +7564,14 @@ class TransactionOperationDelete extends TransactionOperation { 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:delete@$repositoryName]$durationMsInfo'; @override - String toString({Set? processedObjects}) => - RecursiveToString.recursiveToString( - processedObjects, - this, - () => - 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:delete@$repositoryName]{matcher: $matcher$_commandToString}$durationMsInfo', - )!; + String toString({ + Set? processedObjects, + }) => RecursiveToString.recursiveToString( + processedObjects, + this, + () => + 'TransactionOperation$_executedStatus[#$id@#${transaction.id}:delete@$repositoryName]{matcher: $matcher$_commandToString}$durationMsInfo', + )!; } enum TransactionOperationType { @@ -7726,8 +7700,8 @@ abstract class IterableEntityRepository final fieldsEntity = entityHandler.fieldsWithTypeEntityOrReference(); final fieldsEntityRef = entityHandler.fieldsWithEntityReference(); - final fieldsListEntity = - entityHandler.fieldsWithTypeListEntityOrReference(); + final fieldsListEntity = entityHandler + .fieldsWithTypeListEntityOrReference(); final fieldsListEntityRef = entityHandler.fieldsWithEntityReferenceList(); if (fieldsEntity.isEmpty && fieldsListEntity.isEmpty) return false; @@ -7791,12 +7765,11 @@ abstract class IterableEntityRepository }) { checkNotClosed(); - var osIDs = - iterable().map((o) { - var oId = getID(o, entityHandler: entityHandler); - var matches = ids.contains(oId); - return matches ? oId as I : null; - }).nonNulls; + var osIDs = iterable().map((o) { + var oId = getID(o, entityHandler: entityHandler); + var matches = ids.contains(oId); + return matches ? oId as I : null; + }).nonNulls; return osIDs; } @@ -7959,10 +7932,9 @@ abstract class IterableEntityRepository o, ); - var uniques = - fieldsEntityAnnotations?.entries - .where((e) => e.value.hasUnique) - .toList(); + var uniques = fieldsEntityAnnotations?.entries + .where((e) => e.value.hasUnique) + .toList(); if (uniques == null || uniques.isEmpty) return; for (var e in uniques) { @@ -8087,26 +8059,24 @@ abstract class IterableEntityRepository TypeInfo? fieldType, Transaction? transaction, }) { - oIds ??= - os! - .map((o) => getID(o, entityHandler: entityHandler)! as Object) - .toList(); + oIds ??= os! + .map((o) => getID(o, entityHandler: entityHandler)! as Object) + .toList(); if (oIds.isEmpty) { return >{}; } - var entries = - oIds.map((oId) { - var objs = selectRelationship( - null, - field, - oId: oId, - fieldType: fieldType, - transaction: transaction, - ); - return MapEntry(oId, objs); - }).toList(); + var entries = oIds.map((oId) { + var objs = selectRelationship( + null, + field, + oId: oId, + fieldType: fieldType, + transaction: transaction, + ); + return MapEntry(oId, objs); + }).toList(); var results = Map>>.fromEntries( entries, @@ -8182,8 +8152,11 @@ abstract class IterableEntityRepository var value = entityHandler.getField(o, fieldName); if (value == null) return null; - var fieldType = - entityHandler.getFieldType(o, fieldName, resolveFiledName: false)!; + var fieldType = entityHandler.getFieldType( + o, + fieldName, + resolveFiledName: false, + )!; if (!EntityHandler.isValidEntityType(fieldType.type)) { return null; @@ -8197,14 +8170,13 @@ abstract class IterableEntityRepository ); if (elementRepository == null) return null; - var futures = - value.map((e) { - return elementRepository.ensureStored( - e, - transaction: transaction, - operation: operation, - ); - }).toList(); + var futures = value.map((e) { + return elementRepository.ensureStored( + e, + transaction: transaction, + operation: operation, + ); + }).toList(); return futures.resolveAll(); } else { @@ -8326,18 +8298,17 @@ abstract class IterableEntityRepository int? offset, bool? orderByID, OrderDirection? orderDirection, - }) => - applySelectOrderAndPagination( - itr, - (o) => getID(o, entityHandler: entityHandler), - limit: limit, - offset: offset, - orderByID: orderByID, - orderDirection: orderDirection, - // Preserves the pre-existing behavior of this repository, - // where a `limit` of 0 means "no limit": - zeroLimitIsUnlimited: true, - ).toList(); + }) => applySelectOrderAndPagination( + itr, + (o) => getID(o, entityHandler: entityHandler), + limit: limit, + offset: offset, + orderByID: orderByID, + orderDirection: orderDirection, + // Preserves the pre-existing behavior of this repository, + // where a `limit` of 0 means "no limit": + zeroLimitIsUnlimited: true, + ).toList(); @override Map information({bool extended = false}) { diff --git a/lib/src/bones_api_entity_annotation.dart b/lib/src/bones_api_entity_annotation.dart index 89bdfbb2..26897cd3 100644 --- a/lib/src/bones_api_entity_annotation.dart +++ b/lib/src/bones_api_entity_annotation.dart @@ -346,8 +346,9 @@ class EntityFieldInvalid extends Error implements RecursiveToString { entityStr = '($entityStr)'; } - var fieldStr = - fieldName != null && fieldName!.isNotEmpty ? '($fieldName)' : ''; + var fieldStr = fieldName != null && fieldName!.isNotEmpty + ? '($fieldName)' + : ''; var msg = 'reason: $reason'; @@ -367,8 +368,9 @@ class EntityFieldInvalid extends Error implements RecursiveToString { entityStr = '($entityStr)'; } - var fieldStr = - fieldName != null && fieldName!.isNotEmpty ? '($fieldName)' : ''; + var fieldStr = fieldName != null && fieldName!.isNotEmpty + ? '($fieldName)' + : ''; var operationStr = ''; if (operation != null) { @@ -381,10 +383,9 @@ class EntityFieldInvalid extends Error implements RecursiveToString { operationStr = '\n -- Operation>>\n$s'; } - var parentStr = - parentError != null - ? '\n -- Parent ERROR>> [${parentError.runtimeTypeNameUnsafe}] $parentError' - : ''; + var parentStr = parentError != null + ? '\n -- Parent ERROR>> [${parentError.runtimeTypeNameUnsafe}] $parentError' + : ''; return 'Invalid entity$entityStr field$fieldStr> $message$operationStr$parentStr'; } diff --git a/lib/src/bones_api_entity_db.dart b/lib/src/bones_api_entity_db.dart index 0b7beaa4..99387753 100644 --- a/lib/src/bones_api_entity_db.dart +++ b/lib/src/bones_api_entity_db.dart @@ -1200,10 +1200,9 @@ class DBAdapterRegister> { DBAdapterRegister({this.superRegister}); /// Creates a child register. - DBAdapterRegister createRegister< - C2 extends Object, - A2 extends DBAdapter - >() => DBAdapterRegister(superRegister: this); + DBAdapterRegister + createRegister>() => + DBAdapterRegister(superRegister: this); final Map> _registeredAdaptersByName = {}; final Map> _registeredAdaptersByType = {}; @@ -1786,15 +1785,14 @@ class DBEntityRepository extends EntityRepository var ids = matcher.idsValues.whereType().toList(); // `existIDs` has no ordering/pagination hook, so it is applied here: return existIDs(ids, transaction: transaction).resolveMapped( - (existing) => - applySelectOrderAndPagination( - existing, - (id) => id, - limit: limit, - offset: offset, - orderByID: orderByID, - orderDirection: orderDirection, - ).toList(), + (existing) => applySelectOrderAndPagination( + existing, + (id) => id, + limit: limit, + offset: offset, + orderByID: orderByID, + orderDirection: orderDirection, + ).toList(), ); } @@ -2057,8 +2055,9 @@ class DBEntityRepository extends EntityRepository if (results is! Iterable>) { entries = results.nonNulls.toList(); } else { - entries = - results is List> ? results : results.toList(); + entries = results is List> + ? results + : results.toList(); } var fieldsListEntity = _fieldsListEntity; @@ -2206,10 +2205,9 @@ class DBEntityRepository extends EntityRepository resolutionRules: resolutionRulesResolved, ) .resolveMapped( - (entities) => - idsUniques - .mapIndexed((i, id) => MapEntry(id, entities[i])) - .toList(), + (entities) => idsUniques + .mapIndexed((i, id) => MapEntry(id, entities[i])) + .toList(), ); return MapEntry(tableColumn, entities); @@ -2265,9 +2263,8 @@ class DBEntityRepository extends EntityRepository int? _fieldsEntityNoRefLengthCache; - int get _fieldsEntityNoRefLength => - _fieldsEntityNoRefLengthCache ??= - _fieldsEntity.length - _fieldsEntityRef.length; + int get _fieldsEntityNoRefLength => _fieldsEntityNoRefLengthCache ??= + _fieldsEntity.length - _fieldsEntityRef.length; Map get _fieldsListEntity => entityHandler.fieldsWithTypeListEntityOrReference(); @@ -2277,21 +2274,19 @@ class DBEntityRepository extends EntityRepository int? _fieldsListEntityNoRefLengthCache; - int get _fieldsListEntityNoRefLength => - _fieldsListEntityNoRefLengthCache ??= - _fieldsListEntity.length - _fieldsListEntityRef.length; + int get _fieldsListEntityNoRefLength => _fieldsListEntityNoRefLengthCache ??= + _fieldsListEntity.length - _fieldsListEntityRef.length; Map>? _fieldsEntityRepositories; Map> _fieldsEntityRepositoriesAll() => - _fieldsEntityRepositories ??= - _fieldsEntity.entries - .map((e) { - var repo = _resolveEntityRepository(e.value); - return repo != null ? MapEntry(e.key, repo) : null; - }) - .nonNulls - .toMapFromEntries(); + _fieldsEntityRepositories ??= _fieldsEntity.entries + .map((e) { + var repo = _resolveEntityRepository(e.value); + return repo != null ? MapEntry(e.key, repo) : null; + }) + .nonNulls + .toMapFromEntries(); Map> _resolveFieldsEntityRepositories( EntityResolutionRulesResolved resolutionRulesResolved, @@ -2397,8 +2392,10 @@ class DBEntityRepository extends EntityRepository return resolveRelationshipEntities(relationshipsEntities); } - var allTargetIds = - relationships.values.expand((e) => e).toSet().toList(); + var allTargetIds = relationships.values + .expand((e) => e) + .toSet() + .toList(); // ignore: discarded_futures var targetsAsync = targetEntityRepository.selectByIDs( @@ -2416,11 +2413,15 @@ class DBEntityRepository extends EntityRepository ); var relationshipsEntities = relationships.map((id, targetIds) { - var targetEntities = - targetIds.map((id) => allTargetsById[id]).nonNulls.toList(); + var targetEntities = targetIds + .map((id) => allTargetsById[id]) + .nonNulls + .toList(); - var targetEntitiesCast = - targetEntityHandler.castList(targetEntities, targetType)!; + var targetEntitiesCast = targetEntityHandler.castList( + targetEntities, + targetType, + )!; return MapEntry(id, targetEntitiesCast); }); @@ -2498,24 +2499,24 @@ class DBEntityRepository extends EntityRepository ) { var databaseAdapter = repositoryAdapter.databaseAdapter; - var entries = - fieldsListEntity.entries.map((e) { - var fieldName = e.key; - var targetType = e.value.arguments0!.type; + var entries = fieldsListEntity.entries.map((e) { + var fieldName = e.key; + var targetType = e.value.arguments0!.type; - var targetRepositoryAdapter = databaseAdapter - .getRepositoryAdapterByType(targetType); - if (targetRepositoryAdapter == null) return null; + var targetRepositoryAdapter = databaseAdapter.getRepositoryAdapterByType( + targetType, + ); + if (targetRepositoryAdapter == null) return null; - var relationship = tableScheme.getTableRelationshipReference( - sourceTable: tableName, - sourceField: fieldName, - targetTable: targetRepositoryAdapter.name, - ); - if (relationship == null) return null; + var relationship = tableScheme.getTableRelationshipReference( + sourceTable: tableName, + sourceField: fieldName, + targetTable: targetRepositoryAdapter.name, + ); + if (relationship == null) return null; - return MapEntry(e.key, relationship); - }).nonNulls; + return MapEntry(e.key, relationship); + }).nonNulls; var relationshipFields = Map.fromEntries(entries); @@ -2704,11 +2705,10 @@ class DBEntityRepository extends EntityRepository checkNotClosed(); return Transaction.executeBlock((transaction) { - var result = - os - .map((o) => store(o, transaction: transaction)) - .toList(growable: false) - .resolveAll(); + var result = os + .map((o) => store(o, transaction: transaction)) + .toList(growable: false) + .resolveAll(); return result; }, transaction: transaction); @@ -2844,8 +2844,9 @@ abstract class DBEntityRepositoryProvider ) { var repositoriesByType = repositories.groupBy((r) => (r.type, r.name)); - var duplicatedRepositoriesGroups = - repositoriesByType.entries.where((e) => e.value.length > 1).toList(); + var duplicatedRepositoriesGroups = repositoriesByType.entries + .where((e) => e.value.length > 1) + .toList(); if (duplicatedRepositoriesGroups.isEmpty) return null; diff --git a/lib/src/bones_api_entity_db_memory.dart b/lib/src/bones_api_entity_db_memory.dart index b7b462cb..0eab9703 100644 --- a/lib/src/bones_api_entity_db_memory.dart +++ b/lib/src/bones_api_entity_db_memory.dart @@ -130,10 +130,8 @@ class DBSQLMemoryAdapter extends DBSQLAdapter }) { boot(); - var ( - generateTables: generateTables, - checkTables: checkTables, - ) = DBSQLAdapter.parseConfigDBGenerateTablesAndCheckTables(config); + var (generateTables: generateTables, checkTables: checkTables) = + DBSQLAdapter.parseConfigDBGenerateTablesAndCheckTables(config); var populate = config?['populate']; Object? populateTables; @@ -316,10 +314,9 @@ class DBSQLMemoryAdapter extends DBSQLAdapter var tableMap = _getTableMap(ref.sourceTable, false); - var reference = - tableMap?.entries - .where((e) => e.value[sourceField] == id) - .firstOrNull; + var reference = tableMap?.entries + .where((e) => e.value[sourceField] == id) + .firstOrNull; if (reference != null) { var value = reference.value[sourceField]; @@ -396,10 +393,9 @@ class DBSQLMemoryAdapter extends DBSQLAdapter o, ); - var uniques = - fieldsEntityAnnotations?.entries - .where((e) => e.value.hasUnique) - .toList(); + var uniques = fieldsEntityAnnotations?.entries + .where((e) => e.value.hasUnique) + .toList(); if (uniques == null || uniques.isEmpty) return; for (var e in uniques) { @@ -541,8 +537,8 @@ class DBSQLMemoryAdapter extends DBSQLAdapter ); Map>> _getTableIndexes(String table) { - var tableIndexes = - _tablesIndexes[table] ??= >>{}; + var tableIndexes = _tablesIndexes[table] ??= + >>{}; return tableIndexes; } @@ -697,15 +693,13 @@ class DBSQLMemoryAdapter extends DBSQLAdapter var valIter = value is Iterable ? value : [value]; - value = - valIter - .map( - (v) => - fieldListEntityRepository.isOfEntityType(v) - ? fieldListEntityRepository.getEntityID(v) - : v, - ) - .toList(); + value = valIter + .map( + (v) => fieldListEntityRepository.isOfEntityType(v) + ? fieldListEntityRepository.getEntityID(v) + : v, + ) + .toList(); } else if (!fieldType.isPrimitiveType && EntityHandler.isValidEntityType(fieldType.type)) { var fieldEntityRepository = getEntityRepositoryByTypeInfo(fieldType); @@ -879,15 +873,14 @@ class DBSQLMemoryAdapter extends DBSQLAdapter // The `ORDER BY` must be applied before the `OFFSET`/`LIMIT`, // which is what makes an offset-based pagination stable: - var sel = - applySelectOrderAndPagination( - itr, - (e) => e[tableScheme?.idFieldName ?? 'id'], - limit: sql.limit, - offset: sql.offset, - orderByID: sql.orderByID, - orderDirection: sql.orderDirection, - ).toList(); + var sel = applySelectOrderAndPagination( + itr, + (e) => e[tableScheme?.idFieldName ?? 'id'], + limit: sql.limit, + offset: sql.offset, + orderByID: sql.orderByID, + orderDirection: sql.orderDirection, + ).toList(); return sel; } @@ -942,14 +935,13 @@ class DBSQLMemoryAdapter extends DBSQLAdapter var entityHandler = getEntityHandler(tableName: table); if (tableScheme == null || tableScheme.fieldsReferencedTablesLength == 0) { - var entries = - map.entries.where((e) { - return sql.condition!.matchesEntityMap( - e.value, - namedParameters: sql.parametersByPlaceholder, - entityHandler: entityHandler, - ); - }).toList(); + var entries = map.entries.where((e) { + return sql.condition!.matchesEntityMap( + e.value, + namedParameters: sql.parametersByPlaceholder, + entityHandler: entityHandler, + ); + }).toList(); _checkNotReferencedEntities(entries, table, sql); @@ -1051,11 +1043,10 @@ class DBSQLMemoryAdapter extends DBSQLAdapter }), ); - var relationshipsTables = - tableScheme.tableRelationshipReference.values - .expand((e) => e) - .where((e) => e.sourceTable == tableScheme.name) - .toList(); + var relationshipsTables = tableScheme.tableRelationshipReference.values + .expand((e) => e) + .where((e) => e.sourceTable == tableScheme.name) + .toList(); if (relationshipsTables.isNotEmpty) { if (entityHandler == null) { @@ -1067,8 +1058,8 @@ class DBSQLMemoryAdapter extends DBSQLAdapter var fieldId = entityHandler.idFieldName(); var id = obj2[fieldId]; - var fieldsListEntity = - entityHandler.fieldsWithTypeListEntityOrReference(); + var fieldsListEntity = entityHandler + .fieldsWithTypeListEntityOrReference(); for (var e in fieldsListEntity.entries) { var fieldKey = e.key; @@ -1164,33 +1155,32 @@ class DBSQLMemoryAdapter extends DBSQLAdapter return []; } - var targetIds = - relWithID - .map((relId) => relMap[relId]?[targetRelationshipField]) - .nonNulls - .toList(); + var targetIds = relWithID + .map((relId) => relMap[relId]?[targetRelationshipField]) + .nonNulls + .toList(); if (targetIds.isEmpty) { return []; } - var targetObjs = - targetIds.map((tId) => _getByID(targetTable, tId) ?? tId).toList(); + var targetObjs = targetIds + .map((tId) => _getByID(targetTable, tId) ?? tId) + .toList(); var tableScheme2 = getTableScheme(targetTable); if (tableScheme2 != null && tableScheme2.hasTableReferences) { var entityHandler2 = getEntityHandler(tableName: tableScheme2.name); - targetObjs = - targetObjs - .map((e) { - return e is! Map - ? null - : _resolveEntityMap(e, entityHandler2, tableScheme2); - }) - .nonNulls - .toList(); + targetObjs = targetObjs + .map((e) { + return e is! Map + ? null + : _resolveEntityMap(e, entityHandler2, tableScheme2); + }) + .nonNulls + .toList(); } return targetObjs; @@ -1338,10 +1328,9 @@ class DBSQLMemoryAdapter extends DBSQLAdapter if (targetEntityHandler != null) { targetName = getEntityRepositoryByType(targetEntityHandler.type)?.name; targetIdField = targetEntityHandler.idFieldName(); - targetIdType = - targetEntityHandler - .getFieldType(null, targetIdField, resolveFiledName: false) - ?.type; + targetIdType = targetEntityHandler + .getFieldType(null, targetIdField, resolveFiledName: false) + ?.type; } targetName ??= targetEntityType.type.toString().toLowerCase(); @@ -1368,8 +1357,8 @@ class DBSQLMemoryAdapter extends DBSQLAdapter var entityType = fieldType.isListEntityOrReference || fieldType.isEntityReferenceType - ? fieldType.arguments0 - : fieldType; + ? fieldType.arguments0 + : fieldType; if (entityType == null || !EntityHandler.isValidEntityType(entityType.type)) { @@ -1408,44 +1397,43 @@ class DBSQLMemoryAdapter extends DBSQLAdapter allTablesReferences.removeWhere((repo, refs) => refs.isEmpty); - var relationships = - allTablesReferences.entries.expand((e) { - var repo = e.key; - var refs = e.value; - - return refs.values.map((ref) { - var sourceTable = ref.sourceTable; - var sourceField = ref.sourceField; - - var targetTable = ref.targetTable; - var targetField = ref.targetField; - - var sourceEntityHandler = repo.entityHandler; - var sourceFieldId = sourceEntityHandler.idFieldName(); - var sourceFieldIdType = - sourceEntityHandler - .getFieldType(null, sourceFieldId, resolveFiledName: false) - ?.type ?? - int; - - var relTable = '${sourceTable}__${sourceField}__rel'; - var relSourceField = '${sourceTable}__$sourceFieldId'; - var relTargetField = '${targetTable}__$targetField'; - - return TableRelationshipReference( - relTable, - sourceTable, - sourceFieldId, - sourceFieldIdType, - relSourceField, - targetTable, - ref.targetField, - ref.targetFieldType, - relTargetField, - relationshipField: sourceField, - ); - }); - }).toList(); + var relationships = allTablesReferences.entries.expand((e) { + var repo = e.key; + var refs = e.value; + + return refs.values.map((ref) { + var sourceTable = ref.sourceTable; + var sourceField = ref.sourceField; + + var targetTable = ref.targetTable; + var targetField = ref.targetField; + + var sourceEntityHandler = repo.entityHandler; + var sourceFieldId = sourceEntityHandler.idFieldName(); + var sourceFieldIdType = + sourceEntityHandler + .getFieldType(null, sourceFieldId, resolveFiledName: false) + ?.type ?? + int; + + var relTable = '${sourceTable}__${sourceField}__rel'; + var relSourceField = '${sourceTable}__$sourceFieldId'; + var relTargetField = '${targetTable}__$targetField'; + + return TableRelationshipReference( + relTable, + sourceTable, + sourceFieldId, + sourceFieldIdType, + relSourceField, + targetTable, + ref.targetField, + ref.targetFieldType, + relTargetField, + relationshipField: sourceField, + ); + }); + }).toList(); return relationships; } diff --git a/lib/src/bones_api_entity_db_mysql.dart b/lib/src/bones_api_entity_db_mysql.dart index f08b113f..b8b65db0 100644 --- a/lib/src/bones_api_entity_db_mysql.dart +++ b/lib/src/bones_api_entity_db_mysql.dart @@ -92,10 +92,9 @@ class DBMySQLAdapter extends DBSQLAdapter super.logSQL, }) : host = host ?? 'localhost', port = port ?? 3306, - _password = - (password != null && password is! PasswordProvider - ? password.toString() - : null), + _password = (password != null && password is! PasswordProvider + ? password.toString() + : null), _passwordProvider = passwordProvider ?? (password is PasswordProvider ? password : null), super( @@ -164,10 +163,8 @@ class DBMySQLAdapter extends DBSQLAdapter minConnections ??= 1; maxConnections ??= 3; - var ( - generateTables: generateTables, - checkTables: checkTables, - ) = DBSQLAdapter.parseConfigDBGenerateTablesAndCheckTables(config); + var (generateTables: generateTables, checkTables: checkTables) = + DBSQLAdapter.parseConfigDBGenerateTablesAndCheckTables(config); var populate = config?['populate']; Object? populateTables; @@ -428,8 +425,9 @@ class DBMySQLAdapter extends DBSQLAdapter ) { var primaryFields = scheme.where((f) => f['Key'] == 'PRI'); - var primaryFieldsNames = - primaryFields.map((e) => e['Field'].toString()).toList(); + var primaryFieldsNames = primaryFields + .map((e) => e['Field'].toString()) + .toList(); return selectIDFieldName(table, primaryFieldsNames); } @@ -507,45 +505,43 @@ class DBMySQLAdapter extends DBSQLAdapter tablesReferences.add(refs); } - tablesReferences = - tablesReferences.where((m) { - return m.length > 1 && - m.values.where((r) => r.targetTable == table).isNotEmpty && - m.values.where((r) => r.targetTable != table).isNotEmpty; - }).toList(); - - var relationships = - tablesReferences - .map((e) { - var refToTables = e.values - .where((r) => r.targetTable == table) - .toList(growable: false); - - var otherRefs = e.values - .where((r) => r.targetTable != table) - .toList(growable: false); - - if (refToTables.length != 1 || otherRefs.length != 1) { - return null; - } - - var refToTable = refToTables.first; - var otherRef = otherRefs.first; - - return TableRelationshipReference( - refToTable.sourceTable, - refToTable.targetTable, - refToTable.targetField, - refToTable.targetFieldType, - refToTable.sourceField, - otherRef.targetTable, - otherRef.targetField, - otherRef.targetFieldType, - otherRef.sourceField, - ); - }) - .nonNulls - .toList(); + tablesReferences = tablesReferences.where((m) { + return m.length > 1 && + m.values.where((r) => r.targetTable == table).isNotEmpty && + m.values.where((r) => r.targetTable != table).isNotEmpty; + }).toList(); + + var relationships = tablesReferences + .map((e) { + var refToTables = e.values + .where((r) => r.targetTable == table) + .toList(growable: false); + + var otherRefs = e.values + .where((r) => r.targetTable != table) + .toList(growable: false); + + if (refToTables.length != 1 || otherRefs.length != 1) { + return null; + } + + var refToTable = refToTables.first; + var otherRef = otherRefs.first; + + return TableRelationshipReference( + refToTable.sourceTable, + refToTable.targetTable, + refToTable.targetField, + refToTable.targetFieldType, + refToTable.sourceField, + otherRef.targetTable, + otherRef.targetField, + otherRef.targetFieldType, + otherRef.sourceField, + ); + }) + .nonNulls + .toList(); return relationships; } @@ -589,14 +585,12 @@ class DBMySQLAdapter extends DBSQLAdapter }) { if (contextID != null) { var cache = _findFieldsReferencedTablesContextCache[contextID] ??= {}; - return cache[table] ??= _findFieldsReferencedTablesImpl( - connection, - table, - ).then((ret) { - cache[table] = ret; - _findFieldsReferencedTablesCache[table] = ret; - return ret; - }); + return cache[table] ??= _findFieldsReferencedTablesImpl(connection, table) + .then((ret) { + cache[table] = ret; + _findFieldsReferencedTablesCache[table] = ret; + return ret; + }); } return _findFieldsReferencedTablesCache.putIfAbsentCheckedAsync( @@ -609,7 +603,8 @@ class DBMySQLAdapter extends DBSQLAdapter DBMySqlConnectionWrapper connection, String table, ) async { - var sql = ''' + var sql = + ''' SELECT CONSTRAINT_NAME, TABLE_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME @@ -624,42 +619,41 @@ class DBMySQLAdapter extends DBSQLAdapter var referenceFields = results.map((r) => r.fields).toList(growable: false); - var mapEntriesRet = - referenceFields - .map((e) { - var sourceTable = e['TABLE_NAME']; - var sourceField = e['COLUMN_NAME']; - var targetTable = e['REFERENCED_TABLE_NAME']; - var targetField = e['REFERENCED_COLUMN_NAME']; - if (targetTable == null || targetField == null) return null; - - var sourceFieldsTypesRet = getTableFieldsTypes(table); - var targetFieldsTypesRet = getTableFieldsTypes(targetTable); - - return sourceFieldsTypesRet.resolveBoth(targetFieldsTypesRet, ( - sourceFieldsTypes, - targetFieldsTypes, - ) { - var sourceFieldType = sourceFieldsTypes?[sourceField] ?? String; - var targetFieldType = targetFieldsTypes?[targetField] ?? String; - - var reference = TableFieldReference( - sourceTable, - sourceField, - sourceFieldType, - targetTable, - targetField, - targetFieldType, - ); - - return MapEntry( - sourceField, - reference, - ); - }); - }) - .nonNulls - .resolveAll(); + var mapEntriesRet = referenceFields + .map((e) { + var sourceTable = e['TABLE_NAME']; + var sourceField = e['COLUMN_NAME']; + var targetTable = e['REFERENCED_TABLE_NAME']; + var targetField = e['REFERENCED_COLUMN_NAME']; + if (targetTable == null || targetField == null) return null; + + var sourceFieldsTypesRet = getTableFieldsTypes(table); + var targetFieldsTypesRet = getTableFieldsTypes(targetTable); + + return sourceFieldsTypesRet.resolveBoth(targetFieldsTypesRet, ( + sourceFieldsTypes, + targetFieldsTypes, + ) { + var sourceFieldType = sourceFieldsTypes?[sourceField] ?? String; + var targetFieldType = targetFieldsTypes?[targetField] ?? String; + + var reference = TableFieldReference( + sourceTable, + sourceField, + sourceFieldType, + targetTable, + targetField, + targetFieldType, + ); + + return MapEntry( + sourceField, + reference, + ); + }); + }) + .nonNulls + .resolveAll(); return mapEntriesRet.resolveMapped((mapEntries) { var map = Map.fromEntries(mapEntries); @@ -847,26 +841,20 @@ class DBMySQLAdapter extends DBSQLAdapter FutureOr> _executeSQLs( List sql, DBMySqlConnectionWrapper connection, - ) => - sql - .map( - (e) => - connection.query(e.sqlPositional, e.parametersValuesByPosition), - ) - .resolveAll(); + ) => sql + .map( + (e) => connection.query(e.sqlPositional, e.parametersValuesByPosition), + ) + .resolveAll(); FutureOr>> _doDeleteSQLImpl( String table, SQL sql, DBMySqlConnectionWrapper connection, ) { - FutureOr sqlRet = - sql.isDummy - ? null - : connection.query( - sql.sqlPositional, - sql.parametersValuesByPosition, - ); + FutureOr sqlRet = sql.isDummy + ? null + : connection.query(sql.sqlPositional, sql.parametersValuesByPosition); var posSQLs = sql.posSQL; @@ -1053,14 +1041,12 @@ class DBMySQLAdapter extends DBSQLAdapter }); }, validator: (c) => !transaction.isAborted, - onError: - (e, s) => transaction.notifyExecutionError( - e, - s, - errorResolver: resolveError, - debugInfo: - () => transaction.toString(withExecutedOperations: false), - ), + onError: (e, s) => transaction.notifyExecutionError( + e, + s, + errorResolver: resolveError, + debugInfo: () => transaction.toString(withExecutedOperations: false), + ), ); transaction.transactionResult = result; diff --git a/lib/src/bones_api_entity_db_object_directory.dart b/lib/src/bones_api_entity_db_object_directory.dart index daab98a4..2f7315e4 100644 --- a/lib/src/bones_api_entity_db_object_directory.dart +++ b/lib/src/bones_api_entity_db_object_directory.dart @@ -402,11 +402,10 @@ class DBObjectDirectoryAdapter } List _listTableFiles(Directory tableDir) { - var list = - tableDir.listSync(recursive: false).whereType().where((e) { - var path = e.path; - return path.endsWith('.json') && !path.startsWith('.'); - }).toList(); + var list = tableDir.listSync(recursive: false).whereType().where((e) { + var path = e.path; + return path.endsWith('.json') && !path.startsWith('.'); + }).toList(); return list; } @@ -443,11 +442,10 @@ class DBObjectDirectoryAdapter var tableDir = _resolveTableDirectory(table); if (!tableDir.existsSync()) return []; - var existIDs = - ids.where((id) { - var objFile = _resolveObjectFile(table, id); - return objFile.existsSync(); - }).toList(); + var existIDs = ids.where((id) { + var objFile = _resolveObjectFile(table, id); + return objFile.existsSync(); + }).toList(); return existIDs; } @@ -516,8 +514,9 @@ class DBObjectDirectoryAdapter var tableDir = _resolveTableDirectory(table); if (!tableDir.existsSync()) return []; - var entries = - await ids.map((id) => _readObject(table, id)).resolveAllNotNull(); + var entries = await ids + .map((id) => _readObject(table, id)) + .resolveAllNotNull(); return _applyOrderAndPagination( table, @@ -564,12 +563,11 @@ class DBObjectDirectoryAdapter var files = _listTableFiles(tableDir); - var entries = - files.map((f) { - var fileName = pack_path.split(f.path).last; - var id = pack_path.withoutExtension(fileName); - return _readObject(table, id); - }).resolveAllNotNull(); + var entries = files.map((f) { + var fileName = pack_path.split(f.path).last; + var id = pack_path.withoutExtension(fileName); + return _readObject(table, id); + }).resolveAllNotNull(); return entries.resolveMapped( (entries) => _applyOrderAndPagination( @@ -881,15 +879,13 @@ class DBObjectDirectoryAdapter var valIter = value is Iterable ? value : [value]; - value = - valIter - .map( - (v) => - fieldListEntityRepository.isOfEntityType(v) - ? fieldListEntityRepository.getEntityID(v) - : v, - ) - .toList(); + value = valIter + .map( + (v) => fieldListEntityRepository.isOfEntityType(v) + ? fieldListEntityRepository.getEntityID(v) + : v, + ) + .toList(); } else if (fieldType.isListEntity) { var listEntityType = fieldType.listEntityType!; @@ -904,15 +900,13 @@ class DBObjectDirectoryAdapter var valIter = value is Iterable ? value : [value]; - value = - valIter - .map( - (v) => - fieldListEntityRepository.isOfEntityType(v) - ? fieldListEntityRepository.getEntityID(v) - : v, - ) - .toList(); + value = valIter + .map( + (v) => fieldListEntityRepository.isOfEntityType(v) + ? fieldListEntityRepository.getEntityID(v) + : v, + ) + .toList(); } else if (!fieldType.isPrimitiveType && fieldType.entityType != null) { var entityType = fieldType.entityType!; var fieldEntityRepository = getEntityRepositoryByType(entityType); @@ -968,14 +962,13 @@ class DBObjectDirectoryAdapter if (isTransactionWithSingleOperation(op)) { return executeWithPool( f, - onError: - (e, s) => transaction.notifyExecutionError( - e, - s, - errorResolver: resolveError, - operation: op, - debugInfo: () => op.toString(), - ), + onError: (e, s) => transaction.notifyExecutionError( + e, + s, + errorResolver: resolveError, + operation: op, + debugInfo: () => op.toString(), + ), ); } @@ -993,9 +986,9 @@ class DBObjectDirectoryAdapter () => openTransaction(transaction), callCloseTransactionRequired ? () => closeTransaction( - transaction, - transaction.context as DBObjectDirectoryAdapterContext?, - ) + transaction, + transaction.context as DBObjectDirectoryAdapterContext?, + ) : null, ); } diff --git a/lib/src/bones_api_entity_db_object_gcs.dart b/lib/src/bones_api_entity_db_object_gcs.dart index 881f2a9f..274aefa5 100644 --- a/lib/src/bones_api_entity_db_object_gcs.dart +++ b/lib/src/bones_api_entity_db_object_gcs.dart @@ -185,10 +185,9 @@ class DBObjectGCSAdapter extends DBObjectAdapter { if (limitStr != null && limitStr.isNotEmpty) { limitStr = limitStr.replaceAll(RegExp(r'\s'), '').toLowerCase(); - var nPart = - limitStr.length > 1 - ? limitStr.substring(0, limitStr.length - 1) - : limitStr; + var nPart = limitStr.length > 1 + ? limitStr.substring(0, limitStr.length - 1) + : limitStr; int unit; if (limitStr.endsWith('g')) { @@ -607,8 +606,9 @@ class DBObjectGCSAdapter extends DBObjectAdapter { bool? orderByID, OrderDirection? orderDirection, }) async { - var entries = - await ids.map((id) => _readObject(table, id)).resolveAllNotNull(); + var entries = await ids + .map((id) => _readObject(table, id)) + .resolveAllNotNull(); return _applyOrderAndPagination( table, @@ -652,12 +652,11 @@ class DBObjectGCSAdapter extends DBObjectAdapter { }) async { var files = await _listTableFiles(table); - var entries = - await files.map((f) { - var fileName = f.split('/').last; - var id = pack_path.withoutExtension(fileName); - return _readObject(table, id); - }).resolveAllNotNull(); + var entries = await files.map((f) { + var fileName = f.split('/').last; + var id = pack_path.withoutExtension(fileName); + return _readObject(table, id); + }).resolveAllNotNull(); return _applyOrderAndPagination( table, @@ -1047,16 +1046,17 @@ class DBObjectGCSAdapter extends DBObjectAdapter { _checkCacheDirectoryLimitLastTime = checkInitTime; _checkCacheDirectoryLimitSkips = 0; - var files = - list.whereType().where((f) => f.path.endsWith(".json")).toList(); + var files = list + .whereType() + .where((f) => f.path.endsWith(".json")) + .toList(); var totalFiles = files.length; - var filesStats = - await files - .map((f) => MapEntry(f, f.statLimited())) - .toMapFromEntries() - .resolveAllValues(); + var filesStats = await files + .map((f) => MapEntry(f, f.statLimited())) + .toMapFromEntries() + .resolveAllValues(); var totalSize = filesStats.values.map((s) => s.size).sum; @@ -1082,19 +1082,17 @@ class DBObjectGCSAdapter extends DBObjectAdapter { var entries = filesStats.entries.toList(); entries.sort((a, b) => a.value.modified.compareTo(b.value.modified)); - final delNeededFiles = - cacheFilesLimit <= 0 - ? 0 - : - // Del extra files + 20%: - (totalFiles - (cacheFilesLimit * 0.80)).toInt(); + final delNeededFiles = cacheFilesLimit <= 0 + ? 0 + : + // Del extra files + 20%: + (totalFiles - (cacheFilesLimit * 0.80)).toInt(); - final delNeededSize = - cacheLimit <= 0 - ? 0 - : - // Del extra bytes + 20%: - (totalSize - (cacheLimit * 0.80)).toInt(); + final delNeededSize = cacheLimit <= 0 + ? 0 + : + // Del extra bytes + 20%: + (totalSize - (cacheLimit * 0.80)).toInt(); var releaseInfo = [ if (delNeededFiles > 0) '$delNeededFiles files', @@ -1124,8 +1122,10 @@ class DBObjectGCSAdapter extends DBObjectAdapter { final removeInitTime = DateTime.now(); - var delResults = - await del.map((f) => f.deleteLimited()).toList().resolveAll(); + var delResults = await del + .map((f) => f.deleteLimited()) + .toList() + .resolveAll(); final removeTime = DateTime.now().difference(removeInitTime); @@ -1298,15 +1298,13 @@ class DBObjectGCSAdapter extends DBObjectAdapter { var valIter = value is Iterable ? value : [value]; - value = - valIter - .map( - (v) => - fieldListEntityRepository.isOfEntityType(v) - ? fieldListEntityRepository.getEntityID(v) - : v, - ) - .toList(); + value = valIter + .map( + (v) => fieldListEntityRepository.isOfEntityType(v) + ? fieldListEntityRepository.getEntityID(v) + : v, + ) + .toList(); } else if (fieldType.isListEntity) { var listEntityType = fieldType.listEntityType!; @@ -1321,15 +1319,13 @@ class DBObjectGCSAdapter extends DBObjectAdapter { var valIter = value is Iterable ? value : [value]; - value = - valIter - .map( - (v) => - fieldListEntityRepository.isOfEntityType(v) - ? fieldListEntityRepository.getEntityID(v) - : v, - ) - .toList(); + value = valIter + .map( + (v) => fieldListEntityRepository.isOfEntityType(v) + ? fieldListEntityRepository.getEntityID(v) + : v, + ) + .toList(); } else if (!fieldType.isPrimitiveType && fieldType.entityType != null) { var entityType = fieldType.entityType!; var fieldEntityRepository = getEntityRepositoryByType(entityType); @@ -1385,14 +1381,13 @@ class DBObjectGCSAdapter extends DBObjectAdapter { if (isTransactionWithSingleOperation(op)) { return executeWithPool( f, - onError: - (e, s) => transaction.notifyExecutionError( - e, - s, - errorResolver: resolveError, - operation: op, - debugInfo: () => op.toString(), - ), + onError: (e, s) => transaction.notifyExecutionError( + e, + s, + errorResolver: resolveError, + operation: op, + debugInfo: () => op.toString(), + ), ); } @@ -1410,9 +1405,9 @@ class DBObjectGCSAdapter extends DBObjectAdapter { () => openTransaction(transaction), callCloseTransactionRequired ? () => closeTransaction( - transaction, - transaction.context as DBObjectGCSAdapterContext?, - ) + transaction, + transaction.context as DBObjectGCSAdapterContext?, + ) : null, ); } diff --git a/lib/src/bones_api_entity_db_object_memory.dart b/lib/src/bones_api_entity_db_object_memory.dart index 0aaf2386..65655c75 100644 --- a/lib/src/bones_api_entity_db_object_memory.dart +++ b/lib/src/bones_api_entity_db_object_memory.dart @@ -564,19 +564,18 @@ class DBObjectMemoryAdapter var map = _getTableMap(table, false); if (map == null) return []; - var entries = - ids - .map((id) { - var entry = map[id]; + var entries = ids + .map((id) { + var entry = map[id]; - if (entry == null) { - return null; - } + if (entry == null) { + return null; + } - return entry; - }) - .nonNulls - .toList(); + return entry; + }) + .nonNulls + .toList(); return _applyOrderAndPagination( table, @@ -859,15 +858,13 @@ class DBObjectMemoryAdapter var valIter = value is Iterable ? value : [value]; - value = - valIter - .map( - (v) => - fieldListEntityRepository.isOfEntityType(v) - ? fieldListEntityRepository.getEntityID(v) - : v, - ) - .toList(); + value = valIter + .map( + (v) => fieldListEntityRepository.isOfEntityType(v) + ? fieldListEntityRepository.getEntityID(v) + : v, + ) + .toList(); } else if (fieldType.isListEntity) { var listEntityType = fieldType.listEntityType!; @@ -882,15 +879,13 @@ class DBObjectMemoryAdapter var valIter = value is Iterable ? value : [value]; - value = - valIter - .map( - (v) => - fieldListEntityRepository.isOfEntityType(v) - ? fieldListEntityRepository.getEntityID(v) - : v, - ) - .toList(); + value = valIter + .map( + (v) => fieldListEntityRepository.isOfEntityType(v) + ? fieldListEntityRepository.getEntityID(v) + : v, + ) + .toList(); } else if (!fieldType.isPrimitiveType && fieldType.entityType != null) { var entityType = fieldType.entityType!; var fieldEntityRepository = getEntityRepositoryByType(entityType); @@ -946,14 +941,13 @@ class DBObjectMemoryAdapter if (isTransactionWithSingleOperation(op)) { return executeWithPool( f, - onError: - (e, s) => transaction.notifyExecutionError( - e, - s, - errorResolver: resolveError, - operation: op, - debugInfo: () => op.toString(), - ), + onError: (e, s) => transaction.notifyExecutionError( + e, + s, + errorResolver: resolveError, + operation: op, + debugInfo: () => op.toString(), + ), ); } @@ -971,9 +965,9 @@ class DBObjectMemoryAdapter () => openTransaction(transaction), callCloseTransactionRequired ? () => closeTransaction( - transaction, - transaction.context as DBObjectMemoryAdapterContext?, - ) + transaction, + transaction.context as DBObjectMemoryAdapterContext?, + ) : null, ); } diff --git a/lib/src/bones_api_entity_db_postgres.dart b/lib/src/bones_api_entity_db_postgres.dart index 3bc1630c..5589e853 100644 --- a/lib/src/bones_api_entity_db_postgres.dart +++ b/lib/src/bones_api_entity_db_postgres.dart @@ -100,10 +100,9 @@ class DBPostgreSQLAdapter extends DBSQLAdapter super.logSQL, }) : host = host ?? 'localhost', port = port ?? 5432, - _password = - (password != null && password is! PasswordProvider - ? password.toString() - : null), + _password = (password != null && password is! PasswordProvider + ? password.toString() + : null), _passwordProvider = passwordProvider ?? (password is PasswordProvider ? password : null), super( @@ -170,10 +169,8 @@ class DBPostgreSQLAdapter extends DBSQLAdapter minConnections ??= 1; maxConnections ??= 3; - var ( - generateTables: generateTables, - checkTables: checkTables, - ) = DBSQLAdapter.parseConfigDBGenerateTablesAndCheckTables(config); + var (generateTables: generateTables, checkTables: checkTables) = + DBSQLAdapter.parseConfigDBGenerateTablesAndCheckTables(config); var populate = config?['populate']; Object? populateTables; @@ -385,10 +382,9 @@ class DBPostgreSQLAdapter extends DBSQLAdapter } else if (connectivity == DBAdapterConnectivity.insecure) { (connection, secure) = await _connectNoSSLImpl(endpoint, timeout); } else { - (connection, secure) = - await (_lastConnectSSLSupported - ? _connectSSLImpl(endpoint, timeout) - : _connectNoSSLImpl(endpoint, timeout)); + (connection, secure) = await (_lastConnectSSLSupported + ? _connectSSLImpl(endpoint, timeout) + : _connectNoSSLImpl(endpoint, timeout)); } if (connection == null) return null; @@ -640,7 +636,8 @@ class DBPostgreSQLAdapter extends DBSQLAdapter String table, Map fieldsTypes, ) async { - var sql = ''' + var sql = + ''' SELECT pg_get_constraintdef(con.oid) AS constraint_definition FROM pg_constraint con @@ -651,33 +648,34 @@ class DBPostgreSQLAdapter extends DBSQLAdapter var columns = await connection.mappedResultsQuery(sql); - var constraintsDefinitions = - columns.map((m) => m['constraint_definition'].toString()).toList(); + var constraintsDefinitions = columns + .map((m) => m['constraint_definition'].toString()) + .toList(); - var constraints = - constraintsDefinitions.map(_parseConstraint).nonNulls.toSet(); + var constraints = constraintsDefinitions + .map(_parseConstraint) + .nonNulls + .toSet(); return constraints; } TableConstraint? _parseConstraint(String definition) { if (definition.startsWith("PRIMARY KEY")) { - var field = - RegExp(r'\(([^()]+?)\)') - .firstMatch(definition) - ?.group(1) - ?.replaceAll("'", '') - .replaceAll('"', '') - .trim(); + var field = RegExp(r'\(([^()]+?)\)') + .firstMatch(definition) + ?.group(1) + ?.replaceAll("'", '') + .replaceAll('"', '') + .trim(); return field == null ? null : TablePrimaryKeyConstraint(field); } else if (definition.startsWith("UNIQUE")) { - var field = - RegExp(r'\(([^()]+?)\)') - .firstMatch(definition) - ?.group(1) - ?.replaceAll("'", '') - .replaceAll('"', '') - .trim(); + var field = RegExp(r'\(([^()]+?)\)') + .firstMatch(definition) + ?.group(1) + ?.replaceAll("'", '') + .replaceAll('"', '') + .trim(); return field == null ? null : TableUniqueConstraint(field); } else if (definition.startsWith("CHECK")) { var idx1 = definition.indexOf('('); @@ -685,13 +683,9 @@ class DBPostgreSQLAdapter extends DBSQLAdapter var s = definition.substring(idx1 + 1, idx2); - var field = - RegExp(r'\(([^()]+?)\)') - .firstMatch(s) - ?.group(1) - ?.replaceAll("'", '') - .replaceAll('"', '') - .trim(); + var field = RegExp( + r'\(([^()]+?)\)', + ).firstMatch(s)?.group(1)?.replaceAll("'", '').replaceAll('"', '').trim(); if (field == null || field.isEmpty) return null; @@ -700,13 +694,13 @@ class DBPostgreSQLAdapter extends DBSQLAdapter Set? values; if (arrayDef != null && arrayDef.isNotEmpty) { - values = - RegExp( - r"'(.*?)'", - ).allMatches(arrayDef).map((m) => m.group(1)).nonNulls.toSet(); + values = RegExp( + r"'(.*?)'", + ).allMatches(arrayDef).map((m) => m.group(1)).nonNulls.toSet(); } else { - var singleValue = - RegExp(r"\s+=\s+'(.*?)'").firstMatch(s)?.group(1)?.trim(); + var singleValue = RegExp( + r"\s+=\s+'(.*?)'", + ).firstMatch(s)?.group(1)?.trim(); if (singleValue != null) { values = {singleValue}; @@ -726,7 +720,8 @@ class DBPostgreSQLAdapter extends DBSQLAdapter String table, List> scheme, ) async { - var sql = ''' + var sql = + ''' SELECT c.column_name, c.data_type FROM @@ -821,41 +816,40 @@ class DBPostgreSQLAdapter extends DBSQLAdapter contextID: contextID, ); - var relationships = - tablesReferences - .map((e) { - var refToTables = e.values - .where((r) => r.targetTable == table) - .toList(growable: false); - var otherRefs = e.values - .where((r) => r.targetTable != table) - .toList(growable: false); - - if (refToTables.length != 1 || otherRefs.length != 1) { - return null; - } + var relationships = tablesReferences + .map((e) { + var refToTables = e.values + .where((r) => r.targetTable == table) + .toList(growable: false); + var otherRefs = e.values + .where((r) => r.targetTable != table) + .toList(growable: false); + + if (refToTables.length != 1 || otherRefs.length != 1) { + return null; + } - var refToTable = refToTables.single; - var otherRef = otherRefs.single; - - var tableRelationshipReference = TableRelationshipReference( - refToTable.sourceTable, - refToTable.targetTable, - refToTable.targetField, - refToTable.targetFieldType, - refToTable.sourceField, - otherRef.targetTable, - otherRef.targetField, - otherRef.targetFieldType, - otherRef.sourceField, - sourceRelationshipFieldIndex: refToTable.indexName, - targetRelationshipFieldIndex: otherRef.indexName, - ); + var refToTable = refToTables.single; + var otherRef = otherRefs.single; + + var tableRelationshipReference = TableRelationshipReference( + refToTable.sourceTable, + refToTable.targetTable, + refToTable.targetField, + refToTable.targetFieldType, + refToTable.sourceField, + otherRef.targetTable, + otherRef.targetField, + otherRef.targetFieldType, + otherRef.sourceField, + sourceRelationshipFieldIndex: refToTable.indexName, + targetRelationshipFieldIndex: otherRef.indexName, + ); - return tableRelationshipReference; - }) - .nonNulls - .toList(); + return tableRelationshipReference; + }) + .nonNulls + .toList(); return relationships; } @@ -879,12 +873,11 @@ class DBPostgreSQLAdapter extends DBSQLAdapter tablesReferences.add(refs); } - tablesReferences = - tablesReferences.where((m) { - return m.length > 1 && - m.values.where((r) => r.targetTable == table).isNotEmpty && - m.values.where((r) => r.targetTable != table).isNotEmpty; - }).toList(); + tablesReferences = tablesReferences.where((m) { + return m.length > 1 && + m.values.where((r) => r.targetTable == table).isNotEmpty && + m.values.where((r) => r.targetTable != table).isNotEmpty; + }).toList(); return tablesReferences; } @@ -927,14 +920,12 @@ class DBPostgreSQLAdapter extends DBSQLAdapter }) { if (contextID != null) { var cache = _findFieldsReferencedTablesContextCache[contextID] ??= {}; - return cache[table] ??= _findFieldsReferencedTablesImpl( - connection, - table, - ).then((ret) { - cache[table] = ret; - _findFieldsReferencedTablesCache[table] = ret; - return ret; - }); + return cache[table] ??= _findFieldsReferencedTablesImpl(connection, table) + .then((ret) { + cache[table] = ret; + _findFieldsReferencedTablesCache[table] = ret; + return ret; + }); } return _findFieldsReferencedTablesCache.putIfAbsentCheckedAsync( @@ -947,7 +938,8 @@ class DBPostgreSQLAdapter extends DBSQLAdapter PostgreSQLConnectionWrapper connection, String table, ) async { - var sql = ''' + var sql = + ''' SELECT o.conname AS constraint_name, @@ -1031,15 +1023,13 @@ class DBPostgreSQLAdapter extends DBSQLAdapter fkIndexName = null; } - var sourceFieldType = - sourceFieldDataType != null - ? _toFieldType(sourceFieldDataType) - : String; + var sourceFieldType = sourceFieldDataType != null + ? _toFieldType(sourceFieldDataType) + : String; - var targetFieldType = - targetFieldDataType != null - ? _toFieldType(targetFieldDataType) - : String; + var targetFieldType = targetFieldDataType != null + ? _toFieldType(targetFieldDataType) + : String; var reference = TableFieldReference( sourceTable, @@ -1162,16 +1152,15 @@ class DBPostgreSQLAdapter extends DBSQLAdapter substitutionValues: sql.parametersByPlaceholder, ) .resolveMapped((results) { - var count = - results - .map((row) { - var count = row['count'] ?? 0; - return count is int - ? count - : int.tryParse(count.toString().trim()); - }) - .whereType() - .first; + var count = results + .map((row) { + var count = row['count'] ?? 0; + return count is int + ? count + : int.tryParse(count.toString().trim()); + }) + .whereType() + .first; return count; }); } @@ -1414,14 +1403,12 @@ class DBPostgreSQLAdapter extends DBSQLAdapter }); }, validator: (c) => !transaction.isAborted, - onError: - (e, s) => transaction.notifyExecutionError( - e, - s, - errorResolver: resolveError, - debugInfo: - () => transaction.toString(withExecutedOperations: false), - ), + onError: (e, s) => transaction.notifyExecutionError( + e, + s, + errorResolver: resolveError, + debugInfo: () => transaction.toString(withExecutedOperations: false), + ), ); transaction.transactionResult = result; diff --git a/lib/src/bones_api_entity_db_relational.dart b/lib/src/bones_api_entity_db_relational.dart index aa4f6347..e47419c3 100644 --- a/lib/src/bones_api_entity_db_relational.dart +++ b/lib/src/bones_api_entity_db_relational.dart @@ -384,21 +384,19 @@ class DBRelationalEntityRepository Map? _nonPrimitiveFields; Map _getNonPrimitiveFields(O o) => - _nonPrimitiveFields ??= - entityHandler - .fieldsNames(o) - .map((fieldName) { - var fieldType = - entityHandler.getFieldType( - o, - fieldName, - resolveFiledName: false, - )!; - if (fieldType.isPrimitiveType) return null; - return MapEntry(fieldName, fieldType); - }) - .nonNulls - .toMapFromEntries(); + _nonPrimitiveFields ??= entityHandler + .fieldsNames(o) + .map((fieldName) { + var fieldType = entityHandler.getFieldType( + o, + fieldName, + resolveFiledName: false, + )!; + if (fieldType.isPrimitiveType) return null; + return MapEntry(fieldName, fieldType); + }) + .nonNulls + .toMapFromEntries(); @override FutureOr ensureReferencesStored( @@ -441,15 +439,14 @@ class DBRelationalEntityRepository ); if (elementRepository == null) return null; - var futures = - value.map((e) { - if (!elementRepository.isOfEntityType(e)) return e; - return elementRepository.ensureStored( - e, - transaction: transaction, - operation: operation, - ); - }).toList(); + var futures = value.map((e) { + if (!elementRepository.isOfEntityType(e)) return e; + return elementRepository.ensureStored( + e, + transaction: transaction, + operation: operation, + ); + }).toList(); return futures.resolveAll().resolveMapped((ids) { if (fieldValue is EntityReferenceList) { @@ -785,26 +782,25 @@ class DBRelationalEntityRepository ); } - var ret = - fieldsListEntity.entries.map((e) { - var field = e.key; - var fieldType = e.value; - var values = entityHandler.getField(o, field); - if (values is EntityReferenceList) { - values = values.entitiesOrIDs; - } + var ret = fieldsListEntity.entries.map((e) { + var field = e.key; + var fieldType = e.value; + var values = entityHandler.getField(o, field); + if (values is EntityReferenceList) { + values = values.entitiesOrIDs; + } - var list = values is Iterable ? values.asList : [values]; - var listNotNull = list.nonNulls.toList(); + var list = values is Iterable ? values.asList : [values]; + var listNotNull = list.nonNulls.toList(); - return setRelationship( - o, - field, - listNotNull as dynamic, - fieldType: fieldType, - transaction: transaction, - ); - }).resolveAll(); + return setRelationship( + o, + field, + listNotNull as dynamic, + fieldType: fieldType, + transaction: transaction, + ); + }).resolveAll(); return ret.resolveWithValue(true); } @@ -833,15 +829,13 @@ class DBRelationalEntityRepository var oId = entityHandler.getID(o); - var othersIds = - values - .map( - (e) => - valuesEntityHandler.isEntityInstance(e) - ? valuesEntityHandler.getID(e) - : e, - ) - .toList(); + var othersIds = values + .map( + (e) => valuesEntityHandler.isEntityInstance(e) + ? valuesEntityHandler.getID(e) + : e, + ) + .toList(); try { return repositoryAdapter.doInsertRelationship( @@ -932,10 +926,9 @@ class DBRelationalEntityRepository if (fieldValue != null) { var fieldEntityHandler = entityHandler.getEntityHandler( - type: - fieldType.isListEntityOrReference - ? fieldType.listEntityOrReferenceType!.type - : fieldType.type, + type: fieldType.isListEntityOrReference + ? fieldType.listEntityOrReferenceType!.type + : fieldType.type, ); if (fieldValue is EntityReferenceBase) { @@ -944,23 +937,18 @@ class DBRelationalEntityRepository if (fieldEntityHandler != null) { if (fieldValue is Iterable) { - var fieldIds = - fieldValue - .map( - (Object? e) => - fieldEntityHandler.isEntityInstance(e) - ? fieldEntityHandler.getID(e) - : (e.isEntityIDType ? e : null), - ) - .toList(); + var fieldIds = fieldValue + .map( + (Object? e) => fieldEntityHandler.isEntityInstance(e) + ? fieldEntityHandler.getID(e) + : (e.isEntityIDType ? e : null), + ) + .toList(); return fieldIds; } else { - var fieldId = - fieldEntityHandler.isEntityInstance(fieldValue) - ? fieldEntityHandler.getID(fieldValue) - : ((fieldValue as Object?).isEntityIDType - ? fieldValue - : null); + var fieldId = fieldEntityHandler.isEntityInstance(fieldValue) + ? fieldEntityHandler.getID(fieldValue) + : ((fieldValue as Object?).isEntityIDType ? fieldValue : null); return [fieldId]; } } @@ -986,44 +974,43 @@ class DBRelationalEntityRepository if (cachedEntities != null && cachedEntities.isNotEmpty) { var fieldEntityHandler = entityHandler.getEntityHandler( - type: - fieldType.isListEntityOrReference - ? fieldType.arguments0!.type - : fieldType.type, + type: fieldType.isListEntityOrReference + ? fieldType.arguments0!.type + : fieldType.type, ); if (fieldEntityHandler == null) return null; - var relationshipEntries = - cachedEntities.entries.map((e) { - var id = e.key; - var entity = e.value; - - var fieldValue = entityHandler.getField(entity as dynamic, field); - - if (fieldValue != null) { - if (fieldValue is Iterable) { - var fieldIds = - fieldValue.map((e) => fieldEntityHandler.getID(e)).toList(); - return MapEntry(id, fieldIds); - } else if (fieldValue is EntityReferenceList) { - var fieldIds = - fieldValue.entities - ?.map((e) => fieldEntityHandler.getID(e)) - .toList() ?? - []; - return MapEntry(id, fieldIds); - } else if (fieldValue is EntityReference) { - var fieldId = fieldEntityHandler.getID(fieldValue.entity); - return MapEntry(id, [fieldId]); - } else { - var fieldId = fieldEntityHandler.getID(fieldValue); - return MapEntry(id, [fieldId]); - } - } + var relationshipEntries = cachedEntities.entries.map((e) { + var id = e.key; + var entity = e.value; + + var fieldValue = entityHandler.getField(entity as dynamic, field); + + if (fieldValue != null) { + if (fieldValue is Iterable) { + var fieldIds = fieldValue + .map((e) => fieldEntityHandler.getID(e)) + .toList(); + return MapEntry(id, fieldIds); + } else if (fieldValue is EntityReferenceList) { + var fieldIds = + fieldValue.entities + ?.map((e) => fieldEntityHandler.getID(e)) + .toList() ?? + []; + return MapEntry(id, fieldIds); + } else if (fieldValue is EntityReference) { + var fieldId = fieldEntityHandler.getID(fieldValue.entity); + return MapEntry(id, [fieldId]); + } else { + var fieldId = fieldEntityHandler.getID(fieldValue); + return MapEntry(id, [fieldId]); + } + } - return null; - }).nonNulls; + return null; + }).nonNulls; var relationships = Map.fromEntries(relationshipEntries); @@ -1043,13 +1030,15 @@ class DBRelationalEntityRepository TypeInfo? fieldType, Transaction? transaction, }) { - oIds ??= - os! - .map((o) => getID(o, entityHandler: entityHandler)! as Object) - .toList(); + oIds ??= os! + .map((o) => getID(o, entityHandler: entityHandler)! as Object) + .toList(); - fieldType ??= - entityHandler.getFieldType(os?.first, field, resolveFiledName: true)!; + fieldType ??= entityHandler.getFieldType( + os?.first, + field, + resolveFiledName: true, + )!; if (oIds.isEmpty) { return >{}; diff --git a/lib/src/bones_api_entity_db_sql.dart b/lib/src/bones_api_entity_db_sql.dart index 8aadabaf..e4a7dde6 100644 --- a/lib/src/bones_api_entity_db_sql.dart +++ b/lib/src/bones_api_entity_db_sql.dart @@ -624,91 +624,66 @@ abstract class DBSQLAdapter extends DBRelationalAdapter FutureOr _checkDBTablesImpl( Map, TableScheme?> repositorySchemes, ) { - var repositoriesChecks = - repositorySchemes.entries - .map((e) => _checkDBTableScheme(e.key, e.value)) - .toList(); + var repositoriesChecks = repositorySchemes.entries + .map((e) => _checkDBTableScheme(e.key, e.value)) + .toList(); - var repositoriesChecksErrors = - repositoriesChecks.where((e) => e.isError).toList(); + var repositoriesChecksErrors = repositoriesChecks + .where((e) => e.isError) + .toList(); if (repositoriesChecksErrors.isNotEmpty) { - var missingColumnsSQLs = - repositoriesChecksErrors - .where((e) => e.missingColumns?.isNotEmpty ?? false) - .expand((e) => e.generateMissingColumnsSQLs(this)) - .expand( - (e) => e.buildAllSQLs(ifNotExists: true, multiline: false), - ) - .toList(); - - var missingReferenceColumnsSQLs = - repositoriesChecksErrors - .where((e) => e.missingReferenceColumns?.isNotEmpty ?? false) - .expand((e) => e.generateMissingReferenceColumnsSQLs(this)) - .expand( - (e) => e.buildAllSQLs(ifNotExists: true, multiline: false), - ) - .toList(); - - var missingReferenceConstraintsSQLs = - repositoriesChecksErrors - .where((e) => e.missingReferenceConstraints?.isNotEmpty ?? false) - .expand((e) => e.generateMissingReferenceConstraintsSQLs(this)) - .expand( - (e) => e.buildAllSQLs(ifNotExists: true, multiline: false), - ) - .toList(); - - var missingReferenceIndexesSQLs = - repositoriesChecksErrors - .where((e) => e.missingReferenceIndexes?.isNotEmpty ?? false) - .expand((e) => e.generateMissingReferenceIndexesSQLs(this)) - .expand( - (e) => e.buildAllSQLs(ifNotExists: true, multiline: false), - ) - .toList(); - - var missingRelationshipReferenceIndexesSQLs = - repositoriesChecksErrors - .where( - (e) => - e.missingRelationshipReferenceIndexes?.isNotEmpty ?? false, - ) - .expand( - (e) => e.generateMissingRelationshipReferenceIndexesSQLs(this), - ) - .expand( - (e) => e.buildAllSQLs(ifNotExists: true, multiline: false), - ) - .toList(); - - var missingUniqueConstraintsSQLs = - repositoriesChecksErrors - .where((e) => e.missingUniqueConstraints?.isNotEmpty ?? false) - .expand((e) => e.generateMissingUniqueConstraintsSQLs(this)) - .expand( - (e) => e.buildAllSQLs(ifNotExists: true, multiline: false), - ) - .toList(); - - var missingEnumConstraintsSQLs = - repositoriesChecksErrors - .where((e) => e.missingEnumConstraints?.isNotEmpty ?? false) - .expand((e) => e.generateMissingEnumConstraintsSQLs(this)) - .expand( - (e) => e.buildAllSQLs(ifNotExists: true, multiline: false), - ) - .toList(); - - var missingEnumValuesSQLs = - repositoriesChecksErrors - .where((e) => e.missingEnumValues?.isNotEmpty ?? false) - .expand((e) => e.generateMissingEnumValuesSQLs(this)) - .expand( - (e) => e.buildAllSQLs(ifNotExists: true, multiline: false), - ) - .toList(); + var missingColumnsSQLs = repositoriesChecksErrors + .where((e) => e.missingColumns?.isNotEmpty ?? false) + .expand((e) => e.generateMissingColumnsSQLs(this)) + .expand((e) => e.buildAllSQLs(ifNotExists: true, multiline: false)) + .toList(); + + var missingReferenceColumnsSQLs = repositoriesChecksErrors + .where((e) => e.missingReferenceColumns?.isNotEmpty ?? false) + .expand((e) => e.generateMissingReferenceColumnsSQLs(this)) + .expand((e) => e.buildAllSQLs(ifNotExists: true, multiline: false)) + .toList(); + + var missingReferenceConstraintsSQLs = repositoriesChecksErrors + .where((e) => e.missingReferenceConstraints?.isNotEmpty ?? false) + .expand((e) => e.generateMissingReferenceConstraintsSQLs(this)) + .expand((e) => e.buildAllSQLs(ifNotExists: true, multiline: false)) + .toList(); + + var missingReferenceIndexesSQLs = repositoriesChecksErrors + .where((e) => e.missingReferenceIndexes?.isNotEmpty ?? false) + .expand((e) => e.generateMissingReferenceIndexesSQLs(this)) + .expand((e) => e.buildAllSQLs(ifNotExists: true, multiline: false)) + .toList(); + + var missingRelationshipReferenceIndexesSQLs = repositoriesChecksErrors + .where( + (e) => e.missingRelationshipReferenceIndexes?.isNotEmpty ?? false, + ) + .expand( + (e) => e.generateMissingRelationshipReferenceIndexesSQLs(this), + ) + .expand((e) => e.buildAllSQLs(ifNotExists: true, multiline: false)) + .toList(); + + var missingUniqueConstraintsSQLs = repositoriesChecksErrors + .where((e) => e.missingUniqueConstraints?.isNotEmpty ?? false) + .expand((e) => e.generateMissingUniqueConstraintsSQLs(this)) + .expand((e) => e.buildAllSQLs(ifNotExists: true, multiline: false)) + .toList(); + + var missingEnumConstraintsSQLs = repositoriesChecksErrors + .where((e) => e.missingEnumConstraints?.isNotEmpty ?? false) + .expand((e) => e.generateMissingEnumConstraintsSQLs(this)) + .expand((e) => e.buildAllSQLs(ifNotExists: true, multiline: false)) + .toList(); + + var missingEnumValuesSQLs = repositoriesChecksErrors + .where((e) => e.missingEnumValues?.isNotEmpty ?? false) + .expand((e) => e.generateMissingEnumValuesSQLs(this)) + .expand((e) => e.buildAllSQLs(ifNotExists: true, multiline: false)) + .toList(); var alterTablesSQLs = [ ...missingColumnsSQLs, @@ -728,16 +703,15 @@ abstract class DBSQLAdapter extends DBRelationalAdapter ...missingEnumValuesSQLs, ]; - alterTablesSQLs = - alterTablesSQLs.expandIndexed((i, s) { - if (i > 0 && s.isEmpty) { - var prev = alterTablesSQLs[i - 1]; - if (prev.isEmpty) { - return []; - } - } - return [s]; - }).toList(); + alterTablesSQLs = alterTablesSQLs.expandIndexed((i, s) { + if (i > 0 && s.isEmpty) { + var prev = alterTablesSQLs[i - 1]; + if (prev.isEmpty) { + return []; + } + } + return [s]; + }).toList(); while (alterTablesSQLs.isNotEmpty && alterTablesSQLs.first.isEmpty) { alterTablesSQLs.removeAt(0); @@ -799,8 +773,9 @@ abstract class DBSQLAdapter extends DBRelationalAdapter var repoFieldsNames = entityFields.whereNotIn(hiddenFields).toList(); - var repoFieldsMap = - repoFieldsNames.map((f) => MapEntry(f, f)).toMapFromEntries(); + var repoFieldsMap = repoFieldsNames + .map((f) => MapEntry(f, f)) + .toMapFromEntries(); var repoTable = getTableForEntityRepository(repository); @@ -809,16 +784,15 @@ abstract class DBSQLAdapter extends DBRelationalAdapter repoFieldsMap, ); - var schemesTypes = - schemeToRepoMap.entries - .map((e) { - var schemeField = e.key; - var repoField = e.value; - if (repoField == null) return null; - return MapEntry(repoField, scheme.fieldsTypes[schemeField]); - }) - .nonNulls - .toMapFromEntries(); + var schemesTypes = schemeToRepoMap.entries + .map((e) { + var schemeField = e.key; + var repoField = e.value; + if (repoField == null) return null; + return MapEntry(repoField, scheme.fieldsTypes[schemeField]); + }) + .nonNulls + .toMapFromEntries(); for (var e in schemesTypes.entries) { var field = e.key; @@ -838,58 +812,54 @@ abstract class DBSQLAdapter extends DBRelationalAdapter var fieldsInScheme = schemeToRepoMap.values.toList(); - var repoFieldsNotInScheme = - repoFieldsNames.whereNot((f) => fieldsInScheme.contains(f)).toList(); - - var referenceFields = - repoFieldsNotInScheme - .map( - (f) => _checkDBTableSchemeReferenceField( - entityHandler, - scheme, - repoTable, - f, - allowCollectionFields: false, - ), - ) - .nonNulls - .toMapFromEntries(); - - var collectionReferenceFields = - repoFieldsNotInScheme - .map( - (f) => _checkDBTableSchemeReferenceField( - entityHandler, - scheme, - repoTable, - f, - allowCollectionFields: true, - ), - ) - .nonNulls - .toMapFromEntries(); - - var missingReferenceColumns = - referenceFields.entries - .where((e) => e.value == null) - .map((e) => e.key) - .toList(); - - var missingCollectionReferenceColumns = - collectionReferenceFields.entries - .where((e) => e.value == null) - .map((e) => e.key) - .toList(); - - var missingColumns = - repoFieldsNames - .whereNot( - (f) => - fieldsInScheme.contains(f) || - referenceFields.containsKey(f) || - collectionReferenceFields.containsKey(f), - ) - .toList(); + var repoFieldsNotInScheme = repoFieldsNames + .whereNot((f) => fieldsInScheme.contains(f)) + .toList(); + + var referenceFields = repoFieldsNotInScheme + .map( + (f) => _checkDBTableSchemeReferenceField( + entityHandler, + scheme, + repoTable, + f, + allowCollectionFields: false, + ), + ) + .nonNulls + .toMapFromEntries(); + + var collectionReferenceFields = repoFieldsNotInScheme + .map( + (f) => _checkDBTableSchemeReferenceField( + entityHandler, + scheme, + repoTable, + f, + allowCollectionFields: true, + ), + ) + .nonNulls + .toMapFromEntries(); + + var missingReferenceColumns = referenceFields.entries + .where((e) => e.value == null) + .map((e) => e.key) + .toList(); + + var missingCollectionReferenceColumns = collectionReferenceFields.entries + .where((e) => e.value == null) + .map((e) => e.key) + .toList(); + + var missingColumns = repoFieldsNames + .whereNot( + (f) => + fieldsInScheme.contains(f) || + referenceFields.containsKey(f) || + collectionReferenceFields.containsKey(f), + ) + .toList(); var missingFieldReferenceConstraints = {}; var missingFieldReferenceIndexes = {}; @@ -906,137 +876,136 @@ abstract class DBSQLAdapter extends DBRelationalAdapter // REF CONSTRAINTS // - var fieldEntityTypes = - entityHandler - .getFieldsEntityTypes() - .entries - .map((e) { - var entityType = e.value.entityType; - if (entityType == null) return null; + var fieldEntityTypes = entityHandler + .getFieldsEntityTypes() + .entries + .map((e) { + var entityType = e.value.entityType; + if (entityType == null) return null; - var refRepository = repository.provider - .getEntityRepositoryByType(entityType); + var refRepository = repository.provider.getEntityRepositoryByType( + entityType, + ); - if (refRepository == null || - !refRepository.isSameEntityManager(repository)) { - return null; - } + if (refRepository == null || + !refRepository.isSameEntityManager(repository)) { + return null; + } - return MapEntry(e.key, entityType); - }) - .nonNulls - .toMapFromEntries(); + return MapEntry(e.key, entityType); + }) + .nonNulls + .toMapFromEntries(); if (fieldEntityTypes.isNotEmpty) { var fieldsReferencedTables = scheme.fieldsReferencedTables; - var fieldsReferences = - fieldsReferencedTables.entries - .map((e) { - var colum = e.key; - var field = schemeToRepoMap[colum] ?? colum; + var fieldsReferences = fieldsReferencedTables.entries + .map((e) { + var colum = e.key; + var field = schemeToRepoMap[colum] ?? colum; - var entityType = fieldEntityTypes[field]; - if (entityType == null) return null; + var entityType = fieldEntityTypes[field]; + if (entityType == null) return null; - var refTable = e.value.targetTable; - return MapEntry(field, refTable); - }) - .nonNulls - .toMapFromEntries(); + var refTable = e.value.targetTable; + return MapEntry(field, refTable); + }) + .nonNulls + .toMapFromEntries(); - missingFieldReferenceConstraints = - fieldEntityTypes.keys - .where((f) => !fieldsReferences.containsKey(f)) - .toSet(); + missingFieldReferenceConstraints = fieldEntityTypes.keys + .where((f) => !fieldsReferences.containsKey(f)) + .toSet(); if (!dialect.foreignKeyCreatesImplicitIndex) { - missingFieldReferenceIndexes = - fieldsReferencedTables.entries - .where((e) => e.value.indexName == null) - .map((e) => e.key) - .toSet(); + missingFieldReferenceIndexes = fieldsReferencedTables.entries + .where((e) => e.value.indexName == null) + .map((e) => e.key) + .toSet(); } } // RELATIONSHIP CONSTRAINTS // - var fieldListEntityTypes = - entityHandler - .getFieldsListEntityTypes() - .entries - .map((e) { - var entityType = e.value.entityType; - if (entityType == null) return null; + var fieldListEntityTypes = entityHandler + .getFieldsListEntityTypes() + .entries + .map((e) { + var entityType = e.value.entityType; + if (entityType == null) return null; - var refRepository = repository.provider - .getEntityRepositoryByType(entityType); + var refRepository = repository.provider.getEntityRepositoryByType( + entityType, + ); - if (refRepository == null || - !refRepository.isSameEntityManager(repository)) { - return null; - } + if (refRepository == null || + !refRepository.isSameEntityManager(repository)) { + return null; + } - return MapEntry(e.key, entityType); - }) - .nonNulls - .toMapFromEntries(); + return MapEntry(e.key, entityType); + }) + .nonNulls + .toMapFromEntries(); if (fieldListEntityTypes.isNotEmpty) { if (!dialect.foreignKeyCreatesImplicitIndex) { - missingRelationshipFieldReferenceIndexes = - collectionReferenceFields.entries - .expand((e) { - var relFieldRef = e.value; - if (relFieldRef == null) { - return <(String, String, TypeInfo)>[]; - } - - return [ - if (relFieldRef.sourceRelationshipFieldIndex == null) - ( - relFieldRef.relationshipTable, - relFieldRef.sourceRelationshipField, - relFieldRef.sourceFieldEntityType, - ), - if (relFieldRef.targetRelationshipFieldIndex == null) - ( - relFieldRef.relationshipTable, - relFieldRef.targetRelationshipField, - relFieldRef.targetFieldEntityType, - ), - ]; - }) - .nonNulls - .toSet(); + missingRelationshipFieldReferenceIndexes = collectionReferenceFields + .entries + .expand((e) { + var relFieldRef = e.value; + if (relFieldRef == null) { + return <(String, String, TypeInfo)>[]; + } + + return [ + if (relFieldRef.sourceRelationshipFieldIndex == null) + ( + relFieldRef.relationshipTable, + relFieldRef.sourceRelationshipField, + relFieldRef.sourceFieldEntityType, + ), + if (relFieldRef.targetRelationshipFieldIndex == null) + ( + relFieldRef.relationshipTable, + relFieldRef.targetRelationshipField, + relFieldRef.targetFieldEntityType, + ), + ]; + }) + .nonNulls + .toSet(); } } // UNIQUE CONSTRAINTS // - var uniqueConstraints = - scheme.constraints.whereType().toList(); + var uniqueConstraints = scheme.constraints + .whereType() + .toList(); var uniqueConstraintsFields = uniqueConstraints.toFields( fieldMap: schemeToRepoMap, ); - var entityUniqueFields = - entityHandler - .getAllFieldsWithEntityAnnotation( - null, - (a) => a.isUnique, - ) - .keys - .toList(); + var entityUniqueFields = entityHandler + .getAllFieldsWithEntityAnnotation( + null, + (a) => a.isUnique, + ) + .keys + .toList(); - missingUniqueConstraints = - entityUniqueFields.whereNotIn(uniqueConstraintsFields).toSet(); + missingUniqueConstraints = entityUniqueFields + .whereNotIn(uniqueConstraintsFields) + .toSet(); // ENUM CONSTRAINTS // - var enumConstraints = - scheme.constraints.whereType().toList(); + var enumConstraints = scheme.constraints + .whereType() + .toList(); var enumConstraintsFields = enumConstraints.toFields( fieldMap: schemeToRepoMap, @@ -1044,8 +1013,9 @@ abstract class DBSQLAdapter extends DBRelationalAdapter var entityEnumFields = entityHandler.getFieldsEnumTypes().keys.toList(); - missingEnumConstraints = - entityEnumFields.whereNotIn(enumConstraintsFields).toSet(); + missingEnumConstraints = entityEnumFields + .whereNotIn(enumConstraintsFields) + .toSet(); var entityFieldsNamesIndexes = entityHandler.fieldsNamesIndexes(); var entityFieldsNames = entityHandler.fieldsNames(); @@ -1245,8 +1215,9 @@ abstract class DBSQLAdapter extends DBRelationalAdapter Future, TableScheme?>> getRepositoriesSchemes() async { - var reposBlocks = - entityRepositories.splitBeforeIndexed((i, r) => i % 2 == 0).toList(); + var reposBlocks = entityRepositories + .splitBeforeIndexed((i, r) => i % 2 == 0) + .toList(); final allSchemes = , TableScheme?>{}; @@ -1255,16 +1226,15 @@ abstract class DBSQLAdapter extends DBRelationalAdapter final contextID = List.unmodifiable([this, "getRepositoriesSchemes"]); for (var block in reposBlocks) { - var repositorySchemes = - await block - .map( - (r) => MapEntry( - r, - getTableSchemeForEntityRepository(r, contextID: contextID), - ), - ) - .toMapFromEntries() - .resolveAllValues(); + var repositorySchemes = await block + .map( + (r) => MapEntry( + r, + getTableSchemeForEntityRepository(r, contextID: contextID), + ), + ) + .toMapFromEntries() + .resolveAllValues(); allSchemes.addAll(repositorySchemes); } @@ -1492,20 +1462,19 @@ abstract class DBSQLAdapter extends DBRelationalAdapter bool ifNotExists = true, bool sortColumns = true, bool verbose = false, - }) => - entityRepositories - .map( - (r) => MapEntry( - r, - generateCreateTableSQL( - entityRepository: r, - ifNotExists: ifNotExists, - sortColumns: sortColumns, - ), - ), - ) - .toHierarchicalOrder(verbose: verbose) - .toMapFromEntries(); + }) => entityRepositories + .map( + (r) => MapEntry( + r, + generateCreateTableSQL( + entityRepository: r, + ifNotExists: ifNotExists, + sortColumns: sortColumns, + ), + ), + ) + .toHierarchicalOrder(verbose: verbose) + .toMapFromEntries(); /// Generate all the SQLs to create the tables. @override @@ -1518,11 +1487,10 @@ abstract class DBSQLAdapter extends DBRelationalAdapter sortColumns: sortColumns, ); - var allSQLs = - sqls.values - .expand((e) => e.allSQLBuilders) - .toList() - .toHierarchicalOrder(); + var allSQLs = sqls.values + .expand((e) => e.allSQLBuilders) + .toList() + .toHierarchicalOrder(); return allSQLs; } @@ -1558,16 +1526,12 @@ abstract class DBSQLAdapter extends DBRelationalAdapter String fieldName, Object? value, Map fieldsValues, - ) => _resolveFieldValueToSQL( - context, - tableScheme, - fieldName, - value, - ).resolveMapped((refId) { - fieldsValues.putIfAbsent(fieldName, () => refId); - var valueSQL = _conditionSQLGenerator.parameterPlaceholder(fieldName); - return valueSQL; - }); + ) => _resolveFieldValueToSQL(context, tableScheme, fieldName, value) + .resolveMapped((refId) { + fieldsValues.putIfAbsent(fieldName, () => refId); + var valueSQL = _conditionSQLGenerator.parameterPlaceholder(fieldName); + return valueSQL; + }); FutureOr _resolveFieldValueToSQL( EncodingContext context, @@ -1601,10 +1565,9 @@ abstract class DBSQLAdapter extends DBRelationalAdapter var refEntityHandler = refEntityRepository.entityHandler; - var refId = - fieldRef != null - ? refEntityHandler.getField(refEntity, fieldRef.targetField) - : refEntityHandler.getID(refEntity); + var refId = fieldRef != null + ? refEntityHandler.getField(refEntity, fieldRef.targetField) + : refEntityHandler.getID(refEntity); if (refId != null) return refId; @@ -2121,18 +2084,14 @@ abstract class DBSQLAdapter extends DBRelationalAdapter ); } - var sqls = - otherIds.isEmpty - ? [SQL.dummy] - : otherIds - .map( - (otherId) => _generateInsertRelationshipSQL( - relationship, - id, - otherId, - ), - ) - .toList(); + var sqls = otherIds.isEmpty + ? [SQL.dummy] + : otherIds + .map( + (otherId) => + _generateInsertRelationshipSQL(relationship, id, otherId), + ) + .toList(); var constrainSQL = _generateConstrainRelationshipSQL( tableScheme, @@ -2297,37 +2256,32 @@ abstract class DBSQLAdapter extends DBRelationalAdapter return executeTransactionOperation(op, sqls.first, (connection) { _logTransactionOperationSQL('insertRelationshipSQLs', op, sqls); - var retInserts = - sqls.map((sql) { - var ret = doInsertRelationshipSQL( - entityName, - sql.mainTable ?? table, - sql, - op.transaction, - connection, - ); + var retInserts = sqls.map((sql) { + var ret = doInsertRelationshipSQL( + entityName, + sql.mainTable ?? table, + sql, + op.transaction, + connection, + ); - if (sql.hasPosSQL) { - return sql.posSQL! - .map((e) { - _logTransactionOperationSQL( - 'insertRelationship[POS]', - op, - e, - ); - return doDeleteSQL( - entityName, - e.mainTable!, - e, - op.transaction, - connection, - ); - }) - .resolveAllWithValue(ret); - } else { - return ret; - } - }).resolveAll(); + if (sql.hasPosSQL) { + return sql.posSQL! + .map((e) { + _logTransactionOperationSQL('insertRelationship[POS]', op, e); + return doDeleteSQL( + entityName, + e.mainTable!, + e, + op.transaction, + connection, + ); + }) + .resolveAllWithValue(ret); + } else { + return ret; + } + }).resolveAll(); return retInserts.resolveWithValue(true); }); } @@ -2563,14 +2517,13 @@ abstract class DBSQLAdapter extends DBRelationalAdapter if (isTransactionWithSingleOperation(op, sql)) { return executeWithPool( f, - onError: - (e, s) => transaction.notifyExecutionError( - e, - s, - errorResolver: resolveError, - operation: op, - debugInfo: () => sql.mainSQL.toString(), - ), + onError: (e, s) => transaction.notifyExecutionError( + e, + s, + errorResolver: resolveError, + operation: op, + debugInfo: () => sql.mainSQL.toString(), + ), ); } @@ -3052,7 +3005,13 @@ abstract class DBSQLAdapter extends DBRelationalAdapter sql.write(from); if (dialect.acceptsReturningSyntax) { - sql.write(' RETURNING "$tableAlias".*'); + // Some dialects (SQLite) reject a table-qualified wildcard: + // "RETURNING may not use TABLE.* wildcards". + if (dialect.returningAcceptsTableWildcard) { + sql.write(' RETURNING "$tableAlias".*'); + } else { + sql.write(' RETURNING *'); + } } return sql.toString(); @@ -3601,8 +3560,9 @@ class _DBTableCheck { sqlGenerator, ); - var constraints = - columnsSQLs.expand((e) => e.constraints ?? []).toList(); + var constraints = columnsSQLs + .expand((e) => e.constraints ?? []) + .toList(); return constraints; } @@ -3615,8 +3575,9 @@ class _DBTableCheck { sqlGenerator, ); - var indexes = - columnsSQLs.expand((e) => e.indexes ?? []).toList(); + var indexes = columnsSQLs + .expand((e) => e.indexes ?? []) + .toList(); return indexes; } @@ -3629,8 +3590,9 @@ class _DBTableCheck { sqlGenerator, ); - var indexes = - columnsSQLs.expand((e) => e.indexes ?? []).toList(); + var indexes = columnsSQLs + .expand((e) => e.indexes ?? []) + .toList(); return indexes; } @@ -3663,19 +3625,17 @@ class _DBTableCheck { return []; } - var sqls = - missing.map((f) { - var schemeName = - f is _DBRelationshipTableColumn - ? f.relationshipTable - : scheme.name; - return sqlGenerator.generateAddColumnAlterTableSQL( - schemeName, - f.name, - f.type, - entityFieldAnnotations: f.annotations, - ); - }).toList(); + var sqls = missing.map((f) { + var schemeName = f is _DBRelationshipTableColumn + ? f.relationshipTable + : scheme.name; + return sqlGenerator.generateAddColumnAlterTableSQL( + schemeName, + f.name, + f.type, + entityFieldAnnotations: f.annotations, + ); + }).toList(); return sqls; } @@ -3689,15 +3649,14 @@ class _DBTableCheck { return []; } - var sqls = - missing.map((f) { - return sqlGenerator.generateAddUniqueConstraintAlterTableSQL( - scheme.name, - f.name, - f.type, - entityFieldAnnotations: f.annotations, - ); - }).toList(); + var sqls = missing.map((f) { + return sqlGenerator.generateAddUniqueConstraintAlterTableSQL( + scheme.name, + f.name, + f.type, + entityFieldAnnotations: f.annotations, + ); + }).toList(); return sqls; } @@ -3711,15 +3670,14 @@ class _DBTableCheck { return []; } - var sqls = - missing.map((f) { - return sqlGenerator.generateAddEnumConstraintAlterTableSQL( - scheme.name, - f.name, - f.type, - entityFieldAnnotations: f.annotations, - ); - }).toList(); + var sqls = missing.map((f) { + return sqlGenerator.generateAddEnumConstraintAlterTableSQL( + scheme.name, + f.name, + f.type, + entityFieldAnnotations: f.annotations, + ); + }).toList(); return sqls; } @@ -3824,11 +3782,10 @@ class _DBTableColumn { entityHandler.getFieldType(null, fieldName, resolveFiledName: true) ?? TypeInfo.tString; - var annotations = - entityHandler - .getFieldEntityAnnotations(null, fieldName) - ?.whereType() - .toList(); + var annotations = entityHandler + .getFieldEntityAnnotations(null, fieldName) + ?.whereType() + .toList(); return _DBTableColumn(fieldName, type, annotations); } diff --git a/lib/src/bones_api_entity_db_sqlite.dart b/lib/src/bones_api_entity_db_sqlite.dart new file mode 100644 index 00000000..3a4ca12c --- /dev/null +++ b/lib/src/bones_api_entity_db_sqlite.dart @@ -0,0 +1,1370 @@ +import 'dart:typed_data'; + +import 'package:async_extension/async_extension.dart'; +import 'package:collection/collection.dart'; +import 'package:logging/logging.dart' as logging; +import 'package:sqlite3/sqlite3.dart'; +import 'package:statistics/statistics.dart'; + +import 'bones_api_condition_encoder.dart'; +import 'bones_api_entity.dart'; +import 'bones_api_entity_annotation.dart'; +import 'bones_api_entity_db.dart'; +import 'bones_api_entity_db_sql.dart'; +import 'bones_api_initializable.dart'; +import 'bones_api_logging.dart'; +import 'bones_api_sql_builder.dart'; +import 'bones_api_types.dart'; +import 'bones_api_utils.dart'; +import 'bones_api_utils_timedmap.dart'; + +final _log = logging.Logger('DBSQLiteAdapter')..registerAsDbLogger(); + +/// The `:memory:` path accepted by [DBSQLiteAdapter] to select an in-memory +/// database. +const String sqliteMemoryPath = ':memory:'; + +/// A SQLite adapter, backed by the `sqlite3` package (bundled SQLite, `dart:ffi`). +/// +/// Supports both file databases and in-memory databases (see [inMemory]). +/// +/// ## Single native handle +/// +/// The `sqlite3` driver is **synchronous** and SQLite allows a single writer at +/// a time. Two native handles in one isolate are a hazard rather than a benefit: +/// a handle blocking on a lock stalls the whole isolate, so the handle holding +/// the lock could never reach its `COMMIT` — `busy_timeout` would elapse and the +/// statement would fail with *"database is locked"*. And there is nothing to +/// gain, since a synchronous driver has no I/O wait to overlap. +/// +/// So this adapter opens exactly **one** [Database] and every pooled +/// [DBSQLiteConnectionWrapper] is a cheap view over it. This also makes +/// [inMemory] work without any URI/shared-cache trickery (each +/// `sqlite3.openInMemory()` would otherwise be a *separate* database, and the +/// bundled SQLite is compiled with `OMIT_SHARED_CACHE`), and it keeps the +/// [Pool] machinery intact — note that [Pool.catchFromPool] force-creates +/// elements beyond [maxConnections] under contention, so capping the pool at 1 +/// would *not* have been enough to guarantee a single handle. +/// +/// The trade-off is that a statement issued outside the current transaction +/// while one is open participates in it, and is committed or rolled back with +/// it. In a single isolate that can only happen for genuinely interleaved work. +/// A transaction started while another is already open nests as a `SAVEPOINT` +/// rather than failing on a second `BEGIN`. +class DBSQLiteAdapter extends DBSQLAdapter + implements WithRuntimeTypeNameSafe { + @override + String get runtimeTypeNameSafe => 'DBSQLiteAdapter'; + + static bool _boot = false; + + static void boot() { + if (_boot) return; + _boot = true; + + DBSQLAdapter.boot(); + + Transaction.registerErrorFilter( + (e, s) => e is SqliteException || e is _SQLiteStatementException, + ); + + DBSQLAdapter.registerAdapter( + ['sql.sqlite', 'sql.sqlite3', 'sqlite', 'sqlite3'], + DBSQLiteAdapter, + _instantiate, + ); + } + + static FutureOr _instantiate( + config, { + int? minConnections, + int? maxConnections, + EntityRepositoryProvider? parentRepositoryProvider, + String? workingPath, + }) { + try { + return DBSQLiteAdapter.fromConfig( + config, + parentRepositoryProvider: parentRepositoryProvider, + workingPath: workingPath, + ); + } catch (e, s) { + _log.severe("Error instantiating from config", e, s); + return null; + } + } + + /// The database file path, or [sqliteMemoryPath] for an in-memory database. + final String databasePath; + + /// If `true` this is an in-memory database (not backed by a file). + final bool inMemory; + + /// The `busy_timeout` (in milliseconds) applied to the database handle. + /// + /// Only guards against *other OS processes* holding the database file: this + /// adapter uses a single handle, so it can never contend with itself. + final int busyTimeout; + + /// The single native handle shared by every pooled connection wrapper. + Database? _database; + + DBSQLiteAdapter( + String databasePath, { + int? busyTimeout, + int minConnections = 1, + int maxConnections = 3, + super.generateTables, + super.checkTables, + super.populateTables, + super.populateSource, + super.populateSourceVariables, + super.parentRepositoryProvider, + super.workingPath, + super.logSQL, + }) : databasePath = databasePath, + inMemory = _isMemoryPath(databasePath), + busyTimeout = busyTimeout ?? 5000, + super( + 'sqlite', + minConnections, + maxConnections, + DBSQLAdapterCapability( + dialect: const SQLDialect( + 'SQLite', + elementQuote: '"', + acceptsReturningSyntax: true, + // SQLite rejects `RETURNING "alias".*`: + // "RETURNING may not use TABLE.* wildcards". + returningAcceptsTableWildcard: false, + acceptsInsertDefaultValues: true, + // `INSERT IGNORE` is MySQL-only; SQLite uses `ON CONFLICT`: + acceptsInsertIgnore: false, + acceptsInsertOnConflict: true, + acceptsVarcharWithoutMaximumSize: true, + // SQLite does NOT create an implicit index for a foreign key: + foreignKeyCreatesImplicitIndex: false, + createIndexIfNotExists: true, + // SQLite can't parse an `OFFSET` without a preceding `LIMIT`, + // and uses `LIMIT -1` as "no limit": + offsetRequiresLimit: true, + offsetMaxLimitValue: '-1', + ), + transactions: true, + transactionAbort: true, + tableSQL: true, + // SQLite can't `ALTER TABLE ... ADD CONSTRAINT`: + constraintSupport: false, + // A `sqlite3` handle can't be shared with another isolate: + multiIsolateSupport: false, + connectivity: DBAdapterCapabilityConnectivity.none, + ), + ) { + boot(); + + parentRepositoryProvider?.notifyKnownEntityRepositoryProvider(this); + } + + static bool _isMemoryPath(String path) { + path = path.trim(); + return path.isEmpty || + path == sqliteMemoryPath || + path.toLowerCase() == 'memory'; + } + + factory DBSQLiteAdapter.fromConfig( + Map? config, { + String? defaultDatabasePath, + EntityRepositoryProvider? parentRepositoryProvider, + String? workingPath, + }) { + boot(); + + var memory = config?['memory'] ?? config?['inMemory']; + + String? path = + (config?['path'] ?? + config?['file'] ?? + config?['database'] ?? + config?['db'] ?? + defaultDatabasePath) + ?.toString(); + + if (memory == true || (path == null && memory != false)) { + path = sqliteMemoryPath; + } + + if (path == null) throw ArgumentError.notNull('path'); + + int? busyTimeout = + config?['busyTimeout'] ?? + config?['busy-timeout'] ?? + config?['busy_timeout']; + + var (generateTables: generateTables, checkTables: checkTables) = + DBSQLAdapter.parseConfigDBGenerateTablesAndCheckTables(config); + + var populate = config?['populate']; + Object? populateTables; + Object? populateSource; + Object? populateSourceVariables; + + if (populate is Map) { + populateTables = populate['tables']; + populateSource = populate['source']; + populateSourceVariables = populate['variables']; + } + + var logSql = DBSQLAdapter.parseConfigLogSQL(config) ?? false; + + return DBSQLiteAdapter( + path, + busyTimeout: busyTimeout, + generateTables: generateTables, + checkTables: checkTables, + populateTables: populateTables, + populateSource: populateSource, + populateSourceVariables: populateSourceVariables, + parentRepositoryProvider: parentRepositoryProvider, + workingPath: workingPath, + logSQL: logSql, + ); + } + + @override + List initializeDependencies() { + var parentRepositoryProvider = this.parentRepositoryProvider; + return [ + if (parentRepositoryProvider != null) parentRepositoryProvider, + ]; + } + + @override + SQLDialect get dialect => super.dialect as SQLDialect; + + @override + String getConnectionURL(DBSQLiteConnectionWrapper connection) => + 'sqlite://$databasePath'; + + int _connectionCount = 0; + + @override + DBSQLiteConnectionWrapper createConnection() { + var count = ++_connectionCount; + + var connWrapper = _DBSQLiteConnectionWrapped(_openDatabase(), this); + + _log.info( + 'createConnection[#$count $poolAliveElementsSize/$maxConnections]> ' + '${getConnectionURL(connWrapper)}', + ); + + return connWrapper; + } + + /// Opens the shared handle on first use, then returns it. + Database _openDatabase() { + var db = _database; + if (db != null) return db; + + db = _database = inMemory + ? sqlite3.openInMemory() + : sqlite3.open(databasePath); + + // Foreign keys are OFF by default and are a per-connection setting. + db.execute('PRAGMA foreign_keys = ON'); + + if (!inMemory) { + db.execute('PRAGMA busy_timeout = $busyTimeout'); + // WAL lets readers proceed while another *process* writes. + db.execute('PRAGMA journal_mode = WAL'); + db.execute('PRAGMA synchronous = NORMAL'); + } + + return db; + } + + /// Runs [sql] directly on the database handle, bypassing the entity layer. + /// + /// Useful for maintenance statements (`VACUUM`, `PRAGMA`, `ANALYZE`) and for + /// test harnesses that need to reach an [inMemory] database, which is + /// otherwise only accessible through this adapter's handle. + ResultSet executeRawSQL(String sql, [List? parameters]) { + checkNotClosed(); + return _openDatabase().select(sql, _normalizeParameters(parameters)); + } + + /// The names of the tables in this database (excluding SQLite's internal + /// `sqlite_*` tables). + List listTablesNames() => executeRawSQL( + "SELECT name FROM sqlite_master " + "WHERE type = 'table' AND name NOT LIKE 'sqlite_%'", + ).map((r) => r['name'].toString()).toList(); + + @override + bool closeConnection(DBSQLiteConnectionWrapper connection) { + _log.info('closeConnection> $connection'); + // Only marks the wrapper closed: the native handle is shared and is + // disposed by [close]. + try { + connection.close(); + } catch (_) {} + return true; + } + + @override + bool close() { + if (!super.close()) return false; + + var db = _database; + if (db != null) { + _database = null; + try { + db.close(); + } catch (_) {} + } + + return true; + } + + @override + bool isPoolElementValid( + DBSQLiteConnectionWrapper o, { + bool checkUsage = true, + }) => isConnectionValid(o, checkUsage: checkUsage); + + @override + FutureOr isPoolElementInvalid( + DBSQLiteConnectionWrapper o, { + bool checkUsage = true, + }) => !isConnectionValid(o, checkUsage: checkUsage); + + @override + bool isConnectionValid( + DBSQLiteConnectionWrapper connection, { + bool checkUsage = true, + }) { + if (connection.isClosed) return false; + + if (checkUsage && connection.isInactive(connectionInactivityLimit)) { + return false; + } + + return true; + } + + @override + DBSQLiteConnectionWrapper? recyclePoolElement(DBSQLiteConnectionWrapper o) { + // A transaction wrapper is bound to an open transaction: never recycle it. + if (o is _DBSQLiteConnectionTransaction) return null; + return o; + } + + // ------------------------------------------------------------------------- + // Scheme introspection (SQLite `PRAGMA`s, there is no `information_schema`). + // ------------------------------------------------------------------------- + + @override + FutureOr?> getTableFieldsTypesImpl(String table) => + executeWithPool((connection) { + _log.info('getTableFieldsTypesImpl> $table'); + + var columns = _tableInfo(connection, table); + if (columns.isEmpty) return null; + + return _fieldsTypes(columns); + }); + + List _tableInfo(DBSQLiteConnectionWrapper connection, String table) { + // `table_info` accepts the table name as a bound parameter only through the + // table-valued form (`pragma_table_info`), which is what allows quoting it + // safely here: + return connection.select('SELECT * FROM pragma_table_info(?)', [ + table, + ]).toList(); + } + + Map _fieldsTypes(List columns) { + return Map.fromEntries( + columns.map((c) { + var name = c['name'] as String; + var type = _toFieldType((c['type'] ?? '').toString()); + return MapEntry(name, type); + }), + ); + } + + @override + FutureOr getTableSchemeImpl( + String table, + TableRelationshipReference? relationship, { + Object? contextID, + }) => executeWithPool((connection) { + var columns = _tableInfo(connection, table); + if (columns.isEmpty) return null; + + var idFieldName = _findIDField(table, columns); + + var fieldsTypes = _fieldsTypes(columns); + + notifyTableFieldTypes(table, fieldsTypes); + + var fieldsReferencedTablesRet = _findFieldsReferencedTables( + connection, + table, + contextID: contextID, + ); + + return fieldsReferencedTablesRet.resolveMapped((fieldsReferencedTables) { + var relationshipTablesRet = _findRelationshipTables( + connection, + table, + contextID: contextID, + ); + + return relationshipTablesRet.resolveMapped((relationshipTables) { + var tableScheme = TableScheme( + table, + relationship: relationship != null, + idFieldName: idFieldName, + fieldsTypes: fieldsTypes, + fieldsReferencedTables: fieldsReferencedTables, + relationshipTables: relationshipTables, + ); + + _log.info('$tableScheme'); + + return tableScheme; + }); + }); + }); + + String _findIDField(String table, List columns) { + var primaryFieldsNames = columns + .where((c) => (c['pk'] as int? ?? 0) > 0) + .sorted((a, b) => (a['pk'] as int).compareTo(b['pk'] as int)) + .map((c) => c['name'].toString()) + .toList(); + + return selectIDFieldName(table, primaryFieldsNames); + } + + static final RegExp _regExpSpaces = RegExp(r'\s+'); + static final RegExp _regExpIgnoreWords = RegExp( + r'unsigned|signed|varying|precision|native|\(.*?\)', + ); + + Type _toFieldType(String dataType) { + dataType = dataType.toLowerCase(); + dataType = dataType.replaceAll(_regExpIgnoreWords, ' '); + dataType = dataType.replaceAll(_regExpSpaces, ' '); + dataType = dataType.trim(); + + switch (dataType) { + case 'boolean': + case 'bool': + return bool; + case 'tinyint': + case 'smallint': + case 'mediumint': + case 'int': + case 'int2': + case 'int8': + case 'bigint': + case 'integer': + return int; + case 'decimal': + case 'numeric': + return Decimal; + case 'float': + case 'double': + case 'real': + return double; + case 'blob': + return Uint8List; + case 'time': + return Time; + case 'timestamp': + case 'date': + case 'datetime': + return DateTime; + case 'text': + case 'char': + case 'varchar': + case 'clob': + case 'enum': + return String; + default: + // SQLite keeps the declared type verbatim; fall back to its affinity + // rules (https://sqlite.org/datatype3.html#determination_of_column_affinity). + if (dataType.contains('int')) return int; + if (dataType.contains('char') || + dataType.contains('clob') || + dataType.contains('text')) { + return String; + } + if (dataType.contains('blob')) return Uint8List; + if (dataType.contains('real') || + dataType.contains('floa') || + dataType.contains('doub')) { + return double; + } + return String; + } + } + + final Expando>> _listTablesNamesContextCache = + Expando(); + + FutureOr> _listTablesNames( + DBSQLiteConnectionWrapper connection, { + Object? contextID, + }) => _listTablesNamesContextCache.putIfAbsentAsync( + contextID, + () => _listTablesNamesImpl(connection), + ); + + List _listTablesNamesImpl(DBSQLiteConnectionWrapper connection) { + var results = connection.select( + "SELECT name FROM sqlite_master " + "WHERE type = 'table' AND name NOT LIKE 'sqlite_%'", + ); + + return results.map((r) => r['name'].toString()).toList(); + } + + final TimedMap> + _findFieldsReferencedTablesCache = + TimedMap>(Duration(seconds: 30)); + + final Expando>>> + _findFieldsReferencedTablesContextCache = Expando(); + + FutureOr> _findFieldsReferencedTables( + DBSQLiteConnectionWrapper connection, + String table, { + Object? contextID, + }) { + if (contextID != null) { + var cache = _findFieldsReferencedTablesContextCache[contextID] ??= {}; + var cached = cache[table]; + if (cached != null) return cached; + + var ret = _findFieldsReferencedTablesImpl(connection, table); + cache[table] = ret; + + return ret.resolveMapped((r) { + cache[table] = r; + _findFieldsReferencedTablesCache[table] = r; + return r; + }); + } + + return _findFieldsReferencedTablesCache.putIfAbsentCheckedAsync( + table, + () => _findFieldsReferencedTablesImpl(connection, table), + ); + } + + FutureOr> _findFieldsReferencedTablesImpl( + DBSQLiteConnectionWrapper connection, + String table, + ) { + var results = connection.select( + 'SELECT * FROM pragma_foreign_key_list(?)', + [table], + ); + + var mapEntriesRet = results + .map?>>((r) { + var sourceField = r['from']?.toString(); + var targetTable = r['table']?.toString(); + var targetField = r['to']?.toString(); + + if (sourceField == null || targetTable == null) { + return null as FutureOr?>; + } + + // `PRAGMA foreign_key_list` reports a `null` target column when + // the reference points at the target's PRIMARY KEY implicitly. + FutureOr targetFieldRet = + targetField ?? + getTableScheme(targetTable).resolveMapped((s) => s?.idFieldName); + + return targetFieldRet.resolveMapped< + MapEntry? + >((targetField) { + if (targetField == null) return null; + + var sourceFieldsTypesRet = getTableFieldsTypes(table); + var targetFieldsTypesRet = getTableFieldsTypes(targetTable); + + return sourceFieldsTypesRet.resolveBoth(targetFieldsTypesRet, ( + sourceFieldsTypes, + targetFieldsTypes, + ) { + var sourceFieldType = sourceFieldsTypes?[sourceField] ?? String; + var targetFieldType = targetFieldsTypes?[targetField] ?? String; + + return MapEntry( + sourceField, + TableFieldReference( + table, + sourceField, + sourceFieldType, + targetTable, + targetField, + targetFieldType, + ), + ); + }); + }); + }) + .toList() + .resolveAll(); + + return mapEntriesRet.resolveMapped( + (mapEntries) => + Map.fromEntries(mapEntries.nonNulls), + ); + } + + FutureOr> _findRelationshipTables( + DBSQLiteConnectionWrapper connection, + String table, { + Object? contextID, + }) { + var tablesNamesRet = _listTablesNames(connection, contextID: contextID); + + return tablesNamesRet.resolveMapped((tablesNames) { + var tablesReferencesRet = tablesNames + .map( + (t) => _findFieldsReferencedTables( + connection, + t, + contextID: contextID, + ), + ) + .toList() + .resolveAll(); + + return tablesReferencesRet.resolveMapped((tablesReferences) { + var candidates = tablesReferences.where((m) { + return m.length > 1 && + m.values.where((r) => r.targetTable == table).isNotEmpty && + m.values.where((r) => r.targetTable != table).isNotEmpty; + }).toList(); + + return candidates + .map((e) { + var refToTables = e.values + .where((r) => r.targetTable == table) + .toList(growable: false); + + var otherRefs = e.values + .where((r) => r.targetTable != table) + .toList(growable: false); + + if (refToTables.length != 1 || otherRefs.length != 1) { + return null; + } + + var refToTable = refToTables.first; + var otherRef = otherRefs.first; + + return TableRelationshipReference( + refToTable.sourceTable, + refToTable.targetTable, + refToTable.targetField, + refToTable.targetFieldType, + refToTable.sourceField, + otherRef.targetTable, + otherRef.targetField, + otherRef.targetFieldType, + otherRef.sourceField, + ); + }) + .nonNulls + .toList(); + }); + }); + } + + // ------------------------------------------------------------------------- + // Type mapping. + // ------------------------------------------------------------------------- + + // `typeToSQLType` and `foreignKeyTypeToSQLType` are deliberately NOT + // overridden. SQLite has no fixed set of type names — a declared type only + // selects a storage *affinity* — and every name the base `SQLGenerator` + // emits maps to the right one: `INT`/`BIGINT` -> INTEGER, `VARCHAR` -> TEXT, + // `BLOB` -> BLOB, `BOOLEAN`/`DECIMAL` -> NUMERIC, and `TIMESTAMP`/`TIME` -> + // NUMERIC, which keeps their non-numeric ISO-8601 text as TEXT. + // + // The one place a literal type name is load-bearing is the primary key: + + @override + String? primaryKeyTypeToSQLType( + Type type, { + List? entityFieldAnnotations, + }) { + // SQLite has no `SERIAL`: an auto-incrementing primary key must be declared + // exactly as `INTEGER PRIMARY KEY [AUTOINCREMENT]`. Note that `SERIAL` is + // silently *accepted* by SQLite (unknown type names get NUMERIC affinity) + // but would leave the ID `NULL` on every insert. + if (type.isNumericOrDynamicNumberType) { + return 'INTEGER PRIMARY KEY AUTOINCREMENT'; + } + + return super.primaryKeyTypeToSQLType( + type, + entityFieldAnnotations: entityFieldAnnotations, + ); + } + + @override + MapEntry>? enumTypeToSQLType( + Type type, + String column, { + List? entityFieldAnnotations, + }) { + var enumType = super.enumTypeToSQLType( + type, + column, + entityFieldAnnotations: entityFieldAnnotations, + ); + + if (enumType == null) return null; + + // SQLite has no `ENUM` type: emulate it with a `CHECK` constraint. + var values = enumType.value; + return MapEntry(values.isEmpty ? 'VARCHAR' : 'VARCHAR CHECK', values); + } + + // ------------------------------------------------------------------------- + // Parameter binding. + // + // `sqlite3` only binds `int`, `double`, `String`, `List` and `null`, so + // the richer values that reach an adapter need to be normalized here. This is + // the single choke point for every statement executed by this adapter. + // ------------------------------------------------------------------------- + + static List _normalizeParameters(List? values) { + if (values == null || values.isEmpty) return const []; + return values.map(_normalizeParameter).toList(growable: false); + } + + static Object? _normalizeParameter(Object? value) { + if (value == null || value is int || value is double || value is String) { + return value; + } + + if (value is bool) return value ? 1 : 0; + if (value is Uint8List) return value; + if (value is DateTime) return value.toUtc().toIso8601String(); + if (value is Time) return value.toString(); + if (value is Enum) return value.name; + if (value is BigInt) { + return value.isValidInt ? value.toInt() : value.toString(); + } + if (value is DynamicInt) { + var n = value.toBigInt(); + return n.isValidInt ? n.toInt() : n.toString(); + } + if (value is Decimal) return value.toDouble(); + if (value is List) return value; + + return value.toString(); + } + + // ------------------------------------------------------------------------- + // Statement execution. + // ------------------------------------------------------------------------- + + /// Runs [sql] on [connection], tagging any [SqliteException] with the [SQL] + /// that caused it so [resolveError] can name the offending value. + ResultSet _select(DBSQLiteConnectionWrapper connection, SQL sql) { + try { + return connection.select( + sql.sqlPositional, + sql.parametersValuesByPosition, + ); + } on SqliteException catch (e, s) { + Error.throwWithStackTrace(_SQLiteStatementException(e, sql), s); + } + } + + @override + FutureOr executeTableSQL(String createTableSQL) => + executeWithPool((connection) { + try { + connection.execute(createTableSQL); + return true; + } catch (e, s) { + _log.severe("Error executing table SQL:\n$createTableSQL", e, s); + return false; + } + }); + + @override + FutureOr doCountSQL( + String entityName, + String table, + SQL sql, + Transaction transaction, + DBSQLiteConnectionWrapper connection, + ) { + var results = _select(connection, sql); + + var count = results.map((r) => r.values.firstOrNull).firstOrNull ?? 0; + + return count is int ? count : int.tryParse(count.toString().trim()) ?? 0; + } + + @override + FutureOr> doExistIDsSQL( + String entityName, + String table, + SQL sql, + Transaction transaction, + DBSQLiteConnectionWrapper connection, + ) { + if (sql.isDummy) return []; + + var results = _select(connection, sql); + + return parseIDs(results.map((r) => r['id'])); + } + + @override + FutureOr>> doSelectSQL( + String entityName, + String table, + SQL sql, + Transaction transaction, + DBSQLiteConnectionWrapper connection, + ) { + if (sql.isDummy) return >[]; + + var results = _select(connection, sql); + + return _resultsToEntitiesMaps(results); + } + + /// `Row` is an unmodifiable `Map` view over the `ResultSet`: copy it, since + /// the framework mutates the returned entity maps. + static List> _resultsToEntitiesMaps(ResultSet? results) { + if (results == null) return >[]; + return results.map((r) => Map.from(r)).toList(); + } + + @override + FutureOr>> doDeleteSQL( + String entityName, + String table, + SQL sql, + Transaction transaction, + DBSQLiteConnectionWrapper connection, + ) { + if (sql.isFullyDummy) return >[]; + + var preSQLs = sql.preSQL; + if (preSQLs != null) { + for (var e in preSQLs) { + _select(connection, e); + } + } + + ResultSet? results; + if (!sql.isDummy) { + results = _select(connection, sql); + } + + var posSQLs = sql.posSQL; + if (posSQLs != null) { + var posResults = posSQLs.map((e) => _select(connection, e)).toList(); + + var idx = sql.posSQLReturnIndex; + if (idx != null) results = posResults[idx]; + } + + return _resultsToEntitiesMaps(results); + } + + @override + FutureOr doInsertSQL( + String entityName, + String table, + SQL sql, + Transaction transaction, + DBSQLiteConnectionWrapper connection, + ) { + if (sql.isDummy) return null; + + var results = _select(connection, sql); + + return _resolveResultID(connection, results, sql); + } + + @override + FutureOr doUpdateSQL( + String entityName, + String table, + SQL sql, + Object id, + Transaction transaction, + DBSQLiteConnectionWrapper connection, { + bool allowAutoInsert = false, + }) { + if (sql.isDummy) return id; + + var results = _select(connection, sql); + + var affectedRows = connection.updatedRows; + + if (affectedRows == 0 && results.isEmpty) { + var entry = sql.parametersByPlaceholder; + if (!allowAutoInsert) { + throw StateError( + "Can't update not stored entity into table `$table`: $entry", + ); + } + + var fields = sql.namedParameters!; + + return generateInsertSQL( + transaction, + entityName, + table, + fields, + ).resolveMapped((insertSQL) { + _log.info('Update not affecting any row! Auto inserting: $insertSQL'); + return doInsertSQL( + entityName, + table, + insertSQL, + transaction, + connection, + ); + }); + } + + return _resolveResultID(connection, results, sql, id); + } + + dynamic _resolveResultID( + DBSQLiteConnectionWrapper connection, + ResultSet results, + SQL sql, [ + Object? entityId, + ]) { + if (entityId != null) return entityId; + + var returning = results.firstOrNull; + + if (returning != null && returning.isNotEmpty) { + if (returning.length == 1) { + return returning.values.first; + } + + var idFieldName = sql.idFieldName; + if (idFieldName != null) { + var id = returning[idFieldName]; + if (id != null) return id; + } + + return returning.values.first; + } + + // No `RETURNING` row (e.g. a table with a non-`rowid` primary key): + var lastInsertRowId = connection.lastInsertRowId; + return lastInsertRowId != 0 ? lastInsertRowId : null; + } + + @override + Object resolveError( + Object error, + StackTrace stackTrace, + Object? operation, + Object? previousError, + ) { + if (error is DBSQLiteAdapterException) { + return error; + } + + SQL? failedSQL; + if (error is _SQLiteStatementException) { + failedSQL = error.sql; + error = error.error; + } + + if (error is SqliteException) { + var extendedCode = error.extendedResultCode; + + // SQLITE_CONSTRAINT_UNIQUE / SQLITE_CONSTRAINT_PRIMARYKEY: + if (extendedCode == 2067 || extendedCode == 1555) { + var (tableName, fieldName) = _parseConstraintTableField(error.message); + + return EntityFieldInvalid( + "unique", + _invalidValue(failedSQL, fieldName, error), + tableName: tableName, + fieldName: fieldName, + parentError: error, + parentStackTrace: stackTrace, + previousError: previousError, + operation: operation, + ); + } + + // SQLITE_CONSTRAINT_FOREIGNKEY: + if (extendedCode == 787) { + return DBSQLiteAdapterException( + 'delete.constraint', + error.message, + parentError: error, + parentStackTrace: stackTrace, + previousError: previousError, + operation: operation, + ); + } + + // SQLITE_CONSTRAINT_NOTNULL: + if (extendedCode == 1299) { + var (tableName, fieldName) = _parseConstraintTableField(error.message); + + return EntityFieldInvalid( + "null", + _invalidValue(failedSQL, fieldName, error), + tableName: tableName, + fieldName: fieldName, + parentError: error, + parentStackTrace: stackTrace, + previousError: previousError, + operation: operation, + ); + } + + // SQLITE_CONSTRAINT_CHECK: a value outside an emulated `ENUM`. + // Note SQLite's CHECK message quotes the expression + // ("CHECK constraint failed: t IN ('a','b')"), not `table.column`, so + // `_parseConstraintTableField` yields nulls here. + if (extendedCode == 275) { + var (tableName, fieldName) = _parseConstraintTableField(error.message); + + return EntityFieldInvalid( + "check", + error.message, + tableName: tableName, + fieldName: fieldName, + parentError: error, + parentStackTrace: stackTrace, + previousError: previousError, + ); + } + } + + return DBSQLiteAdapterException( + 'error', + '$error', + parentError: error, + parentStackTrace: stackTrace, + previousError: previousError, + operation: operation, + ); + } + + /// SQLite constraint messages name the offending columns as + /// `UNIQUE constraint failed: table.column[, table.column]`. + static final RegExp _regExpConstraintField = RegExp( + r'constraint failed:\s*(\w+)\.(\w+)', + caseSensitive: false, + ); + + static (String?, String?) _parseConstraintTableField(String message) { + var match = _regExpConstraintField.firstMatch(message); + if (match == null) return (null, null); + return (match[1], match[2]); + } + + /// The value that violated a constraint. + /// + /// Unlike PostgreSQL (`Key (email)=(joe@…) already exists`) and MySQL + /// (`Duplicate entry 'joe@…' for key …`), SQLite's message only names + /// `table.column`, so the value is recovered from the statement's bound + /// parameters (keyed by field name). Falls back to the driver message. + static Object? _invalidValue( + SQL? sql, + String? fieldName, + SqliteException error, + ) { + if (sql != null && fieldName != null) { + var parameters = sql.parametersByPlaceholder; + if (parameters.containsKey(fieldName)) return parameters[fieldName]; + } + return error.message; + } + + // ------------------------------------------------------------------------- + // Transactions. + // ------------------------------------------------------------------------- + + @override + Future openTransaction(Transaction transaction) { + var contextCompleter = Completer(); + + var result = executeWithPool( + (connection) { + return connection.openTransaction((transactionWrap) { + contextCompleter.complete(transactionWrap); + + return _runTransaction(transaction, connection); + }); + }, + validator: (c) => !transaction.isAborted, + onError: (e, s) => transaction.notifyExecutionError( + e, + s, + errorResolver: resolveError, + debugInfo: () => transaction.toString(withExecutedOperations: false), + ), + ); + + transaction.transactionResult = result; + + return contextCompleter.future; + } + + /// Unlike the PostgreSQL/MySQL drivers, SQLite has no `runTx` helper that + /// commits or rolls back around a callback, so the `COMMIT`/`ROLLBACK` is + /// driven by [_DBSQLiteConnectionWrapped.openTransaction] and this must let + /// the error escape to it. + /// + /// Note that [resolveTransactionResult] is what throws on an aborted + /// transaction (since [throwTransactionResultWithError] is `true`), so it has + /// to be inside the same `try` as [Transaction.transactionFuture] — a + /// `.then(onValue, onError:)` pair would not catch it. + Future _runTransaction( + Transaction transaction, + DBSQLiteConnectionWrapper connection, + ) async { + var res = await transaction.transactionFuture; + return await resolveTransactionResult(res, transaction, connection); + } + + @override + bool get cancelTransactionResultWithError => false; + + @override + bool get throwTransactionResultWithError => true; + + @override + bool cancelTransaction( + Transaction transaction, + DBSQLiteConnectionWrapper? connection, + Object? error, + StackTrace? stackTrace, + ) { + // The `ROLLBACK` is issued by `_DBSQLiteConnectionWrapped.openTransaction` + // when the transaction block throws. + return true; + } + + @override + bool get callCloseTransactionRequired => false; + + @override + FutureOr closeTransaction( + Transaction transaction, + DBSQLiteConnectionWrapper? connection, + ) {} + + @override + String toString() { + var closedStr = isClosed ? ', closed' : ''; + var modeStr = inMemory ? 'memory' : databasePath; + return 'DBSQLiteAdapter#$instanceID{$modeStr$closedStr}'; + } +} + +/// A [DBSQLiteAdapter] connection wrapper. +abstract class DBSQLiteConnectionWrapper extends DBConnectionWrapper { + DBSQLiteConnectionWrapper(super.nativeConnection); + + /// Runs [sql] and returns its rows. + ResultSet select(String sql, [List? parameters]); + + /// Runs [sql] discarding any rows. + void execute(String sql, [List? parameters]); + + /// The `rowid` of the most recent successful insert. + int get lastInsertRowId; + + /// The number of rows changed by the most recent statement. + int get updatedRows; + + /// Runs [queryBlock] inside a SQL transaction. + Future openTransaction( + Future Function(DBSQLiteConnectionWrapper connectionTransaction) queryBlock, + ); + + bool _nativeClosed = false; + + @override + bool isClosedImpl() => _nativeClosed; + + /// Only marks this wrapper closed: the native [Database] is shared by every + /// wrapper and is owned (and disposed) by [DBSQLiteAdapter]. + @override + void closeImpl() { + _nativeClosed = true; + } +} + +class _DBSQLiteConnectionWrapped extends DBSQLiteConnectionWrapper { + final DBSQLiteAdapter _adapter; + + _DBSQLiteConnectionWrapped(super.nativeConnection, this._adapter); + + @override + String get connectionURL => 'sqlite://${_adapter.databasePath}'; + + @override + ResultSet select(String sql, [List? parameters]) { + updateLastAccessTime(); + return nativeConnection.select( + sql, + DBSQLiteAdapter._normalizeParameters(parameters), + ); + } + + @override + void execute(String sql, [List? parameters]) { + updateLastAccessTime(); + nativeConnection.execute( + sql, + DBSQLiteAdapter._normalizeParameters(parameters), + ); + } + + @override + int get lastInsertRowId => nativeConnection.lastInsertRowId; + + @override + int get updatedRows => nativeConnection.updatedRows; + + static int _savePointCount = 0; + + @override + Future openTransaction( + Future Function(DBSQLiteConnectionWrapper connectionTransaction) queryBlock, + ) async { + updateLastAccessTime(); + + // Every wrapper shares one native handle, so a plain `BEGIN` issued while + // another transaction is already open would fail with "cannot start a + // transaction within a transaction". `SAVEPOINT` nests, so use it whenever + // the handle is already in a transaction (`autocommit` is `false`). + var nested = !nativeConnection.autocommit; + var savePoint = nested ? 'bones_api_sp_${++_savePointCount}' : null; + + if (savePoint != null) { + nativeConnection.execute('SAVEPOINT "$savePoint"'); + } else { + // `IMMEDIATE` takes the write lock upfront: a deferred transaction would + // only try to upgrade on its first write, which can fail mid-transaction + // when another *process* holds the database file. + nativeConnection.execute(_adapter.inMemory ? 'BEGIN' : 'BEGIN IMMEDIATE'); + } + + try { + var result = await queryBlock(_DBSQLiteConnectionTransaction(this)); + + nativeConnection.execute( + savePoint != null ? 'RELEASE "$savePoint"' : 'COMMIT', + ); + + return result; + } catch (_) { + try { + if (savePoint != null) { + nativeConnection + ..execute('ROLLBACK TO "$savePoint"') + ..execute('RELEASE "$savePoint"'); + } else { + nativeConnection.execute('ROLLBACK'); + } + } catch (_) {} + rethrow; + } + } + + @override + String get info => 'db: ${_adapter.databasePath}, closed: $isClosed'; + + @override + String get runtimeTypeNameSafe => '_DBSQLiteConnectionWrapped'; +} + +class _DBSQLiteConnectionTransaction extends DBSQLiteConnectionWrapper { + final _DBSQLiteConnectionWrapped parent; + + _DBSQLiteConnectionTransaction(this.parent) : super(parent.nativeConnection); + + @override + String get connectionURL => parent.connectionURL; + + @override + ResultSet select(String sql, [List? parameters]) => + parent.select(sql, parameters); + + @override + void execute(String sql, [List? parameters]) => + parent.execute(sql, parameters); + + @override + int get lastInsertRowId => parent.lastInsertRowId; + + @override + int get updatedRows => parent.updatedRows; + + // SQLite has no nested transactions: re-enter the current one (same as the + // MySQL adapter). + @override + Future openTransaction( + Future Function(DBSQLiteConnectionWrapper connectionTransaction) queryBlock, + ) => queryBlock(this); + + @override + String get runtimeTypeNameSafe => '_DBSQLiteConnectionTransaction'; +} + +/// Pairs a [SqliteException] with the [SQL] that caused it. +/// +/// SQLite's constraint messages only name `table.column`, so +/// [DBSQLiteAdapter.resolveError] needs the statement's bound parameters to +/// report *which value* was rejected. +class _SQLiteStatementException implements Exception { + final SqliteException error; + final SQL sql; + + _SQLiteStatementException(this.error, this.sql); + + @override + String toString() => '$error\nCausing SQL: $sql'; +} + +/// Exception thrown by [DBSQLiteAdapter] operations. +class DBSQLiteAdapterException extends DBSQLAdapterException { + @override + String get runtimeTypeNameSafe => 'DBSQLiteAdapterException'; + + DBSQLiteAdapterException( + super.type, + super.message, { + super.parentError, + super.parentStackTrace, + super.operation, + super.previousError, + }); +} diff --git a/lib/src/bones_api_entity_reference.dart b/lib/src/bones_api_entity_reference.dart index e0dc6cc8..432c103c 100644 --- a/lib/src/bones_api_entity_reference.dart +++ b/lib/src/bones_api_entity_reference.dart @@ -78,15 +78,13 @@ class EntityInstantiatorHandler extends EntityInstantiator { } static Map resolveEntityMap(Object entity) { - var entityMap = - entity is Map - ? entity - : TypeParser.parseMap(entity) ?? {}; + var entityMap = entity is Map + ? entity + : TypeParser.parseMap(entity) ?? {}; - var entityMapCast = - entityMap is Map - ? entityMap - : entityMap.map((key, value) => MapEntry('$key', value)); + var entityMapCast = entityMap is Map + ? entityMap + : entityMap.map((key, value) => MapEntry('$key', value)); return entityMapCast; } @@ -747,15 +745,13 @@ class EntityReference extends EntityReferenceBase { } static Map _resolveEntityMap(Object entity) { - var entityMap = - entity is Map - ? entity - : TypeParser.parseMap(entity) ?? {}; - - var entityMapCast = - entityMap is Map - ? entityMap - : entityMap.map((key, value) => MapEntry('$key', value)); + var entityMap = entity is Map + ? entity + : TypeParser.parseMap(entity) ?? {}; + + var entityMapCast = entityMap is Map + ? entityMap + : entityMap.map((key, value) => MapEntry('$key', value)); return entityMapCast; } @@ -1052,8 +1048,8 @@ class EntityReference extends EntityReferenceBase { var entityProvider = this.entityProvider; if (entityProvider != null) { var entityByID = - // ignore: discarded_futures - entityProvider.getEntityByID(id, type: type, sync: true); + // ignore: discarded_futures + entityProvider.getEntityByID(id, type: type, sync: true); if (entityByID is Future) { throw StateError( "Can't get entity (#$id@`$T`) from `entityProvider`, returned a `Future`: $entityProvider", @@ -1394,14 +1390,10 @@ class EntityReference extends EntityReferenceBase { var entityFetcher = _entityFetcher; if (entityFetcher != null) { - fetcher = - (ids, type) => - ids - .map( - (id) => entityFetcher(id, type), - ) // ignore: discarded_futures - .toList() - .resolveAll(); // ignore: discarded_futures + fetcher = (ids, type) => ids + .map((id) => entityFetcher(id, type)) // ignore: discarded_futures + .toList() + .resolveAll(); // ignore: discarded_futures } return EntityReferenceList._( @@ -1624,17 +1616,16 @@ class EntityReferenceList extends EntityReferenceBase { var entities = json['entities']; if (entities != null && entities is List) { - var entitiesMaps = - entities is List?> - ? entities - : entities - .map((e) { - var m = e is Map ? e : TypeParser.parseMap(e); - return m is Map - ? m - : m?.map((key, value) => MapEntry('$key', value)); - }) - .toList(growable: false); + var entitiesMaps = entities is List?> + ? entities + : entities + .map((e) { + var m = e is Map ? e : TypeParser.parseMap(e); + return m is Map + ? m + : m?.map((key, value) => MapEntry('$key', value)); + }) + .toList(growable: false); return EntityReferenceList.fromEntitiesMaps( entitiesMaps, @@ -1819,26 +1810,20 @@ class EntityReferenceList extends EntityReferenceBase { var entityHandler = this.entityHandler; if (entityHandler != null) { - _ids = - entitiesMaps - .map( - (map) => - map == null ? null : entityHandler.resolveIDFromMap(map), - ) - .toList(); + _ids = entitiesMaps + .map( + (map) => map == null ? null : entityHandler.resolveIDFromMap(map), + ) + .toList(); entityCache ??= JsonEntityCacheSimple(); List> lAsync = entitiesMaps .map( - (map) => - map == null - ? null - // ignore: discarded_futures - : entityHandler.createFromMap( - map, - entityCache: entityCache, - ), + (map) => map == null + ? null + // ignore: discarded_futures + : entityHandler.createFromMap(map, entityCache: entityCache), ) .toList(growable: false); @@ -2092,18 +2077,17 @@ class EntityReferenceList extends EntityReferenceBase { var entityProvider = this.entityProvider; if (ids != null && entityProvider != null) { - entities = - ids.map((id) { - var entityByID = + entities = ids.map((id) { + var entityByID = // ignore: discarded_futures entityProvider.getEntityByID(id, type: type, sync: true); - if (entityByID is Future) { - throw StateError( - "Can't get entity (#$id@`$T`) from `entityProvider`, returned a `Future`: $entityProvider", - ); - } - return entityByID; - }).toList(); + if (entityByID is Future) { + throw StateError( + "Can't get entity (#$id@`$T`) from `entityProvider`, returned a `Future`: $entityProvider", + ); + } + return entityByID; + }).toList(); if (entities.every((e) => e == null)) { entities = null; @@ -2175,30 +2159,26 @@ class EntityReferenceList extends EntityReferenceBase { if (entities == null) return null; if (jsonEncoder != null) { - var jsonList = - entities - .map( - (e) => - e == null - ? null - : (jsonEncoder.toJson(e) as Map?), - ) - .toList(); + var jsonList = entities + .map( + (e) => e == null + ? null + : (jsonEncoder.toJson(e) as Map?), + ) + .toList(); return jsonList; } var entityHandler = this.entityHandler; if (entityHandler != null) { - var jsonList = - entities - .map( - (e) => - e == null - ? null - : (entityHandler.toJson(e) as Map?), - ) - .toList(); + var jsonList = entities + .map( + (e) => e == null + ? null + : (entityHandler.toJson(e) as Map?), + ) + .toList(); return jsonList; } @@ -2403,11 +2383,10 @@ class EntityReferenceList extends EntityReferenceBase { if (entityProvider == null) return entities; - var l = - ids - .map((id) => entityProvider.getEntityByID(id, type: type)) - .toList() - .resolveAll(); + var l = ids + .map((id) => entityProvider.getEntityByID(id, type: type)) + .toList() + .resolveAll(); return l; } @@ -2481,8 +2460,11 @@ class EntityReferenceList extends EntityReferenceBase { if (ids.length == 1) { _entities = [o]; } else { - var entities = - _entities = List.filled(ids.length, null, growable: true); + var entities = _entities = List.filled( + ids.length, + null, + growable: true, + ); entities[ids.lastIndex] = o; } @@ -2643,10 +2625,9 @@ class EntityReferenceList extends EntityReferenceBase { var entityHandler = this.entityHandler; if (entityHandler != null) { - var otherIDs = - otherEntities - .map((e) => _getEntityIDImpl(entityHandler, e as T)) - .toList(); + var otherIDs = otherEntities + .map((e) => _getEntityIDImpl(entityHandler, e as T)) + .toList(); return _idsEquality.equals(ids, otherIDs); } @@ -2708,8 +2689,7 @@ class EntityReferenceList extends EntityReferenceBase { final entitiesFetcher = _entitiesFetcher; if (entitiesFetcher != null) { - fetcher = - (id, type) => + fetcher = (id, type) => // ignore: discarded_futures entitiesFetcher([id], type).resolveMapped((l) => l?.firstOrNull); } diff --git a/lib/src/bones_api_entity_rules.dart b/lib/src/bones_api_entity_rules.dart index b804bbc3..1b8777c4 100644 --- a/lib/src/bones_api_entity_rules.dart +++ b/lib/src/bones_api_entity_rules.dart @@ -558,13 +558,13 @@ class EntityAccessRules extends EntityRules { return s._isInnocuousImpl() ? EntityAccessRules.innocuous : EntityAccessRules._simplified( - s.ruleType, - s.entityType, - s.entityFields, - s.rules, - s.condition, - s.masker, - ); + s.ruleType, + s.entityType, + s.entityFields, + s.rules, + s.condition, + s.masker, + ); } EntityAccessRules _simplifiedImpl() { @@ -575,13 +575,12 @@ class EntityAccessRules extends EntityRules { .where((r) => !r.isInnocuous) .map((r) => r.simplified()); - var rulesSimpleFlat = - rulesSimple.expand((r) { - var rules = r.rules; - return rules != null && rules.isNotEmpty && r.entityType == null - ? rules - : [r]; - }).toList(); + var rulesSimpleFlat = rulesSimple.expand((r) { + var rules = r.rules; + return rules != null && rules.isNotEmpty && r.entityType == null + ? rules + : [r]; + }).toList(); if (entityType != null) { var r1 = copyWith(rules: []); @@ -614,15 +613,13 @@ class EntityAccessRules extends EntityRules { var rulesA = a.rules; var rulesB = b.rules; - var listA = - rulesA != null && rulesA.isNotEmpty && a.entityType == null - ? rulesA - : [a]; + var listA = rulesA != null && rulesA.isNotEmpty && a.entityType == null + ? rulesA + : [a]; - var listB = - rulesB != null && rulesB.isNotEmpty && b.entityType == null - ? rulesB - : [b]; + var listB = rulesB != null && rulesB.isNotEmpty && b.entityType == null + ? rulesB + : [b]; var allRules = [...listA, ...listB]; @@ -671,26 +668,24 @@ class EntityAccessRules extends EntityRules { var propsStr = props.isNotEmpty ? '{${props.join(', ')}}' : ''; final rules = this.rules; - var rulesStr = - rules != null && rules.isNotEmpty - ? '<\n ${rules.join('\n ')}\n>' - : ''; + var rulesStr = rules != null && rules.isNotEmpty + ? '<\n ${rules.join('\n ')}\n>' + : ''; return 'EntityAccessRules$ruleTypeStr$propsStr$rulesStr'; } @override - Map toJson() => - isInnocuous - ? {} - : { - if (ruleType != null) 'ruleType': ruleType?.name, - if (entityType != null) 'entityType': '$entityType', - if (entityFields != null && entityFields!.isNotEmpty) - 'entityFields': entityFields, - if (rules != null && rules!.isNotEmpty) - 'rules': rules!.map((e) => e.toJson()).toList(), - }; + Map toJson() => isInnocuous + ? {} + : { + if (ruleType != null) 'ruleType': ruleType?.name, + if (entityType != null) 'entityType': '$entityType', + if (entityFields != null && entityFields!.isNotEmpty) + 'entityFields': entityFields, + if (rules != null && rules!.isNotEmpty) + 'rules': rules!.map((e) => e.toJson()).toList(), + }; ToEncodableJson? toJsonEncodable( APIRequest apiRequest, @@ -1218,17 +1213,15 @@ class EntityResolutionRules extends EntityRules { Map? lazyTypes, bool mergeTolerant = false, }) { - var eagerTypesNotNull = - eagerTypes?.entries.map((e) { - var eager = e.value; - return eager != null ? MapEntry(e.key, eager) : null; - }).nonNulls; - - var lazyTypesNotNull = - lazyTypes?.entries.map((e) { - var lazy = e.value; - return lazy != null ? MapEntry(e.key, lazy) : null; - }).nonNulls; + var eagerTypesNotNull = eagerTypes?.entries.map((e) { + var eager = e.value; + return eager != null ? MapEntry(e.key, eager) : null; + }).nonNulls; + + var lazyTypesNotNull = lazyTypes?.entries.map((e) { + var lazy = e.value; + return lazy != null ? MapEntry(e.key, lazy) : null; + }).nonNulls; var fetchEager = eagerTypesNotNull?.map((e) { var t = e.key; @@ -1254,33 +1247,31 @@ class EntityResolutionRules extends EntityRules { return !lazy ? t : null; }); - var eagerEntityTypes = - [...?fetchEager, ...?fetchNotLazy].nonNulls - .expand((e) => e is Iterable ? e : [e]) - .whereType() - .toList(); + var eagerEntityTypes = [...?fetchEager, ...?fetchNotLazy].nonNulls + .expand((e) => e is Iterable ? e : [e]) + .whereType() + .toList(); - var lazyEntityTypes = - [...?fetchLazy, ...?fetchNotEager].nonNulls - .expand((e) => e is Iterable ? e : [e]) - .whereType() - .toList(); + var lazyEntityTypes = [...?fetchLazy, ...?fetchNotEager].nonNulls + .expand((e) => e is Iterable ? e : [e]) + .whereType() + .toList(); if (allEager != null && allEager) { return lazyEntityTypes.isNotEmpty ? EntityResolutionRules( - allEager: true, - lazyEntityTypes: lazyEntityTypes, - mergeTolerant: mergeTolerant, - ) + allEager: true, + lazyEntityTypes: lazyEntityTypes, + mergeTolerant: mergeTolerant, + ) : EntityResolutionRules.fetchEagerAll(mergeTolerant: mergeTolerant); } else if (allLazy != null && allLazy) { return eagerEntityTypes.isNotEmpty ? EntityResolutionRules( - allLazy: true, - eagerEntityTypes: eagerEntityTypes, - mergeTolerant: mergeTolerant, - ) + allLazy: true, + eagerEntityTypes: eagerEntityTypes, + mergeTolerant: mergeTolerant, + ) : EntityResolutionRules.fetchLazyAll(mergeTolerant: mergeTolerant); } @@ -1340,8 +1331,9 @@ class EntityResolutionRules extends EntityRules { eagerEntityTypes.isNotEmpty && lazyEntityTypes.isNotEmpty) { if (eagerEntityTypes.any((t) => lazyEntityTypes.contains(t))) { - var conflict = - eagerEntityTypes.where((t) => lazyEntityTypes.contains(t)).toList(); + var conflict = eagerEntityTypes + .where((t) => lazyEntityTypes.contains(t)) + .toList(); return conflict; } } @@ -1508,11 +1500,13 @@ class EntityResolutionRules extends EntityRules { } } - var lazyEntityTypes = - this.lazyEntityTypes.merge(other.lazyEntityTypes).nullIfEmpty(); + var lazyEntityTypes = this.lazyEntityTypes + .merge(other.lazyEntityTypes) + .nullIfEmpty(); - var eagerEntityTypes = - this.eagerEntityTypes.merge(other.eagerEntityTypes).nullIfEmpty(); + var eagerEntityTypes = this.eagerEntityTypes + .merge(other.eagerEntityTypes) + .nullIfEmpty(); var merge = EntityResolutionRules( allowEntityFetch: allowEntityFetch, @@ -1602,25 +1596,26 @@ class EntityResolutionRules extends EntityRules { return isInnocuous ? 'EntityResolutionRules{innocuous}' : 'EntityResolutionRules{${[if (allLazy != null) 'allLazy: $allLazy', if (allEager != null) 'allEager: $allEager', if (allowEntityFetch) 'allowEntityFetch', if (allowReadFile) 'allowReadFile', if (lazyEntityTypes != null && lazyEntityTypes.isNotEmpty) 'lazyEntityTypes: $lazyEntityTypes', if (eagerEntityTypes != null && eagerEntityTypes.isNotEmpty) 'eagerEntityTypes: $eagerEntityTypes'].join(', ')}' - '}'; + '}'; } @override - Map toJson() => - isInnocuous - ? {} - : { - if (allLazy != null) 'allLazy': allLazy, - if (allEager != null) 'allEager': allEager, - if (allowEntityFetch) 'allowEntityFetch': true, - if (allowReadFile) 'allowReadFile': true, - if (lazyEntityTypes != null && lazyEntityTypes!.isNotEmpty) - 'lazyEntityTypes': - lazyEntityTypes!.map((e) => e.toString()).toList(), - if (eagerEntityTypes != null && eagerEntityTypes!.isNotEmpty) - 'eagerEntityTypes': - eagerEntityTypes!.map((e) => e.toString()).toList(), - }; + Map toJson() => isInnocuous + ? {} + : { + if (allLazy != null) 'allLazy': allLazy, + if (allEager != null) 'allEager': allEager, + if (allowEntityFetch) 'allowEntityFetch': true, + if (allowReadFile) 'allowReadFile': true, + if (lazyEntityTypes != null && lazyEntityTypes!.isNotEmpty) + 'lazyEntityTypes': lazyEntityTypes! + .map((e) => e.toString()) + .toList(), + if (eagerEntityTypes != null && eagerEntityTypes!.isNotEmpty) + 'eagerEntityTypes': eagerEntityTypes! + .map((e) => e.toString()) + .toList(), + }; } class EntityResolutionRulesResolved implements EntityResolutionRules { @@ -1880,9 +1875,8 @@ mixin EntityRulesResolver { static EntityRulesContextProvider? _singleContextProvider; - static void _updateSingleContextProvider() => - _singleContextProvider = - _cotextProviders.length == 1 ? _cotextProviders.first : null; + static void _updateSingleContextProvider() => _singleContextProvider = + _cotextProviders.length == 1 ? _cotextProviders.first : null; /// Returns the current [EntityRulesContextProvider]s. static List get cotextProviders => @@ -2010,10 +2004,10 @@ mixin EntityRulesResolver { return identical(merge, EntityResolutionRules.innocuous) ? EntityResolutionRulesResolved._innocuousResolved : EntityResolutionRulesResolved( - merge, - contextRules: context, - rules: rules, - ); + merge, + contextRules: context, + rules: rules, + ); } } } diff --git a/lib/src/bones_api_error_zone.dart b/lib/src/bones_api_error_zone.dart index 9952e04b..1baf7613 100644 --- a/lib/src/bones_api_error_zone.dart +++ b/lib/src/bones_api_error_zone.dart @@ -26,8 +26,8 @@ Zone createErrorZone({ var zoneId = ++_errorZoneIDCount; var zoneSpecification = ZoneSpecification( - handleUncaughtError: - (self, parent, zone, error, stackTrace) => _handleUncaughtError( + handleUncaughtError: (self, parent, zone, error, stackTrace) => + _handleUncaughtError( uncaughtErrorTitle, printErrorToStderr, onUncaughtError, @@ -213,9 +213,8 @@ class ZoneField { } zoneSpecification = ZoneSpecification( - handleUncaughtError: - (self, parent, zone, error, stack) => - handleUncaughtError(error, stack), + handleUncaughtError: (self, parent, zone, error, stack) => + handleUncaughtError(error, stack), ); } diff --git a/lib/src/bones_api_extension.dart b/lib/src/bones_api_extension.dart index 16de9706..dffc1b35 100644 --- a/lib/src/bones_api_extension.dart +++ b/lib/src/bones_api_extension.dart @@ -578,10 +578,9 @@ extension TypeReflectionEntityExtension on TypeReflection { isListEntity ? EntityHandler.isValidEntityType(arguments0!.type) : false; /// Returns `true` if [isListEntityOrReference] AND [EntityHandler.isValidEntityType] for the entity type ([argumentType] `0`). - bool get isValidListEntityOrReferenceType => - isListEntityOrReference - ? EntityHandler.isValidEntityType(arguments0!.type) - : false; + bool get isValidListEntityOrReferenceType => isListEntityOrReference + ? EntityHandler.isValidEntityType(arguments0!.type) + : false; /// Returns `true` if [type] is equals to [EntityReference]. bool get isEntityReferenceType => type == EntityReference; @@ -630,10 +629,9 @@ extension TypeInfoEntityExtension on TypeInfo { isListEntity ? EntityHandler.isValidEntityType(arguments0!.type) : false; /// Returns `true` if [isListEntityOrReference] AND [EntityHandler.isValidEntityType] for the entity type ([argumentType] `0`). - bool get isValidListEntityOrReferenceType => - isListEntityOrReference - ? EntityHandler.isValidEntityType(arguments0!.type) - : false; + bool get isValidListEntityOrReferenceType => isListEntityOrReference + ? EntityHandler.isValidEntityType(arguments0!.type) + : false; /// Returns `true` if [type] is equals to [EntityReference]. bool get isEntityReferenceType => type == EntityReference; @@ -822,10 +820,9 @@ extension TypeInfoEntityExtension on TypeInfo { EntityReference castCall() { EntityHandler? oEntityHandler; if (entityHandler != null) { - oEntityHandler = - entityHandler is EntityHandler - ? (entityHandler as EntityHandler) - : entityHandler.getEntityHandler(type: type); + oEntityHandler = entityHandler is EntityHandler + ? (entityHandler as EntityHandler) + : entityHandler.getEntityHandler(type: type); } return _toEntityReferenceImpl( @@ -954,23 +951,20 @@ extension TypeInfoEntityExtension on TypeInfo { } if (entityFetcher != null && entitiesFetcher == null) { - entitiesFetcher = - (ids, type) => - ids - // ignore: discarded_futures - .map((id) => id == null ? null : entityFetcher(id, type)) - .toList() - // ignore: discarded_futures - .resolveAll(); + entitiesFetcher = (ids, type) => ids + // ignore: discarded_futures + .map((id) => id == null ? null : entityFetcher(id, type)) + .toList() + // ignore: discarded_futures + .resolveAll(); } EntityReferenceList castCall() { EntityHandler? oEntityHandler; if (entityHandler != null) { - oEntityHandler = - entityHandler is EntityHandler - ? (entityHandler as EntityHandler) - : entityHandler.getEntityHandler(type: type); + oEntityHandler = entityHandler is EntityHandler + ? (entityHandler as EntityHandler) + : entityHandler.getEntityHandler(type: type); } return _toEntityReferenceListImpl( @@ -1038,10 +1032,9 @@ extension TypeInfoEntityExtension on TypeInfo { ); } else if (o is List?> || o.every((Object? e) => e == null || e is Map)) { - var entitiesMaps = - o is List?> - ? o - : o.cast?>(); + var entitiesMaps = o is List?> + ? o + : o.cast?>(); return EntityReferenceList.fromEntitiesMaps( entitiesMaps, type: type, diff --git a/lib/src/bones_api_html_document.dart b/lib/src/bones_api_html_document.dart index afbb653c..fa1a6835 100644 --- a/lib/src/bones_api_html_document.dart +++ b/lib/src/bones_api_html_document.dart @@ -142,10 +142,11 @@ class HTMLDocument { } String get bodyStyles { - var styles = (_bodyStyles != null && _bodyStyles.trim().isNotEmpty - ? _bodyStyles - : '') - .split(RegExp(r'\s*;\s*')); + var styles = + (_bodyStyles != null && _bodyStyles.trim().isNotEmpty + ? _bodyStyles + : '') + .split(RegExp(r'\s*;\s*')); final bodyColor = this.bodyColor; if (bodyColor != null && bodyColor.isNotEmpty) { @@ -402,17 +403,16 @@ class HTMLInput { html.write('\n'); - valStr = - value != null - ? (enumReflection.name(value) ?? - value.toString().split('.').last) - : ''; + valStr = value != null + ? (enumReflection.name(value) ?? value.toString().split('.').last) + : ''; for (var e in enumReflection.values) { var enumName = enumReflection.name(e) ?? ''; - var selected = - equalsIgnoreAsciiCase(enumName, valStr) ? ' selected' : ''; + var selected = equalsIgnoreAsciiCase(enumName, valStr) + ? ' selected' + : ''; html.write('\n'); } diff --git a/lib/src/bones_api_initializable.dart b/lib/src/bones_api_initializable.dart index b6dd44fb..d5e47a40 100644 --- a/lib/src/bones_api_initializable.dart +++ b/lib/src/bones_api_initializable.dart @@ -158,10 +158,9 @@ class InitializationResult { @override String toString() { - var depsStr = - dependencies.isNotEmpty - ? '->${dependencies.length <= 3 ? dependencies.toInitializationStatus() : dependencies.length}' - : ''; + var depsStr = dependencies.isNotEmpty + ? '->${dependencies.length <= 3 ? dependencies.toInitializationStatus() : dependencies.length}' + : ''; if (ok) { return '[OK]@${initializable.initializationStatus}$depsStr'; @@ -370,8 +369,8 @@ class _InitializationChain { bool _markInitializedDependency(Initializable dependency) { _checkDependency(dependency); - var initializedDependencies = - _initializedDependencies ??= []; + var initializedDependencies = _initializedDependencies ??= + []; return initializedDependencies.addUnique(dependency); } @@ -507,13 +506,14 @@ class _InitializationChain { dependencies ??= _dependencies; if (dependencies == null || dependencies.isEmpty) return; - var depsInitializingAsync = - dependencies.whereInitializing().whereAsyncInitialization(); + var depsInitializingAsync = dependencies + .whereInitializing() + .whereAsyncInitialization(); if (depsInitializingAsync.isEmpty) return; - var subDepsInitializing = - depsInitializingAsync._subDependenciesInitialized(); + var subDepsInitializing = depsInitializingAsync + ._subDependenciesInitialized(); //print('!!! depsInitializingAsync: $runtimeTypeNameUnsafe >> ${depsInitializingAsync.toInitializationStatus()}'); //print('!!! subDepsInitializing: $runtimeTypeNameUnsafe >> ${subDepsInitializing.toInitializationStatus()}'); @@ -642,9 +642,8 @@ mixin Initializable { var r = await initCircular.timeout( Duration(milliseconds: 1000), - onTimeout: - () => - InitializationResult.error(this, circularTimeoutError), + onTimeout: () => + InitializationResult.error(this, circularTimeoutError), ); if (r.ok) { return r; @@ -656,11 +655,8 @@ mixin Initializable { if (parentsLength3 > parentsLength2) { var r2 = await initCircular.timeout( Duration(milliseconds: 1000), - onTimeout: - () => InitializationResult.error( - this, - circularTimeoutError, - ), + onTimeout: () => + InitializationResult.error(this, circularTimeoutError), ); if (r2.ok) { @@ -851,23 +847,18 @@ mixin Initializable { chain._markInitializedDependencies(dependencies); - var depsInits = - dependencies - .map((e) => MapEntry(e, e._doInitializationImpl(this))) - .toList(); + var depsInits = dependencies + .map((e) => MapEntry(e, e._doInitializationImpl(this))) + .toList(); - var depsInitsAsync = - depsInits - .where((e) => e.value is Future) - .map( - (e) => MapEntry(e.key, e.value as Future), - ) - .toList(); + var depsInitsAsync = depsInits + .where((e) => e.value is Future) + .map((e) => MapEntry(e.key, e.value as Future)) + .toList(); - var depsCompleters = - depsInitsAsync - .map((e) => MapEntry(e.key, e.value.toCompleter())) - .toList(); + var depsCompleters = depsInitsAsync + .map((e) => MapEntry(e.key, e.value.toCompleter())) + .toList(); chain._setInitializedDependenciesCompleters(depsCompleters); @@ -919,11 +910,10 @@ mixin Initializable { var dependencies = result.dependencies.uniqueEntries(); if (!identical(this, result.initializable)) { - dependencies = - [ - result.initializable, - ...dependencies, - ].uniqueEntries(); + dependencies = [ + result.initializable, + ...dependencies, + ].uniqueEntries(); result = InitializationResult.ok(this, dependencies: dependencies); } @@ -940,23 +930,21 @@ mixin Initializable { depsResults, ) { if (depsResults.hasSkipped) { - var skipped = - depsResults.skipped - ?.notInitialized(ignoreFinalizing: true) - .toList(); + var skipped = depsResults.skipped + ?.notInitialized(ignoreFinalizing: true) + .toList(); if (skipped?.isNotEmpty ?? false) { return (() => _doDependenciesInitialization( - dependencies, - forceCircular: true, - )) - .tryCallThen((depsResults2) { - var allDepsResults = depsResults.merge( - depsResults2, - dependencies: dependencies, - ); - return _finalizeInitializationWithDeps(allDepsResults, result); - }, onError: (e, s) => _onInitializationError(e, s)); + dependencies, + forceCircular: true, + )).tryCallThen((depsResults2) { + var allDepsResults = depsResults.merge( + depsResults2, + dependencies: dependencies, + ); + return _finalizeInitializationWithDeps(allDepsResults, result); + }, onError: (e, s) => _onInitializationError(e, s)); } } @@ -970,10 +958,9 @@ mixin Initializable { ) { var dependenciesDeep = _chain.dependenciesDeep; - var depsInitializing = - dependenciesDeep - .where((e) => e.initializationStatus.initializing) - .toList(); + var depsInitializing = dependenciesDeep + .where((e) => e.initializationStatus.initializing) + .toList(); if (_chain.parentsLength == 0 && depsInitializing.isNotEmpty) { _log.info( @@ -1020,8 +1007,9 @@ mixin Initializable { InitializationResult result, ) { if (depsResults.hasSkipped) { - var skipped = - depsResults.skipped?.notInitialized(ignoreFinalizing: true).toList(); + var skipped = depsResults.skipped + ?.notInitialized(ignoreFinalizing: true) + .toList(); if (skipped?.isNotEmpty ?? false) { _log.warning( @@ -1034,10 +1022,9 @@ mixin Initializable { var dependenciesDeep = _chain.dependenciesDeep; - var depsInitializing = - dependenciesDeep - .where((e) => e.initializationStatus.initializing) - .toList(); + var depsInitializing = dependenciesDeep + .where((e) => e.initializationStatus.initializing) + .toList(); if (_chain.parentsLength == 0 && depsInitializing.isNotEmpty) { _log.warning( @@ -1189,12 +1176,11 @@ class _DependenciesInitialization { _DependenciesInitialization other, { required List? dependencies, }) { - var allResults = - [...results, ...other.results] - .groupBy((dep) => dep.initializable) - .map((dep, res) => MapEntry(dep, res.first)) - .values - .toList(); + var allResults = [...results, ...other.results] + .groupBy((dep) => dep.initializable) + .map((dep, res) => MapEntry(dep, res.first)) + .values + .toList(); return _DependenciesInitialization(allResults, dependencies: dependencies); } @@ -1240,12 +1226,10 @@ extension InitializableListExtension on List { List get idle => where((e) => !e._status.initialized && !e._status.initializing).toList(); - List notInitialized({bool ignoreFinalizing = false}) => - where( - (e) => - !e._status.initialized && - (!ignoreFinalizing || !e._status.finalizing), - ).toList(); + List notInitialized({bool ignoreFinalizing = false}) => where( + (e) => + !e._status.initialized && (!ignoreFinalizing || !e._status.finalizing), + ).toList(); } extension _ListExtension on List { diff --git a/lib/src/bones_api_logging.dart b/lib/src/bones_api_logging.dart index 673e4672..ff69cab3 100644 --- a/lib/src/bones_api_logging.dart +++ b/lib/src/bones_api_logging.dart @@ -533,24 +533,23 @@ abstract class LoggerHandler { Future? call; - call = - _bufferedCalls[identifier] ??= Future.delayed( - Duration(milliseconds: 100), - () async { - for (var levelBlock in buffer) { - var level = levelBlock.$1; - var messages = levelBlock.$2; - messagesBlockLogger(level, messages); - } - - buffer.clear(); - - var prevCall = _bufferedCalls[identifier]; - if (identical(prevCall, call)) { - _bufferedCalls[identifier] = null; - } - }, - ); + call = _bufferedCalls[identifier] ??= Future.delayed( + Duration(milliseconds: 100), + () async { + for (var levelBlock in buffer) { + var level = levelBlock.$1; + var messages = levelBlock.$2; + messagesBlockLogger(level, messages); + } + + buffer.clear(); + + var prevCall = _bufferedCalls[identifier]; + if (identical(prevCall, call)) { + _bufferedCalls[identifier] = null; + } + }, + ); } void _logToHttpClient( diff --git a/lib/src/bones_api_mixin.dart b/lib/src/bones_api_mixin.dart index 703213f4..3289c4ba 100644 --- a/lib/src/bones_api_mixin.dart +++ b/lib/src/bones_api_mixin.dart @@ -94,17 +94,16 @@ mixin Pool { FutureOr> filterPoolElements(FutureOr Function(O o) filter) { var hasFuture = false; - var elements = - _pool.map>((o) { - var valid = filter(o); + var elements = _pool.map>((o) { + var valid = filter(o); - if (valid is Future) { - hasFuture = true; - return valid.then((valid) => valid ? o : null); - } + if (valid is Future) { + hasFuture = true; + return valid.then((valid) => valid ? o : null); + } - return valid ? o : null; - }).toList(); + return valid ? o : null; + }).toList(); if (hasFuture) { return elements.resolveAllNotNull(); @@ -715,22 +714,21 @@ mixin FieldsFromMap { Map? mapLC, Map? mapSimple, }) { - var fieldsMap = - fields - .map( - (f) => MapEntry( - f, - getFieldKeyInMap( - f, - map, - fieldLC: fieldLC, - fieldSimple: fieldSimple, - mapLC: mapLC, - mapSimple: mapSimple, - ), - ), - ) - .toMapFromEntries(); + var fieldsMap = fields + .map( + (f) => MapEntry( + f, + getFieldKeyInMap( + f, + map, + fieldLC: fieldLC, + fieldSimple: fieldSimple, + mapLC: mapLC, + mapSimple: mapSimple, + ), + ), + ) + .toMapFromEntries(); return fieldsMap; } diff --git a/lib/src/bones_api_module.dart b/lib/src/bones_api_module.dart index 051e4ae0..64aed248 100644 --- a/lib/src/bones_api_module.dart +++ b/lib/src/bones_api_module.dart @@ -595,8 +595,9 @@ class APIRouteBuilder { var receivesAPIRequest = apiMethod.receivesAPIRequest; var methodRules = apiMethod.annotations.whereType().toList(); - var classRules = - classReflection.classAnnotations.whereType().toList(); + var classRules = classReflection.classAnnotations + .whereType() + .toList(); List rules; if (methodRules.isEmpty) { @@ -604,10 +605,9 @@ class APIRouteBuilder { } else { var noGlobalRules = methodRules.any((r) => r.noGlobalRules); - rules = - noGlobalRules - ? methodRules - : [...methodRules, ...classRules.where((r) => r.globalRules)]; + rules = noGlobalRules + ? methodRules + : [...methodRules, ...classRules.where((r) => r.globalRules)]; } var config = apiMethod.annotations.whereType().firstOrNull; @@ -630,20 +630,9 @@ class APIRouteBuilder { var paramName = apiMethod.normalParametersNames.first; var parameters = {paramName: APIRequest.typeInfo}; - routeHandler = - returnTypeInfo.hasArguments - ? returnTypeInfo.callCastedArgumentA( - () => _APIRouteHandlerAPIMethodAPIRequestAPIResponse( - module, - requestMethod, - apiMethod.name, - apiMethod, - parameters, - rules, - config, - ), - ) - : _APIRouteHandlerAPIMethodAPIRequestAPIResponse( + routeHandler = returnTypeInfo.hasArguments + ? returnTypeInfo.callCastedArgumentA( + () => _APIRouteHandlerAPIMethodAPIRequestAPIResponse( module, requestMethod, apiMethod.name, @@ -651,7 +640,17 @@ class APIRouteBuilder { parameters, rules, config, - ); + ), + ) + : _APIRouteHandlerAPIMethodAPIRequestAPIResponse( + module, + requestMethod, + apiMethod.name, + apiMethod, + parameters, + rules, + config, + ); } else { var parameters = Map.fromEntries( apiMethod.allParameters.map( @@ -659,20 +658,9 @@ class APIRouteBuilder { ), ); - routeHandler = - returnTypeInfo.hasArguments - ? returnTypeInfo.callCastedArgumentA( - () => _APIRouteHandlerAPIMethodReflection( - module, - requestMethod, - apiMethod.name, - apiMethod, - parameters, - rules, - config, - ), - ) - : _APIRouteHandlerAPIMethodReflection( + routeHandler = returnTypeInfo.hasArguments + ? returnTypeInfo.callCastedArgumentA( + () => _APIRouteHandlerAPIMethodReflection( module, requestMethod, apiMethod.name, @@ -680,7 +668,17 @@ class APIRouteBuilder { parameters, rules, config, - ); + ), + ) + : _APIRouteHandlerAPIMethodReflection( + module, + requestMethod, + apiMethod.name, + apiMethod, + parameters, + rules, + config, + ); } addRouteHandler(routeHandler); @@ -813,8 +811,9 @@ class APIRouteBuilder { return TypeParser.parseMap( value, keyParser: argParser0 is TypeElementParser ? argParser0 : null, - valueParser: - argParser1 is TypeElementParser ? argParser1 : null, + valueParser: argParser1 is TypeElementParser + ? argParser1 + : null, ); }); @@ -930,29 +929,27 @@ class APIRouteBuilder { var listEntityType = parameterTypeInfo.listEntityType!; if (value is Iterable) { - var list = - value - .map( - (e) => _resolveValueAsEntity( - listEntityType, - e, - entityCache: entityCache, - resolutionRules: resolutionRules, - ), - ) - .toList(); - - return _castList(list, listEntityType); - } else { - var list = TypeParser.parseList( - value, - elementParser: + var list = value + .map( (e) => _resolveValueAsEntity( listEntityType, e, entityCache: entityCache, resolutionRules: resolutionRules, ), + ) + .toList(); + + return _castList(list, listEntityType); + } else { + var list = TypeParser.parseList( + value, + elementParser: (e) => _resolveValueAsEntity( + listEntityType, + e, + entityCache: entityCache, + resolutionRules: resolutionRules, + ), ); if (list != null) { @@ -966,10 +963,9 @@ class APIRouteBuilder { ); if (classReflection != null) { - var map = - value is Map - ? value - : value.map((k, v) => MapEntry(k.toString(), v)); + var map = value is Map + ? value + : value.map((k, v) => MapEntry(k.toString(), v)); var o = classReflection.createFromMapSync( map, entityCache: entityCache, @@ -1025,15 +1021,14 @@ class APIRouteBuilder { } List apiInfo([APIRequest? apiRequest]) { - var routesHandlers = - [ - ...module._routesHandlers.values, - ...module._routesHandlersGET.values, - ...module._routesHandlersPOST.values, - ...module._routesHandlersPATH.values, - ...module._routesHandlersPUT.values, - ...module._routesHandlersDELETE.values, - ].toDistinctList(); + var routesHandlers = [ + ...module._routesHandlers.values, + ...module._routesHandlersGET.values, + ...module._routesHandlersPOST.values, + ...module._routesHandlersPATH.values, + ...module._routesHandlersPUT.values, + ...module._routesHandlersDELETE.values, + ].toDistinctList(); var info = routesHandlers.map((e) => e.apiInfo(apiRequest)).toList(); return info; @@ -1229,8 +1224,9 @@ abstract class APIModuleProxyCallerListener } var typeInfo = returnType.typeInfo; - var mainType = - typeInfo.isFuture ? (typeInfo.arguments0 ?? typeInfo) : typeInfo; + var mainType = typeInfo.isFuture + ? (typeInfo.arguments0 ?? typeInfo) + : typeInfo; // var debugJsonPretty = Json.encode(json, pretty: true); // print(debugJsonPretty); @@ -1344,12 +1340,11 @@ class APIModuleProxyDirectCaller extends APIModuleProxyCallerListener { if (!accessRules.isInnocuous) { return Json.toJson( payload, - toEncodableProvider: - (o) => accessRules.toJsonEncodable( - apiRequest, - Json.defaultToEncodableJsonProvider(), - o, - ), + toEncodableProvider: (o) => accessRules.toJsonEncodable( + apiRequest, + Json.defaultToEncodableJsonProvider(), + o, + ), ); } } diff --git a/lib/src/bones_api_platform.dart b/lib/src/bones_api_platform.dart index 3d949b09..f63a5e16 100644 --- a/lib/src/bones_api_platform.dart +++ b/lib/src/bones_api_platform.dart @@ -167,23 +167,23 @@ class APIPlatformCapability { (int64 ? int64Max.toInt() : (int53 - ? int53Max.toInt() - : (int32 - ? int32Max.toInt() - : throw StateError( - 'maxSafeInteger error: Platform `int` not defined!', - )))), + ? int53Max.toInt() + : (int32 + ? int32Max.toInt() + : throw StateError( + 'maxSafeInteger error: Platform `int` not defined!', + )))), minSafeInteger = minSafeInteger ?? (int64 ? int64Min.toInt() : (int53 - ? int53Min.toInt() - : (int32 - ? int32Min.toInt() - : throw StateError( - 'minSafeInteger error: Platform `int` not defined!', - )))); + ? int53Min.toInt() + : (int32 + ? int32Min.toInt() + : throw StateError( + 'minSafeInteger error: Platform `int` not defined!', + )))); APIPlatformCapability.bits64({required bool canReadFile}) : this(canReadFile: canReadFile, int64: true, double64: true); diff --git a/lib/src/bones_api_repository.dart b/lib/src/bones_api_repository.dart index 89a6f04d..6670b063 100644 --- a/lib/src/bones_api_repository.dart +++ b/lib/src/bones_api_repository.dart @@ -37,13 +37,12 @@ abstract class APIRepository with Initializable { EntityRepository? entityRepository, EntityRepositoryProvider? provider, Type? type, - }) : entityRepository = - resolveEntityRepository( - entityRepository: entityRepository, - provider: provider, - type: type ?? O, - required: true, - )! { + }) : entityRepository = resolveEntityRepository( + entityRepository: entityRepository, + provider: provider, + type: type ?? O, + required: true, + )! { // ignore: discarded_futures this.entityRepository.ensureInitialized(parent: this); } diff --git a/lib/src/bones_api_security.dart b/lib/src/bones_api_security.dart index a4f56f7f..d29af934 100644 --- a/lib/src/bones_api_security.dart +++ b/lib/src/bones_api_security.dart @@ -33,8 +33,9 @@ abstract class APISecurity { String? sharedStoreID, SharedStoreProviderSync? storeProvider, }) : tokenDuration = tokenDuration ?? Duration(hours: 3), - tokenLength = - tokenLength != null && tokenLength > 32 ? tokenLength : 512, + tokenLength = tokenLength != null && tokenLength > 32 + ? tokenLength + : 512, _sharedStoreField = SharedStoreField.tryFrom( sharedStoreField: sharedStoreField, @@ -765,10 +766,9 @@ abstract class APISecurity { var credentialUsername = request.credential!.username; if (!usernames.contains(credentialUsername)) return null; - var tokensWithUsername = - sessionTokens - .where((t) => t.username == credentialUsername) - .toSet(); + var tokensWithUsername = sessionTokens + .where((t) => t.username == credentialUsername) + .toSet(); return tokensWithUsername; } else { return sessionTokens; @@ -872,8 +872,9 @@ abstract class APISecurity { ) { var username = getRequestParameterUsername(request).trim(); var token = getRequestParameterToken(request).trim(); - var refreshToken = - token.isEmpty ? getRequestParameterRefreshToken(request).trim() : ''; + var refreshToken = token.isEmpty + ? getRequestParameterRefreshToken(request).trim() + : ''; if (username.isNotEmpty) { if (token.isNotEmpty) { @@ -972,8 +973,11 @@ abstract class APISecurity { } } -typedef APITokenInfo = - ({APIToken apiToken, Object? data, List? permissions}); +typedef APITokenInfo = ({ + APIToken apiToken, + Object? data, + List? permissions, +}); /// The tokens store for the [APISecurity]. class APITokenStore { @@ -1401,11 +1405,10 @@ class APIRoutePermissionTypeRule extends APIRouteAuthenticatedRule { var normalizedType = _expandoNormalizedTypes[this]; if (normalizedType != null) return normalizedType; - normalizedType = - _requiredPermissionTypes - .map(APIPermission.normalizeType) - .where(APIPermission.validateType) - .toSet(); + normalizedType = _requiredPermissionTypes + .map(APIPermission.normalizeType) + .where(APIPermission.validateType) + .toSet(); _expandoNormalizedTypes[this] = normalizedType; return normalizedType; @@ -1419,11 +1422,10 @@ class APIRoutePermissionTypeRule extends APIRouteAuthenticatedRule { var requiredPermissionTypes = this.requiredPermissionTypes; - var authPermissions = - authentication - .enabledPermissionsOfTypes(requiredPermissionTypes) - .map((e) => e.type) - .toSet(); + var authPermissions = authentication + .enabledPermissionsOfTypes(requiredPermissionTypes) + .map((e) => e.type) + .toSet(); return authPermissions.length == requiredPermissionTypes.length; } @@ -1515,8 +1517,8 @@ class APIEntityRules extends APIRouteRule { } class SecureRandom implements Random { - static final Random _globalRandom1 = - Random()..advance(maxSteps: 211, random: Random()); + static final Random _globalRandom1 = Random() + ..advance(maxSteps: 211, random: Random()); static int _seedCounter = 0; diff --git a/lib/src/bones_api_server.dart b/lib/src/bones_api_server.dart index 57132e6f..caf6c741 100644 --- a/lib/src/bones_api_server.dart +++ b/lib/src/bones_api_server.dart @@ -234,10 +234,9 @@ class APIServerConfig { Object? args, }) : development = development ?? false, name = name != null && name.trim().isNotEmpty ? name : 'Bones_API', - version = - version != null && version.trim().isNotEmpty - ? version - : BonesAPI.VERSION, + version = version != null && version.trim().isNotEmpty + ? version + : BonesAPI.VERSION, address = normalizeAddress(address, apiConfig: apiConfig), port = resolvePort(port, apiConfig: apiConfig), securePort = resolveSecurePort( @@ -352,14 +351,15 @@ class APIServerConfig { var address = a.optionAsString('address'); var letsEncrypt = a.flag('letsencrypt'); - var letsEncryptProduction = - letsEncrypt ? a.flagOr('letsencrypt-production', null) : false; - var allowRequestLetsEncryptCertificate = - letsEncrypt - ? a.flagOr('allow-request-letsencrypt-certificate', null) - : false; - var letsEncryptDirectory = - letsEncrypt ? a.optionAsDirectory('letsencrypt-directory') : null; + var letsEncryptProduction = letsEncrypt + ? a.flagOr('letsencrypt-production', null) + : false; + var allowRequestLetsEncryptCertificate = letsEncrypt + ? a.flagOr('allow-request-letsencrypt-certificate', null) + : false; + var letsEncryptDirectory = letsEncrypt + ? a.optionAsDirectory('letsencrypt-directory') + : null; var hotReload = a.flag('hot-reload'); var totalWorkers = a.optionAsInt('total-workers'); @@ -522,24 +522,23 @@ class APIServerConfig { List domains, [ Directory? rootDir, ]) { - var domainsRoots = - domains - .map((e) { - var s = e.toString(); - var parts = s.split('='); - - var domain = parts[0]; - - if (parts.length == 1) { - return rootDir != null ? MapEntry(domain, rootDir) : null; - } else { - var path = parts[1].trim(); - var dir = path.isNotEmpty ? Directory(path) : rootDir; - return dir != null ? MapEntry(domain, dir) : null; - } - }) - .nonNulls - .toMapFromEntries(); + var domainsRoots = domains + .map((e) { + var s = e.toString(); + var parts = s.split('='); + + var domain = parts[0]; + + if (parts.length == 1) { + return rootDir != null ? MapEntry(domain, rootDir) : null; + } else { + var path = parts[1].trim(); + var dir = path.isNotEmpty ? Directory(path) : rootDir; + return dir != null ? MapEntry(domain, dir) : null; + } + }) + .nonNulls + .toMapFromEntries(); return domainsRoots; } @@ -706,24 +705,23 @@ class APIServerConfig { } } - List files = - list - .map((e) { - if (e is RegExp) return e; + List files = list + .map((e) { + if (e is RegExp) return e; - var s = e.toString().trim(); - if (s.isEmpty) return null; + var s = e.toString().trim(); + if (s.isEmpty) return null; - if (s.startsWith('re:')) { - return RegExp(s.substring(3)); - } else if (s.startsWith('regexp:')) { - return RegExp(s.substring(7)); - } else { - return s; - } - }) - .nonNulls - .toList(); + if (s.startsWith('re:')) { + return RegExp(s.substring(3)); + } else if (s.startsWith('regexp:')) { + return RegExp(s.substring(7)); + } else { + return s; + } + }) + .nonNulls + .toList(); if (list.isEmpty) { return def ?? []; @@ -857,16 +855,15 @@ class APIServerConfig { values = [o]; } - var entries = - values - .map( - (e) => parseDomainEntry( - e, - checkDirectoryExistence: checkDirectoryExistence, - ), - ) - .nonNulls - .toList(); + var entries = values + .map( + (e) => parseDomainEntry( + e, + checkDirectoryExistence: checkDirectoryExistence, + ), + ) + .nonNulls + .toList(); if (entries.isEmpty) return {}; var map = _removeInvalidDomains( @@ -978,14 +975,13 @@ class APIServerConfig { 'apiCacheControl': apiCacheControl, 'staticFilesCacheControl': staticFilesCacheControl, 'longLivedStaticFilesCacheControl': longLivedStaticFilesCacheControl, - 'longLivedStaticFilesCached': - longLivedStaticFilesCached.map((e) { - if (e is RegExp) { - return 'regexp:${e.pattern}'; - } else { - return e.toString(); - } - }).toList(), + 'longLivedStaticFilesCached': longLivedStaticFilesCached.map((e) { + if (e is RegExp) { + return 'regexp:${e.pattern}'; + } else { + return e.toString(); + } + }).toList(), 'cacheStaticFilesResponses': cacheStaticFilesResponses, 'staticFilesCacheMaxMemorySize': staticFilesCacheMaxMemorySize, 'staticFilesCacheMaxContentLength': staticFilesCacheMaxContentLength, @@ -1265,11 +1261,10 @@ class APIServer extends _APIServerBase { if (totalAuxiliaryWorkers >= 1) { _log.info("Spawning $totalAuxiliaryWorkers parallel workers..."); - workersSpawns = - await _auxiliaryWorkers - .map((e) => MapEntry(e, e.spawnIsolate())) - .toMapFromEntries() - .resolveAllValues(); + workersSpawns = await _auxiliaryWorkers + .map((e) => MapEntry(e, e.spawnIsolate())) + .toMapFromEntries() + .resolveAllValues(); _log.info("Spawned $totalAuxiliaryWorkers parallel workers."); } @@ -1391,12 +1386,11 @@ class APIServer extends _APIServerBase { var connectionInfo = _getConnectionInfo(request); var requesterAddress = connectionInfo?.remoteAddress.address; - var requesterSource = - requesterAddress == null - ? APIRequesterSource.unknown - : (_isLocalAddress(requesterAddress) - ? APIRequesterSource.local - : APIRequesterSource.remote); + var requesterSource = requesterAddress == null + ? APIRequesterSource.unknown + : (_isLocalAddress(requesterAddress) + ? APIRequesterSource.local + : APIRequesterSource.remote); String? sessionID; bool newSession = false; @@ -1439,10 +1433,9 @@ class APIServer extends _APIServerBase { if (mimeType != null && payload != null && mimeType.isFormURLEncoded) { var payloadMap = payload as Map; - parametersResolved = - parameters.isEmpty - ? payloadMap - : {...parameters, ...payloadMap}; + parametersResolved = parameters.isEmpty + ? payloadMap + : {...parameters, ...payloadMap}; payload = null; mimeType = null; @@ -1495,8 +1488,9 @@ class APIServer extends _APIServerBase { return cookies; } - static final MimeType _mimeTypeTextPlain = - MimeType.parse(MimeType.textPlain)!; + static final MimeType _mimeTypeTextPlain = MimeType.parse( + MimeType.textPlain, + )!; static FutureOr<(MimeType, Object)?> _resolvePayload( Request request, { @@ -1555,11 +1549,10 @@ class APIServer extends _APIServerBase { var requestedUri = request.requestedUri; mimeType = _resolveMimeTypeByExtension(requestedUri.path); - mimeType ??= - requestedUri.queryParameters.entries - .map((e) => _resolveMimeTypeByExtension(e.value)) - .nonNulls - .firstOrNull; + mimeType ??= requestedUri.queryParameters.entries + .map((e) => _resolveMimeTypeByExtension(e.value)) + .nonNulls + .firstOrNull; } return mimeType; @@ -1580,34 +1573,35 @@ class APIServer extends _APIServerBase { Request request, { int? maxPayloadLength, bool? decompressPayload, - }) => _loadPayloadString( - mimeType, - request, - maxPayloadLength: maxPayloadLength, - decompressPayload: decompressPayload, - ).then((s) { - if (s == null) return null; - - Object payload = s; - if (mimeType.isJSON) { - s = s.trim(); - if (s.isEmpty) return null; - try { - payload = json.decode(s); - } catch (e, s) { - _log.severe( - "_resolvePayloadFromString> Error decoding JSON: <<$s>>", - e, - s, - ); - return null; - } - } else if (mimeType.isFormURLEncoded) { - payload = decodeQueryStringParameters(s, charset: mimeType.charset); - } + }) => + _loadPayloadString( + mimeType, + request, + maxPayloadLength: maxPayloadLength, + decompressPayload: decompressPayload, + ).then((s) { + if (s == null) return null; + + Object payload = s; + if (mimeType.isJSON) { + s = s.trim(); + if (s.isEmpty) return null; + try { + payload = json.decode(s); + } catch (e, s) { + _log.severe( + "_resolvePayloadFromString> Error decoding JSON: <<$s>>", + e, + s, + ); + return null; + } + } else if (mimeType.isFormURLEncoded) { + payload = decodeQueryStringParameters(s, charset: mimeType.charset); + } - return (mimeType, payload); - }); + return (mimeType, payload); + }); static Future _loadPayloadString( MimeType mimeType, @@ -1762,8 +1756,10 @@ class APIServer extends _APIServerBase { var idx = headerAuthorization.indexOf(_regExpSpace); if (idx <= 0) return null; - var credentialType = - headerAuthorization.substring(0, idx).trim().toLowerCase(); + var credentialType = headerAuthorization + .substring(0, idx) + .trim() + .toLowerCase(); var credential = headerAuthorization.substring(idx + 1).trim(); if (credentialType == 'basic') { @@ -1972,12 +1968,11 @@ class APIServer extends _APIServerBase { Json.encodeToSink( payload, bytesSink, - toEncodableProvider: - (o) => accessRules.toJsonEncodable( - apiRequest, - Json.defaultToEncodableJsonProvider(), - o, - ), + toEncodableProvider: (o) => accessRules.toJsonEncodable( + apiRequest, + Json.defaultToEncodableJsonProvider(), + o, + ), ); return _resolveJsonEncodePayloadBytes( @@ -2049,28 +2044,25 @@ class APIServer extends _APIServerBase { @override String toString() { - var serverResponseDelayStr = - serverResponseDelay != null - ? ', serverResponseDelay: ${serverResponseDelay!.toStringUnit()}' - : ''; - - var domainsStr = - domainsRoots.isNotEmpty - ? ', domains: [${domainsRoots.entries.map((e) { - var key = e.key; - var val = e.value; - - return '${key is RegExp ? 'r/${key.pattern}/' : '`$key`'}=${val.path}'; - }).join(' ; ')}]' - : ''; - - var secureStr = - securePort < 10 - ? '' - : ', securePort: $securePort, ' - 'letsEncrypt: $letsEncrypt' - '${(letsEncrypt ? (letsEncryptProduction ? ' @production' : ' @staging') : '')}, ' - 'letsEncryptDirectory: ${letsEncryptDirectory?.path}'; + var serverResponseDelayStr = serverResponseDelay != null + ? ', serverResponseDelay: ${serverResponseDelay!.toStringUnit()}' + : ''; + + var domainsStr = domainsRoots.isNotEmpty + ? ', domains: [${domainsRoots.entries.map((e) { + var key = e.key; + var val = e.value; + + return '${key is RegExp ? 'r/${key.pattern}/' : '`$key`'}=${val.path}'; + }).join(' ; ')}]' + : ''; + + var secureStr = securePort < 10 + ? '' + : ', securePort: $securePort, ' + 'letsEncrypt: $letsEncrypt' + '${(letsEncrypt ? (letsEncryptProduction ? ' @production' : ' @staging') : '')}, ' + 'letsEncryptDirectory: ${letsEncryptDirectory?.path}'; return 'APIServer[${BonesAPI.VERSION}]{ ' 'apiRoot: ${apiRoot.name}[${apiRoot.version}] (${apiRoot.runtimeTypeNameUnsafe}), ' @@ -2128,15 +2120,14 @@ class APIServer extends _APIServerBase { address = _parseArg(args, 'address', 'a', 'localhost', 0); port = int.parse(_parseArg(args, 'port', 'p', '8080', 1)); - var hotReloadStr = - _parseArg( - args, - 'hotreload', - 'r', - 'false', - 2, - flag: true, - ).toLowerCase(); + var hotReloadStr = _parseArg( + args, + 'hotreload', + 'r', + 'false', + 2, + flag: true, + ).toLowerCase(); hotReload = hotReloadStr == 'true' || hotReloadStr == 'hotreload'; @@ -2338,8 +2329,8 @@ final class APIServerWorker extends _APIServerBase { final Map _directoriesStaticHandlers = {}; Handler _getDirectoryStaticHandler(Directory rootDirectory) => - _directoriesStaticHandlers[rootDirectory - .path] ??= _createDirectoryHandler(rootDirectory); + _directoriesStaticHandlers[rootDirectory.path] ??= + _createDirectoryHandler(rootDirectory); APIServerResponseCache? _responseCache; @@ -2351,11 +2342,10 @@ final class APIServerWorker extends _APIServerBase { var gzipCompressionLevel = 4; if (cacheStaticFilesResponses) { - var responseCache = - _responseCache = APIServerResponseCache( - maxMemorySize: staticFilesCacheMaxMemorySize, - maxContentLength: staticFilesCacheMaxContentLength, - ); + var responseCache = _responseCache = APIServerResponseCache( + maxMemorySize: staticFilesCacheMaxMemorySize, + maxContentLength: staticFilesCacheMaxContentLength, + ); pipeline = pipeline.addMiddleware(responseCache.middleware); @@ -2630,10 +2620,9 @@ final class APIServerWorker extends _APIServerBase { _log.info('Using `SESSIONID` cookies.'); } - var workerInfo = - totalWorkers > 1 - ? '[${isMainWorker ? 'main' : 'auxiliary'} worker#$workerIndex/$totalWorkers]' - : ''; + var workerInfo = totalWorkers > 1 + ? '[${isMainWorker ? 'main' : 'auxiliary'} worker#$workerIndex/$totalWorkers]' + : ''; _log.info('Started HTTP Server$workerInfo at: $address:$port'); @@ -3157,10 +3146,9 @@ final class APIServerWorker extends _APIServerBase { headers['keep-alive'] = 'timeout=$timeout, max=$max'; } - var allMetrics = - apiRequest.hasMetrics - ? CombinedMapView([apiRequest.metrics, apiResponse.metrics]) - : apiResponse.metrics; + var allMetrics = apiRequest.hasMetrics + ? CombinedMapView([apiRequest.metrics, apiResponse.metrics]) + : apiResponse.metrics; headers['server-timing'] = APIServer.resolveServerTiming( allMetrics, @@ -3281,23 +3269,21 @@ final class APIServerWorker extends _APIServerBase { @override String toString() { - var domainsStr = - domainsRoots.isNotEmpty - ? ', domains: [${domainsRoots.entries.map((e) { - var key = e.key; - var val = e.value; - - return '${key is RegExp ? 'r/${key.pattern}/' : '`$key`'}=${val.path}'; - }).join(' ; ')}]' - : ''; - - var secureStr = - securePort < 10 - ? '' - : ', securePort: $securePort, ' - 'letsEncrypt: $letsEncrypt' - '${(letsEncrypt ? (letsEncryptProduction ? ' @production' : ' @staging') : '')}, ' - 'letsEncryptDirectory: ${letsEncryptDirectory?.path}'; + var domainsStr = domainsRoots.isNotEmpty + ? ', domains: [${domainsRoots.entries.map((e) { + var key = e.key; + var val = e.value; + + return '${key is RegExp ? 'r/${key.pattern}/' : '`$key`'}=${val.path}'; + }).join(' ; ')}]' + : ''; + + var secureStr = securePort < 10 + ? '' + : ', securePort: $securePort, ' + 'letsEncrypt: $letsEncrypt' + '${(letsEncrypt ? (letsEncryptProduction ? ' @production' : ' @staging') : '')}, ' + 'letsEncryptDirectory: ${letsEncryptDirectory?.path}'; return 'APIServerWorker{ apiRoot: ${apiRoot.name}[${apiRoot.version}] (${apiRoot.runtimeTypeNameUnsafe}), address: $address, port: $port$secureStr, cacheStaticFilesResponses: $cacheStaticFilesResponses, hotReload: $hotReload (${APIHotReload.get().isEnabled ? 'enabled' : 'disabled'}), cookieless: $cookieless, SESSIONID: $useSessionID, started: $isStarted, stopped: $isStopped$domainsStr }'; } diff --git a/lib/src/bones_api_server_cache.dart b/lib/src/bones_api_server_cache.dart index a113b853..2719f17b 100644 --- a/lib/src/bones_api_server_cache.dart +++ b/lib/src/bones_api_server_cache.dart @@ -1312,9 +1312,9 @@ extension _MapMemorySize on Map? { return self == null ? 0 : 8 + - self.entries - .map((e) => 32 + e.key.memorySize + e.value.memorySize) - .sum; + self.entries + .map((e) => 32 + e.key.memorySize + e.value.memorySize) + .sum; } } @@ -1324,9 +1324,9 @@ extension _MapStringObjectMemorySize on Map? { return self == null ? 0 : 8 + - self.entries - .map((e) => 32 + e.key.memorySize + e.value.memorySize) - .sum; + self.entries + .map((e) => 32 + e.key.memorySize + e.value.memorySize) + .sum; } } diff --git a/lib/src/bones_api_sql_builder.dart b/lib/src/bones_api_sql_builder.dart index 0b40bc9c..e4c97240 100644 --- a/lib/src/bones_api_sql_builder.dart +++ b/lib/src/bones_api_sql_builder.dart @@ -93,6 +93,14 @@ class SQLDialect extends DBDialect { /// If `true` indicates that this adapter's SQL needs a temporary table to return rows for inserts/deletes. final bool acceptsTemporaryTableForReturning; + /// If `true` a `RETURNING` clause can select all the columns of a table + /// qualified by its alias (`RETURNING "alias".*`). + /// + /// SQLite only accepts the unqualified wildcard (`RETURNING *`), rejecting + /// the qualified form with *"RETURNING may not use TABLE.\* wildcards"*. + /// Only meaningful when [acceptsReturningSyntax] is `true`. + final bool returningAcceptsTableWildcard; + /// If `true` indicates that this adapter's SQL can use the `DEFAULT VALUES` directive for inserts. final bool acceptsInsertDefaultValues; @@ -129,6 +137,7 @@ class SQLDialect extends DBDialect { this.acceptsOutputSyntax = false, this.acceptsReturningSyntax = false, this.acceptsTemporaryTableForReturning = false, + this.returningAcceptsTableWildcard = true, this.acceptsInsertDefaultValues = false, this.acceptsInsertIgnore = false, this.acceptsInsertOnConflict = false, @@ -195,6 +204,7 @@ class SQLDialect extends DBDialect { 'acceptsOutputSyntax: $acceptsOutputSyntax, ' 'acceptsReturningSyntax: $acceptsReturningSyntax, ' 'acceptsTemporaryTableForReturning: $acceptsTemporaryTableForReturning, ' + 'returningAcceptsTableWildcard: $returningAcceptsTableWildcard, ' 'acceptsInsertDefaultValues: $acceptsInsertDefaultValues, ' 'acceptsInsertIgnore: $acceptsInsertIgnore, ' 'acceptsInsertOnConflict: $acceptsInsertOnConflict, ' @@ -243,12 +253,9 @@ abstract class SQLBuilder implements Comparable { /// Build all the `SQL`s, including the [extraSQLBuilders]. List buildAllSQLs({bool multiline = true, bool ifNotExists = true}) { var allSQLBuilders = this.allSQLBuilders; - var sqls = - allSQLBuilders - .map( - (e) => e.buildSQL(multiline: multiline, ifNotExists: ifNotExists), - ) - .toList(); + var sqls = allSQLBuilders + .map((e) => e.buildSQL(multiline: multiline, ifNotExists: ifNotExists)) + .toList(); return sqls; } } @@ -383,29 +390,27 @@ class CreateTableSQL extends TableSQL { List? _referenceTables; @override - List get referenceTables => - _referenceTables ??= - { - ...entries.expand((e) => e.referenceTables ?? []), - }.toList(); + List get referenceTables => _referenceTables ??= { + ...entries.expand((e) => e.referenceTables ?? []), + }.toList(); List? _relationshipsTables; /// Returns the tables of the [relationships]. - List get relationshipsTables => - _relationshipsTables ??= - { - ...?relationships?.expand( - (e) => e.referenceTables.where((t) => t != table), - ), - }.toList(); + List get relationshipsTables => _relationshipsTables ??= { + ...?relationships?.expand( + (e) => e.referenceTables.where((t) => t != table), + ), + }.toList(); List? _referenceAndRelationshipTables; /// Returns the tables in [referenceTables] and [relationshipsTables]. List get referenceAndRelationshipTables => - _referenceAndRelationshipTables ??= - {...referenceTables, ...relationshipsTables}.toList(); + _referenceAndRelationshipTables ??= { + ...referenceTables, + ...relationshipsTables, + }.toList(); @override List get dependentTables => referenceAndRelationshipTables; @@ -436,10 +441,9 @@ class CreateTableSQL extends TableSQL { var constraints = this.constraints; if (onlyDependents) { - constraints = - constraints - .where((c) => c.referenceTables?.any((t) => t != table) ?? false) - .toList(); + constraints = constraints + .where((c) => c.referenceTables?.any((t) => t != table) ?? false) + .toList(); } var alterTablesConstraint = []; @@ -591,11 +595,9 @@ class AlterTableSQL extends TableSQL { List? _referenceTables; @override - List get referenceTables => - _referenceTables ??= - { - ...entries.expand((e) => e.referenceTables ?? []).nonNulls, - }.toList(); + List get referenceTables => _referenceTables ??= { + ...entries.expand((e) => e.referenceTables ?? []).nonNulls, + }.toList(); @override List get dependentTables => referenceTables; @@ -698,13 +700,12 @@ extension SQLBuilderIterableMapEntryExtension var allSQLs = expand((e) => e.value.allSQLBuilders).toList(); allSQLs.bestOrder(); - var ordered = - sorted((a, b) { - var i1 = allSQLs.indexOf(a.value); - var i2 = allSQLs.indexOf(b.value); - var cmp = i1.compareTo(i2); - return cmp; - }).toList(); + var ordered = sorted((a, b) { + var i1 = allSQLs.indexOf(a.value); + var i2 = allSQLs.indexOf(b.value); + var cmp = i1.compareTo(i2); + return cmp; + }).toList(); return ordered; } @@ -716,13 +717,12 @@ extension SQLBuilderIterableMapEntryExtension (e) => e.value.allSQLBuilders, ).toList().toHierarchicalOrder(verbose: verbose); - var ordered = - sorted((a, b) { - var i1 = allSQLs.indexOf(a.value); - var i2 = allSQLs.indexOf(b.value); - var cmp = i1.compareTo(i2); - return cmp; - }).toList(); + var ordered = sorted((a, b) { + var i1 = allSQLs.indexOf(a.value); + var i2 = allSQLs.indexOf(b.value); + var cmp = i1.compareTo(i2); + return cmp; + }).toList(); return ordered; } @@ -747,14 +747,14 @@ extension SQLBuilderListExtension on List { for (var sql in this) { if (processed.add(sql)) { var dependentTables = sql.dependentTables; - var dependentSQLs = - dependentTables - ?.map((t) => createTables.getCreateTable(t)) - .nonNulls - .toList(); + var dependentSQLs = dependentTables + ?.map((t) => createTables.getCreateTable(t)) + .nonNulls + .toList(); - var unprocessedDependencies = - dependentSQLs?.where((s) => !processed.contains(s)).toList(); + var unprocessedDependencies = dependentSQLs + ?.where((s) => !processed.contains(s)) + .toList(); if (unprocessedDependencies != null && unprocessedDependencies.isNotEmpty) { @@ -843,10 +843,9 @@ extension SQLBuilderListExtension on List { : refTables.where((r) => _indexOfTable(r, 0) != null).toList(); } - Map> _entriesReferences() => - map( - (e) => MapEntry(e, _referenceTablesInList(e.referenceTables)), - ).toMapFromEntries(); + Map> _entriesReferences() => map( + (e) => MapEntry(e, _referenceTablesInList(e.referenceTables)), + ).toMapFromEntries(); Map> _entriesRelationships() => whereType() @@ -883,21 +882,20 @@ extension SQLBuilderListExtension on List { final entriesReferences = _entriesReferences(); final entriesRelationships = _entriesRelationships(); - var withParents = - whereType().where((e) => e.parentTable != null).toList(); + var withParents = whereType() + .where((e) => e.parentTable != null) + .toList(); removeAll(withParents); - var withRelationship = - whereType() - .where((e) => e.relationshipsTables.isNotEmpty) - .toList(); + var withRelationship = whereType() + .where((e) => e.relationshipsTables.isNotEmpty) + .toList(); removeAll(withRelationship); - var withReference = - where((e) { - var referenceTables = e.referenceTables; - return referenceTables != null && referenceTables.isNotEmpty; - }).toList(); + var withReference = where((e) { + var referenceTables = e.referenceTables; + return referenceTables != null && referenceTables.isNotEmpty; + }).toList(); removeAll(withReference); withParents._bestOrderLoop( @@ -917,10 +915,9 @@ extension SQLBuilderListExtension on List { _bestOrderLoop(); - refsGetter(SQLBuilder e) => - e is CreateTableSQL - ? e.referenceAndRelationshipTables - : e.referenceTables ?? []; + refsGetter(SQLBuilder e) => e is CreateTableSQL + ? e.referenceAndRelationshipTables + : e.referenceTables ?? []; _addByRefPos(withReference, addAtEnd: true); _addByRefPos(withRelationship, addAtEnd: true, refsGetter: refsGetter); @@ -1254,11 +1251,10 @@ extension SQLBuilderListExtension on List { graph.populate( this, inputsProvider: (step, sql) => getCreateTables(sql.dependentTables), - outputsProvider: - (step, sql) => [ - ...?sql.extraSQLBuilders, - //if (sql is CreateTableSQL) ...getCreateTables(sql.relationshipsTables) - ], + outputsProvider: (step, sql) => [ + ...?sql.extraSQLBuilders, + //if (sql is CreateTableSQL) ...getCreateTables(sql.relationshipsTables) + ], ); return graph; @@ -1271,15 +1267,14 @@ extension SQLBuilderListExtension on List { var graph = toGraph(); - var sqlBuildOrder = - graph - .walkOutputsOrderFrom( - graph.rootValues, - sortByInputDependency: true, - expandSideRoots: true, - maxExpansion: 1, - ) - .toListOfValues(); + var sqlBuildOrder = graph + .walkOutputsOrderFrom( + graph.rootValues, + sortByInputDependency: true, + expandSideRoots: true, + maxExpansion: 1, + ) + .toListOfValues(); var invalidSQLsOrders = sqlBuildOrder.invalidSQLsOrder(); @@ -1510,11 +1505,10 @@ abstract mixin class SQLGenerator { var idName = entityHandler.idFieldName(); var idType = entityHandler.idType(); - var idEntityAnnotations = - entityHandler - .getFieldEntityAnnotations(null, idName) - ?.whereType() - .toList(); + var idEntityAnnotations = entityHandler + .getFieldEntityAnnotations(null, idName) + ?.whereType() + .toList(); var sqlType = foreignKeyTypeToSQLType( TypeInfo.fromType(idType), @@ -1821,11 +1815,10 @@ abstract mixin class SQLGenerator { var idType = entityHandler.idType(); var idColumnName = normalizeColumnName(idFieldName); - var idAnnotations = - entityHandler - .getFieldEntityAnnotations(null, idFieldName) - ?.whereType() - .toList(); + var idAnnotations = entityHandler + .getFieldEntityAnnotations(null, idFieldName) + ?.whereType() + .toList(); var idTypeSQL = primaryKeyTypeToSQLType( idType, @@ -1843,12 +1836,11 @@ abstract mixin class SQLGenerator { var indexSQLs = []; - var fieldsEntries = - entityHandler - .fieldsTypes() - .entries - .where((e) => !e.value.isListEntityOrReference) - .toList(); + var fieldsEntries = entityHandler + .fieldsTypes() + .entries + .where((e) => !e.value.isListEntityOrReference) + .toList(); if (sortColumns) { fieldsEntries.sort((a, b) => a.key.compareTo(b.key)); @@ -1862,11 +1854,10 @@ abstract mixin class SQLGenerator { var fieldType = e.value; if (fieldName == idFieldName) continue; - var entityFieldAnnotations = - entityHandler - .getFieldEntityAnnotations(null, fieldName) - ?.whereType() - .toList(); + var entityFieldAnnotations = entityHandler + .getFieldEntityAnnotations(null, fieldName) + ?.whereType() + .toList(); if (entityFieldAnnotations != null && entityFieldAnnotations.hasHidden) { continue; @@ -2033,22 +2024,20 @@ abstract mixin class SQLGenerator { var relationshipSQLs = []; - var relationshipEntries = - entityHandler - .fieldsTypes() - .entries - .where((e) => e.value.isListEntityOrReference) - .toList(); + var relationshipEntries = entityHandler + .fieldsTypes() + .entries + .where((e) => e.value.isListEntityOrReference) + .toList(); for (var e in relationshipEntries) { var fieldName = e.key; var fieldType = e.value.arguments0!; - var entityFieldAnnotations = - entityHandler - .getFieldEntityAnnotations(null, fieldName) - ?.whereType() - .toList(); + var entityFieldAnnotations = entityHandler + .getFieldEntityAnnotations(null, fieldName) + ?.whereType() + .toList(); var columnName = normalizeColumnName(fieldName); diff --git a/lib/src/bones_api_test_utils_config.dart b/lib/src/bones_api_test_utils_config.dart index 4da8cf78..327f5715 100644 --- a/lib/src/bones_api_test_utils_config.dart +++ b/lib/src/bones_api_test_utils_config.dart @@ -360,11 +360,10 @@ class APITestConfigDBSQLMemory extends APITestConfigDB with APITestConfigBase { var tables = allRepositories.map((e) => e.name).toList(); - var tablesSchemes = - await tables - .map((t) => sqlAdapter.getTableScheme(t)) - .toList() - .resolveAll(); + var tablesSchemes = await tables + .map((t) => sqlAdapter.getTableScheme(t)) + .toList() + .resolveAll(); var relationshipTables = tablesSchemes.nonNulls.expand( (e) => e.tableRelationshipReference.values.expand((e) => e), diff --git a/lib/src/bones_api_test_utils_postgres.dart b/lib/src/bones_api_test_utils_postgres.dart index 9c6961d3..da9fb8b3 100644 --- a/lib/src/bones_api_test_utils_postgres.dart +++ b/lib/src/bones_api_test_utils_postgres.dart @@ -72,11 +72,10 @@ class APITestConfigDockerPostgreSQL var body = res.split(RegExp(r'--+\+--+\+--+\+'))[1]; var parts = body.split(RegExp(r'[\r\n]')); - var names = - parts - .where((p) => p.contains(RegExp(r'\s+\|\s+\w+\s+\|'))) - .map((p) => p.split(RegExp(r'\s+\|\s+'))[1].trim()) - .toList(); + var names = parts + .where((p) => p.contains(RegExp(r'\s+\|\s+\w+\s+\|'))) + .map((p) => p.split(RegExp(r'\s+\|\s+'))[1].trim()) + .toList(); return names; } diff --git a/lib/src/bones_api_test_utils_sqlite.dart b/lib/src/bones_api_test_utils_sqlite.dart new file mode 100644 index 00000000..b58ace05 --- /dev/null +++ b/lib/src/bones_api_test_utils_sqlite.dart @@ -0,0 +1,125 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:logging/logging.dart' as logging; + +import 'bones_api_entity_db_sqlite.dart'; +import 'bones_api_test_utils_config.dart'; +import 'bones_api_utils.dart'; + +final _log = logging.Logger('APITestConfigSQLite'); + +/// A [APITestConfigDBSQL] for a SQLite database. +/// +/// Unlike the PostgreSQL and MySQL test configurations this needs no Docker +/// container: SQLite is embedded, so [start] only decides *where* the database +/// lives — an in-memory database, or a file in a temporary directory that +/// [stop] deletes. +class APITestConfigSQLite extends APITestConfigDBSQL + implements WithRuntimeTypeNameSafe { + @override + String get runtimeTypeNameSafe => 'APITestConfigSQLite'; + + /// If `true` uses an in-memory database instead of a temporary file. + final bool memory; + + /// The adapter under test. + /// + /// [runSQL] and [listTables] must reach the *same* database as the entity + /// tests, and an in-memory database is only reachable through the adapter + /// that owns its handle, so the test's adapter creator has to assign this. + DBSQLiteAdapter? sqlAdapter; + + Directory? _tempDir; + + APITestConfigSQLite(Map apiConfig, {this.memory = false}) + : super('SQLite', apiConfig) { + DBSQLiteAdapter.boot(); + } + + @override + Map get dbConfig { + var db = apiConfigMap['db']; + if (db is! Map) { + db = apiConfigMap['db'] = {}; + } + + var sqlite = db['sqlite']; + if (sqlite is! Map) { + sqlite = db['sqlite'] = {}; + } + + return sqlite as Map; + } + + /// SQLite is embedded: there is no port to allocate. + @override + FutureOr resolveFreePort(int port) => 0; + + /// The resolved database path (`:memory:` when [memory]). + String get databasePath => dbConfig['path']?.toString() ?? sqliteMemoryPath; + + @override + FutureOr start() { + var config = dbConfig; + + if (memory) { + config['memory'] = true; + config['path'] = sqliteMemoryPath; + } else { + var dir = _tempDir ??= Directory.systemTemp.createTempSync( + 'bones_api_test_sqlite_', + ); + config['memory'] = false; + config['path'] = '${dir.path}/test.db'; + } + + _log.info('** STARTED> $this'); + + return true; + } + + @override + FutureOr stop() { + sqlAdapter = null; + + var dir = _tempDir; + if (dir != null) { + _tempDir = null; + try { + dir.deleteSync(recursive: true); + } catch (e) { + _log.warning("Error deleting temporary directory: ${dir.path}", e); + } + } + + return true; + } + + @override + Future runSQL(String sqlInline) async { + var sqlAdapter = _requireAdapter(); + sqlAdapter.executeRawSQL(sqlInline); + return ''; + } + + @override + Future> listTables() async => + _requireAdapter().listTablesNames(); + + DBSQLiteAdapter _requireAdapter() { + var sqlAdapter = this.sqlAdapter; + if (sqlAdapter == null) { + throw StateError( + "Null `sqlAdapter`: the test's adapter creator must assign " + "`APITestConfigSQLite.sqlAdapter`, so that `runSQL` and `listTables` " + "reach the same database.", + ); + } + return sqlAdapter; + } + + @override + String toString() => + '$runtimeTypeNameSafe{${memory ? 'memory' : databasePath}}'; +} diff --git a/lib/src/bones_api_types.dart b/lib/src/bones_api_types.dart index fac65c05..97a29c7c 100644 --- a/lib/src/bones_api_types.dart +++ b/lib/src/bones_api_types.dart @@ -416,28 +416,27 @@ class Time implements Comparable