Merge branch 'fix-bug/mysql8' into release/5.0.0

This commit is contained in:
thomashii 2022-02-14 08:20:39 +08:00
commit 35bf04b168
71 changed files with 572 additions and 309 deletions

View file

@ -43,7 +43,7 @@ class Controller {
var name = await applyRoutes(app, app.container!.reflector); var name = await applyRoutes(app, app.container!.reflector);
app.controllers[name] = this; app.controllers[name] = this;
return null; //return null;
} }
/// Applies the routes from this [Controller] to some [router]. /// Applies the routes from this [Controller] to some [router].

View file

@ -76,7 +76,9 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
/// Closes any open [StreamController]s on this instance. **Internal use only**. /// Closes any open [StreamController]s on this instance. **Internal use only**.
@override @override
Future close() { Future close() {
_ctrl.forEach((c) => c.close()); for (var c in _ctrl) {
c.close();
}
beforeIndexed._close(); beforeIndexed._close();
beforeRead._close(); beforeRead._close();
beforeCreated._close(); beforeCreated._close();
@ -144,8 +146,8 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
}); });
@override @override
void addRoutes([Service? s]) { void addRoutes([Service? service]) {
super.addRoutes(s ?? inner); super.addRoutes(service ?? inner);
} }
/// Runs the [listener] before every service method specified. /// Runs the [listener] before every service method specified.
@ -274,162 +276,162 @@ class HookedService<Id, Data, T extends Service<Id, Data>>
} }
@override @override
Future<List<Data>> index([Map<String, dynamic>? _params]) { Future<List<Data>> index([Map<String, dynamic>? params]) {
var params = _stripReq(_params); var localParams = _stripReq(params);
return beforeIndexed return beforeIndexed
._emit(HookedServiceEvent(false, _getRequest(_params), ._emit(HookedServiceEvent(false, _getRequest(params),
_getResponse(_params), inner, HookedServiceEvent.indexed, _getResponse(params), inner, HookedServiceEvent.indexed,
params: params)) params: localParams))
.then((before) { .then((before) {
if (before._canceled) { if (before._canceled) {
return afterIndexed return afterIndexed
._emit(HookedServiceEvent(true, _getRequest(_params), ._emit(HookedServiceEvent(true, _getRequest(params),
_getResponse(_params), inner, HookedServiceEvent.indexed, _getResponse(params), inner, HookedServiceEvent.indexed,
params: params, result: before.result)) params: localParams, result: before.result))
.then((after) => after.result as List<Data>); .then((after) => after.result as List<Data>);
} }
return inner.index(params).then((result) { return inner.index(localParams).then((result) {
return afterIndexed return afterIndexed
._emit(HookedServiceEvent(true, _getRequest(_params), ._emit(HookedServiceEvent(true, _getRequest(params),
_getResponse(_params), inner, HookedServiceEvent.indexed, _getResponse(params), inner, HookedServiceEvent.indexed,
params: params, result: result)) params: localParams, result: result))
.then((after) => after.result as List<Data>); .then((after) => after.result as List<Data>);
}); });
}); });
} }
@override @override
Future<Data> read(Id id, [Map<String, dynamic>? _params]) { Future<Data> read(Id id, [Map<String, dynamic>? params]) {
var params = _stripReq(_params); var localParams = _stripReq(params);
return beforeRead return beforeRead
._emit(HookedServiceEvent(false, _getRequest(_params), ._emit(HookedServiceEvent(false, _getRequest(params),
_getResponse(_params), inner, HookedServiceEvent.read, _getResponse(params), inner, HookedServiceEvent.read,
id: id, params: params)) id: id, params: localParams))
.then((before) { .then((before) {
if (before._canceled) { if (before._canceled) {
return afterRead return afterRead
._emit(HookedServiceEvent(true, _getRequest(_params), ._emit(HookedServiceEvent(true, _getRequest(params),
_getResponse(_params), inner, HookedServiceEvent.read, _getResponse(params), inner, HookedServiceEvent.read,
id: id, params: params, result: before.result)) id: id, params: localParams, result: before.result))
.then((after) => after.result as Data); .then((after) => after.result as Data);
} }
return inner.read(id, params).then((result) { return inner.read(id, localParams).then((result) {
return afterRead return afterRead
._emit(HookedServiceEvent(true, _getRequest(_params), ._emit(HookedServiceEvent(true, _getRequest(params),
_getResponse(_params), inner, HookedServiceEvent.read, _getResponse(params), inner, HookedServiceEvent.read,
id: id, params: params, result: result)) id: id, params: localParams, result: result))
.then((after) => after.result as Data); .then((after) => after.result as Data);
}); });
}); });
} }
@override @override
Future<Data> create(Data data, [Map<String, dynamic>? _params]) { Future<Data> create(Data data, [Map<String, dynamic>? params]) {
var params = _stripReq(_params); var localParams = _stripReq(params);
return beforeCreated return beforeCreated
._emit(HookedServiceEvent(false, _getRequest(_params), ._emit(HookedServiceEvent(false, _getRequest(params),
_getResponse(_params), inner, HookedServiceEvent.created, _getResponse(params), inner, HookedServiceEvent.created,
data: data, params: params)) data: data, params: localParams))
.then((before) { .then((before) {
if (before._canceled) { if (before._canceled) {
return afterCreated return afterCreated
._emit(HookedServiceEvent(true, _getRequest(_params), ._emit(HookedServiceEvent(true, _getRequest(params),
_getResponse(_params), inner, HookedServiceEvent.created, _getResponse(params), inner, HookedServiceEvent.created,
data: before.data, params: params, result: before.result)) data: before.data, params: localParams, result: before.result))
.then((after) => after.result as Data); .then((after) => after.result as Data);
} }
return inner.create(before.data!, params).then((result) { return inner.create(before.data!, localParams).then((result) {
return afterCreated return afterCreated
._emit(HookedServiceEvent(true, _getRequest(_params), ._emit(HookedServiceEvent(true, _getRequest(params),
_getResponse(_params), inner, HookedServiceEvent.created, _getResponse(params), inner, HookedServiceEvent.created,
data: before.data, params: params, result: result)) data: before.data, params: localParams, result: result))
.then((after) => after.result as Data); .then((after) => after.result as Data);
}); });
}); });
} }
@override @override
Future<Data> modify(Id id, Data data, [Map<String, dynamic>? _params]) { Future<Data> modify(Id id, Data data, [Map<String, dynamic>? params]) {
var params = _stripReq(_params); var localParams = _stripReq(params);
return beforeModified return beforeModified
._emit(HookedServiceEvent(false, _getRequest(_params), ._emit(HookedServiceEvent(false, _getRequest(params),
_getResponse(_params), inner, HookedServiceEvent.modified, _getResponse(params), inner, HookedServiceEvent.modified,
id: id, data: data, params: params)) id: id, data: data, params: localParams))
.then((before) { .then((before) {
if (before._canceled) { if (before._canceled) {
return afterModified return afterModified
._emit(HookedServiceEvent(true, _getRequest(_params), ._emit(HookedServiceEvent(true, _getRequest(params),
_getResponse(_params), inner, HookedServiceEvent.modified, _getResponse(params), inner, HookedServiceEvent.modified,
id: id, id: id,
data: before.data, data: before.data,
params: params, params: localParams,
result: before.result)) result: before.result))
.then((after) => after.result as Data); .then((after) => after.result as Data);
} }
return inner.modify(id, before.data!, params).then((result) { return inner.modify(id, before.data!, localParams).then((result) {
return afterModified return afterModified
._emit(HookedServiceEvent(true, _getRequest(_params), ._emit(HookedServiceEvent(true, _getRequest(params),
_getResponse(_params), inner, HookedServiceEvent.created, _getResponse(params), inner, HookedServiceEvent.created,
id: id, data: before.data, params: params, result: result)) id: id, data: before.data, params: localParams, result: result))
.then((after) => after.result as Data); .then((after) => after.result as Data);
}); });
}); });
} }
@override @override
Future<Data> update(Id id, Data data, [Map<String, dynamic>? _params]) { Future<Data> update(Id id, Data data, [Map<String, dynamic>? params]) {
var params = _stripReq(_params); var localParams = _stripReq(params);
return beforeUpdated return beforeUpdated
._emit(HookedServiceEvent(false, _getRequest(_params), ._emit(HookedServiceEvent(false, _getRequest(params),
_getResponse(_params), inner, HookedServiceEvent.updated, _getResponse(params), inner, HookedServiceEvent.updated,
id: id, data: data, params: params)) id: id, data: data, params: localParams))
.then((before) { .then((before) {
if (before._canceled) { if (before._canceled) {
return afterUpdated return afterUpdated
._emit(HookedServiceEvent(true, _getRequest(_params), ._emit(HookedServiceEvent(true, _getRequest(params),
_getResponse(_params), inner, HookedServiceEvent.updated, _getResponse(params), inner, HookedServiceEvent.updated,
id: id, id: id,
data: before.data, data: before.data,
params: params, params: localParams,
result: before.result)) result: before.result))
.then((after) => after.result as Data); .then((after) => after.result as Data);
} }
return inner.update(id, before.data!, params).then((result) { return inner.update(id, before.data!, localParams).then((result) {
return afterUpdated return afterUpdated
._emit(HookedServiceEvent(true, _getRequest(_params), ._emit(HookedServiceEvent(true, _getRequest(params),
_getResponse(_params), inner, HookedServiceEvent.updated, _getResponse(params), inner, HookedServiceEvent.updated,
id: id, data: before.data, params: params, result: result)) id: id, data: before.data, params: localParams, result: result))
.then((after) => after.result as Data); .then((after) => after.result as Data);
}); });
}); });
} }
@override @override
Future<Data> remove(Id id, [Map<String, dynamic>? _params]) { Future<Data> remove(Id id, [Map<String, dynamic>? params]) {
var params = _stripReq(_params); var localParams = _stripReq(params);
return beforeRemoved return beforeRemoved
._emit(HookedServiceEvent(false, _getRequest(_params), ._emit(HookedServiceEvent(false, _getRequest(params),
_getResponse(_params), inner, HookedServiceEvent.removed, _getResponse(params), inner, HookedServiceEvent.removed,
id: id, params: params)) id: id, params: localParams))
.then((before) { .then((before) {
if (before._canceled) { if (before._canceled) {
return afterRemoved return afterRemoved
._emit(HookedServiceEvent(true, _getRequest(_params), ._emit(HookedServiceEvent(true, _getRequest(params),
_getResponse(_params), inner, HookedServiceEvent.removed, _getResponse(params), inner, HookedServiceEvent.removed,
id: id, params: params, result: before.result)) id: id, params: localParams, result: before.result))
.then((after) => after.result) as Data; .then((after) => after.result) as Data;
} }
return inner.remove(id, params).then((result) { return inner.remove(id, localParams).then((result) {
return afterRemoved return afterRemoved
._emit(HookedServiceEvent(true, _getRequest(_params), ._emit(HookedServiceEvent(true, _getRequest(params),
_getResponse(_params), inner, HookedServiceEvent.removed, _getResponse(params), inner, HookedServiceEvent.removed,
id: id, params: params, result: result)) id: id, params: localParams, result: result))
.then((after) => after.result as Data); .then((after) => after.result as Data);
}); });
}); });
@ -520,7 +522,7 @@ class HookedServiceEvent<Id, Data, T extends Service<Id, Data>> {
Map<String, dynamic>? _params; Map<String, dynamic>? _params;
final RequestContext? _request; final RequestContext? _request;
final ResponseContext? _response; final ResponseContext? _response;
var result; dynamic result;
String get eventName => _eventName; String get eventName => _eventName;
@ -557,7 +559,9 @@ class HookedServiceEventDispatcher<Id, Data, T extends Service<Id, Data>> {
final List<HookedServiceEventListener<Id, Data, T>> listeners = []; final List<HookedServiceEventListener<Id, Data, T>> listeners = [];
void _close() { void _close() {
_ctrl.forEach((c) => c.close()); for (var c in _ctrl) {
c.close();
}
listeners.clear(); listeners.clear();
} }

View file

@ -26,7 +26,7 @@ RequestHandler ioc(Function handler, {Iterable<String> optional = const []}) {
Future resolveInjection(requirement, InjectionRequest injection, Future resolveInjection(requirement, InjectionRequest injection,
RequestContext req, ResponseContext res, bool throwOnUnresolved, RequestContext req, ResponseContext res, bool throwOnUnresolved,
[Container? container]) async { [Container? container]) async {
var propFromApp; dynamic propFromApp;
container ??= req.container ?? res.app!.container; container ??= req.container ?? res.app!.container;
if (requirement == RequestContext) { if (requirement == RequestContext) {

View file

@ -58,7 +58,9 @@ class MapService extends Service<String?, Map<String, dynamic>> {
for (var key in query!.keys) { for (var key in query!.keys) {
if (!item.containsKey(key)) { if (!item.containsKey(key)) {
return false; return false;
} else if (item[key] != query[key]) return false; } else if (item[key] != query[key]) {
return false;
}
} }
return true; return true;
@ -77,11 +79,11 @@ class MapService extends Service<String?, Map<String, dynamic>> {
@override @override
Future<Map<String, dynamic>> create(Map<String, dynamic> data, Future<Map<String, dynamic>> create(Map<String, dynamic> data,
[Map<String, dynamic>? params]) { [Map<String, dynamic>? params]) {
if (data is! Map) { //if (data is! Map) {
throw AngelHttpException.badRequest( // throw AngelHttpException.badRequest(
message: // message:
'MapService does not support `create` with ${data.runtimeType}.'); // 'MapService does not support `create` with ${data.runtimeType}.');
} //}
var now = DateTime.now().toIso8601String(); var now = DateTime.now().toIso8601String();
var result = Map<String, dynamic>.from(data); var result = Map<String, dynamic>.from(data);
@ -98,11 +100,11 @@ class MapService extends Service<String?, Map<String, dynamic>> {
@override @override
Future<Map<String, dynamic>> modify(String? id, Map<String, dynamic> data, Future<Map<String, dynamic>> modify(String? id, Map<String, dynamic> data,
[Map<String, dynamic>? params]) { [Map<String, dynamic>? params]) {
if (data is! Map) { //if (data is! Map) {
throw AngelHttpException.badRequest( // throw AngelHttpException.badRequest(
message: // message:
'MapService does not support `modify` with ${data.runtimeType}.'); // 'MapService does not support `modify` with ${data.runtimeType}.');
} //}
if (!items.any(_matchesId(id))) return create(data, params); if (!items.any(_matchesId(id))) return create(data, params);
return read(id).then((item) { return read(id).then((item) {
@ -121,11 +123,11 @@ class MapService extends Service<String?, Map<String, dynamic>> {
@override @override
Future<Map<String, dynamic>> update(String? id, Map<String, dynamic> data, Future<Map<String, dynamic>> update(String? id, Map<String, dynamic> data,
[Map<String, dynamic>? params]) { [Map<String, dynamic>? params]) {
if (data is! Map) { //if (data is! Map) {
throw AngelHttpException.badRequest( // throw AngelHttpException.badRequest(
message: // message:
'MapService does not support `update` with ${data.runtimeType}.'); // 'MapService does not support `update` with ${data.runtimeType}.');
} //}
if (!items.any(_matchesId(id))) return create(data, params); if (!items.any(_matchesId(id))) return create(data, params);
return read(id).then((old) { return read(id).then((old) {

View file

@ -83,10 +83,10 @@ class Parameter {
final String? query; final String? query;
/// Only execute the handler if the value of this parameter matches the given value. /// Only execute the handler if the value of this parameter matches the given value.
final match; final dynamic match;
/// Specify a default value. /// Specify a default value.
final defaultValue; final dynamic defaultValue;
/// If `true` (default), then an error will be thrown if this parameter is not present. /// If `true` (default), then an error will be thrown if this parameter is not present.
final bool? required; final bool? required;
@ -117,6 +117,8 @@ class Parameter {
if (session?.isNotEmpty == true) { if (session?.isNotEmpty == true) {
return StateError('Session does not contain required key "$session".'); return StateError('Session does not contain required key "$session".');
} }
return null;
} }
/// Obtains a value for this parameter from a [RequestContext]. /// Obtains a value for this parameter from a [RequestContext].

View file

@ -82,8 +82,8 @@ class Angel extends Routable {
/// ///
/// This value is memoized the first time you call it, so do not change environment /// This value is memoized the first time you call it, so do not change environment
/// configuration at runtime! /// configuration at runtime!
@deprecated //@deprecated
bool get isProduction => environment.isProduction; //bool get isProduction => environment.isProduction;
/// The [AngelEnvironment] in which the application is running. /// The [AngelEnvironment] in which the application is running.
/// ///

View file

@ -8,6 +8,7 @@ import 'http2_request_context.dart';
class Http2ResponseContext extends ResponseContext<ServerTransportStream> { class Http2ResponseContext extends ResponseContext<ServerTransportStream> {
@override @override
final Angel? app; final Angel? app;
final ServerTransportStream stream; final ServerTransportStream stream;
@override @override

View file

@ -68,7 +68,7 @@ Future<void> hello(RequestContext req, ResponseContext res) {
var s = Stream<List<int>>.fromIterable([bytes]); var s = Stream<List<int>>.fromIterable([bytes]);
return s.pipe(res); return s.pipe(res);
} else { } else {
return Future.value(true); return Future.value();
} }
} }

View file

@ -47,8 +47,8 @@ void main() {
nested = Angel(reflector: MirrorsReflector()); nested = Angel(reflector: MirrorsReflector());
todos = Angel(reflector: MirrorsReflector()); todos = Angel(reflector: MirrorsReflector());
[app, nested, todos].forEach((Angel? app) { for (var app in [app, nested, todos]) {
app?.logger = Logger('routing_test') app.logger = Logger('routing_test')
..onRecord.listen((rec) { ..onRecord.listen((rec) {
if (rec.error != null) { if (rec.error != null) {
stdout stdout
@ -57,7 +57,7 @@ void main() {
..writeln(cyan.wrap(rec.stackTrace.toString())); ..writeln(cyan.wrap(rec.stackTrace.toString()));
} }
}); });
}); }
todos.get('/action/:action', (req, res) => res.json(req.params)); todos.get('/action/:action', (req, res) => res.json(req.params));

View file

@ -1,5 +1,10 @@
# Change Log # Change Log
## 4.0.4
* Updated README
* Removed deprecated
## 4.0.3 ## 4.0.3
* Updated README * Updated README

View file

@ -10,5 +10,5 @@ A database migration framework built for Angel3 ORM.
## Supported database ## Supported database
* PostgreSQL version 10 or later * PostgreSQL version 10 or later
* MySQL 8.0 or later * MySQL 8.0.x
* MariaDB 10.2.1 or later * MariaDB 10.2.x

View file

@ -19,8 +19,8 @@ abstract class Table {
MigrationColumn date(String name) => declare(name, ColumnType.date); MigrationColumn date(String name) => declare(name, ColumnType.date);
@deprecated //@deprecated
MigrationColumn dateTime(String name) => timeStamp(name, timezone: true); //MigrationColumn dateTime(String name) => timeStamp(name, timezone: true);
MigrationColumn timeStamp(String name, {bool timezone = false}) { MigrationColumn timeStamp(String name, {bool timezone = false}) {
if (timezone != true) return declare(name, ColumnType.timeStamp); if (timezone != true) return declare(name, ColumnType.timeStamp);

View file

@ -1,11 +1,14 @@
name: angel3_migration name: angel3_migration
version: 4.0.3 version: 4.1.0
description: Database migration runtime for Angel3 ORM. Use this package to define schemas. description: Database migration runtime for Angel3 ORM. Use this package to define schemas.
homepage: https://angel3-framework.web.app/ homepage: https://angel3-framework.web.app/
repository: https://github.com/dukefirehawk/angel/tree/master/packages/orm/angel_migration repository: https://github.com/dukefirehawk/angel/tree/master/packages/orm/angel_migration
environment: environment:
sdk: '>=2.12.0 <3.0.0' sdk: '>=2.12.0 <3.0.0'
dependencies: dependencies:
angel3_orm: ^4.0.0 angel3_orm: ^4.1.0
dev_dependencies: dev_dependencies:
lints: ^1.0.0 lints: ^1.0.0
dependency_overrides:
angel3_orm:
path: ../angel_orm

View file

@ -1,5 +1,9 @@
# Change Log # Change Log
## 4.1.2
* Updated README
## 4.1.1 ## 4.1.1
* Updated README * Updated README

View file

@ -10,5 +10,5 @@ Command-line based database migration runner for Angel3 ORM.
Supported database: Supported database:
* PostgreSQL version 10 or later * PostgreSQL version 10 or later
* MySQL 8.0 or later * MySQL 8.0.x
* MariaDB 10.2.1 or later * MariaDB 10.2.x

View file

@ -1,13 +1,13 @@
name: angel3_migration_runner name: angel3_migration_runner
version: 4.1.1 version: 4.1.2
description: Command-line based database migration runner for Angel3's ORM. description: Command-line based database migration runner for Angel3's ORM.
homepage: https://angel3-framework.web.app/ homepage: https://angel3-framework.web.app/
repository: https://github.com/dukefirehawk/angel/tree/master/packages/orm/angel_migration_runner repository: https://github.com/dukefirehawk/angel/tree/master/packages/orm/angel_migration_runner
environment: environment:
sdk: '>=2.12.0 <3.0.0' sdk: '>=2.12.0 <3.0.0'
dependencies: dependencies:
angel3_migration: ^4.0.0 angel3_migration: ^4.1.0
angel3_orm: ^4.0.0 angel3_orm: ^4.1.0
args: ^2.1.0 args: ^2.1.0
charcode: ^1.2.0 charcode: ^1.2.0
postgres: ^2.4.0 postgres: ^2.4.0
@ -15,4 +15,8 @@ dependencies:
logging: ^1.0.0 logging: ^1.0.0
dev_dependencies: dev_dependencies:
lints: ^1.0.0 lints: ^1.0.0
dependency_overrides:
angel3_orm:
path: ../angel_orm
angel3_migration:
path: ../angel_migration

View file

@ -1,5 +1,10 @@
# Change Log # Change Log
## 4.1.0
* Added `MySQLDialect` to handle MySQL database specific features
* Updated `insert` and `update` query to support database without writable CTE
## 4.0.6 ## 4.0.6
* Fixed multiple `orderBy` error * Fixed multiple `orderBy` error

View file

@ -10,6 +10,7 @@ Runtime support for Angel3 ORM. Includes a clean, database-agnostic query builde
## Supported database ## Supported database
* PostgreSQL version 10, 11, 12, 13 and 14 * PostgreSQL version 10, 11, 12, 13 and 14
* MySQL 8.0 or later * MariaDB 10.2.x
* MySQL 8.0.x
For documentation about the ORM, see [Developer Guide](https://angel3-docs.dukefirehawk.com/guides/orm) For documentation about the ORM, see [Developer Guide](https://angel3-docs.dukefirehawk.com/guides/orm)

View file

@ -26,8 +26,9 @@ class _FakeExecutor extends QueryExecutor {
@override @override
Future<List<List>> query( Future<List<List>> query(
String tableName, String? query, Map<String, dynamic> substitutionValues, String tableName, String query, Map<String, dynamic> substitutionValues,
[returningFields = const []]) async { {String returningQuery = '',
List<String> returningFields = const []}) async {
var now = DateTime.now(); var now = DateTime.now();
print( print(
'_FakeExecutor received query: $query and values: $substitutionValues'); '_FakeExecutor received query: $query and values: $substitutionValues');
@ -40,6 +41,11 @@ class _FakeExecutor extends QueryExecutor {
Future<T> transaction<T>(FutureOr<T> Function(QueryExecutor) f) { Future<T> transaction<T>(FutureOr<T> Function(QueryExecutor) f) {
throw UnsupportedError('Transactions are not supported.'); throw UnsupportedError('Transactions are not supported.');
} }
final Dialect _dialect = const PostgreSQLDialect();
@override
Dialect get dialect => _dialect;
} }
@orm @orm

View file

@ -13,3 +13,4 @@ export 'src/query.dart';
export 'src/relations.dart'; export 'src/relations.dart';
export 'src/union.dart'; export 'src/union.dart';
export 'src/util.dart'; export 'src/util.dart';
export 'src/dialect/dialect.dart';

View file

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

View file

@ -0,0 +1,25 @@
abstract class Dialect {
bool get cteSupport;
bool get writableCteSupport;
}
class MySQLDialect implements Dialect {
const MySQLDialect();
@override
bool get cteSupport => true;
@override
bool get writableCteSupport => false;
}
class PostgreSQLDialect implements Dialect {
const PostgreSQLDialect();
@override
bool get cteSupport => true;
@override
bool get writableCteSupport => true;
}

View file

@ -1,13 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:angel3_orm/angel3_orm.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'annotations.dart';
import 'join_builder.dart';
import 'order_by.dart';
import 'query_base.dart';
import 'query_executor.dart';
import 'query_values.dart';
import 'query_where.dart';
import 'package:optional/optional.dart'; import 'package:optional/optional.dart';
/// A SQL `SELECT` query builder. /// A SQL `SELECT` query builder.
@ -70,7 +64,7 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
nn++; nn++;
_names[name] = nn; _names[name] = nn;
} else { } else {
_names[name] = 1; _names[name] = 0; //1;
} }
return n == 0 ? name : '$name$n'; return n == 0 ? name : '$name$n';
} }
@ -298,7 +292,7 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
} }
return ss; return ss;
})); }));
_joins.forEach((j) { for (var j in _joins) {
var c = compiledJoins[j] = j.compile(trampoline); var c = compiledJoins[j] = j.compile(trampoline);
//if (c != null) { //if (c != null) {
if (c != '') { if (c != '') {
@ -310,7 +304,7 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
f.add('NULL'); f.add('NULL');
} }
} }
}); }
} }
if (withFields) b.write(f.join(', ')); if (withFields) b.write(f.join(', '));
fromQuery ??= tableName; fromQuery ??= tableName;
@ -349,12 +343,12 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
Future<List<T>> delete(QueryExecutor executor) { Future<List<T>> delete(QueryExecutor executor) {
var sql = compile({}, preamble: 'DELETE', withFields: false); var sql = compile({}, preamble: 'DELETE', withFields: false);
//_log.fine("Delete Query = $sql"); //_log.warning("Delete Query = $sql");
if (_joins.isEmpty) { if (_joins.isEmpty) {
return executor return executor
.query(tableName, sql, substitutionValues, .query(tableName, sql, substitutionValues,
fields.map(adornWithTableName).toList()) returningFields: fields.map(adornWithTableName).toList())
.then((it) => deserializeList(it)); .then((it) => deserializeList(it));
} else { } else {
return executor.transaction((tx) async { return executor.transaction((tx) async {
@ -379,14 +373,31 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
if (insertion == '') { if (insertion == '') {
throw StateError('No values have been specified for update.'); throw StateError('No values have been specified for update.');
} else { } else {
// TODO: How to do this in a non-Postgres DB?
var returning = fields.map(adornWithTableName).join(', ');
var sql = compile({}); var sql = compile({});
var returningSql = '';
if (executor.dialect is PostgreSQLDialect) {
var returning = fields.map(adornWithTableName).join(', ');
sql = 'WITH $tableName as ($insertion RETURNING $returning) ' + sql; sql = 'WITH $tableName as ($insertion RETURNING $returning) ' + sql;
} else if (executor.dialect is MySQLDialect) {
// Default to using 'id' as primary key in model
if (fields.contains("id")) {
returningSql = '$sql where $tableName.id=?';
} else {
var returningSelect = values?.compileInsertSelect(this, tableName);
returningSql = '$sql where $returningSelect';
}
//_log.fine("Insert Query = $sql"); sql = '$insertion';
} else {
throw ArgumentError("Unsupported database dialect.");
}
return executor.query(tableName, sql, substitutionValues).then((it) { //_log.warning("Insert Query = $sql");
return executor
.query(tableName, sql, substitutionValues,
returningQuery: returningSql)
.then((it) {
// Return SQL execution results // Return SQL execution results
return it.isEmpty ? Optional.empty() : deserialize(it.first); return it.isEmpty ? Optional.empty() : deserialize(it.first);
}); });
@ -399,7 +410,7 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
if (valuesClause == '') { if (valuesClause == '') {
throw StateError('No values have been specified for update.'); throw StateError('No values have been specified for update.');
} else { }
updateSql.write(' $valuesClause'); updateSql.write(' $valuesClause');
var whereClause = where?.compile(); var whereClause = where?.compile();
if (whereClause?.isNotEmpty == true) { if (whereClause?.isNotEmpty == true) {
@ -409,15 +420,21 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
var returning = fields.map(adornWithTableName).join(', '); var returning = fields.map(adornWithTableName).join(', ');
var sql = compile({}); var sql = compile({});
var returningSql = '';
if (executor.dialect is PostgreSQLDialect) {
sql = 'WITH $tableName as ($updateSql RETURNING $returning) ' + sql; sql = 'WITH $tableName as ($updateSql RETURNING $returning) ' + sql;
} else if (executor.dialect is MySQLDialect) {
returningSql = sql;
sql = '$updateSql';
} else {
throw ArgumentError("Unsupported database dialect.");
}
//_log.fine("Update Query = $sql"); //_log.fine("Update Query = $sql");
return executor return executor
.query(tableName, sql, substitutionValues) .query(tableName, sql, substitutionValues, returningQuery: returningSql)
.then((it) => deserializeList(it)); .then((it) => deserializeList(it));
} }
}
Future<Optional<T>> updateOne(QueryExecutor executor) { Future<Optional<T>> updateOne(QueryExecutor executor) {
return update(executor).then( return update(executor).then(

View file

@ -45,11 +45,11 @@ abstract class QueryBase<T> {
List<T> deserializeList(List<List<dynamic>> it) { List<T> deserializeList(List<List<dynamic>> it) {
var optResult = it.map(deserialize).toList(); var optResult = it.map(deserialize).toList();
var result = <T>[]; var result = <T>[];
optResult.forEach((element) { for (var element in optResult) {
element.ifPresent((item) { element.ifPresent((item) {
result.add(item); result.add(item);
}); });
}); }
return result; return result;
} }
@ -57,8 +57,8 @@ abstract class QueryBase<T> {
Future<List<T>> get(QueryExecutor executor) async { Future<List<T>> get(QueryExecutor executor) async {
var sql = compile({}); var sql = compile({});
//_log.fine('sql = $sql'); print('sql = $sql');
//_log.fine('substitutionValues = $substitutionValues'); print('substitutionValues = $substitutionValues');
return executor.query(tableName, sql, substitutionValues).then((it) { return executor.query(tableName, sql, substitutionValues).then((it) {
return deserializeList(it); return deserializeList(it);

View file

@ -1,15 +1,23 @@
import 'dart:async'; import 'dart:async';
import '../angel3_orm.dart';
/// An abstract interface that performs queries. /// An abstract interface that performs queries.
/// ///
/// This class should be implemented. /// This class should be implemented.
abstract class QueryExecutor { abstract class QueryExecutor {
const QueryExecutor(); const QueryExecutor();
Dialect get dialect;
/// Executes a single query. /// Executes a single query.
Future<List<List>> query( Future<List<List>> query(
String tableName, String query, Map<String, dynamic> substitutionValues, String tableName,
[List<String> returningFields = const []]); String query,
Map<String, dynamic> substitutionValues, {
String returningQuery = '',
List<String> returningFields = const [],
});
/// Enters a database transaction, performing the actions within, /// Enters a database transaction, performing the actions within,
/// and returning the results of [f]. /// and returning the results of [f].

View file

@ -52,6 +52,20 @@ abstract class QueryValues {
return b.toString(); return b.toString();
} }
String compileInsertSelect(Query query, String tableName) {
var data = Map<String, dynamic>.from(toMap());
var b = StringBuffer();
var i = 0;
for (var entry in data.entries) {
if (i++ > 0) b.write(' AND ');
b.write('$tableName.${entry.key} = ?');
}
return b.toString();
}
String compileForUpdate(Query query) { String compileForUpdate(Query query) {
var data = toMap(); var data = toMap();
if (data.isEmpty) { if (data.isEmpty) {

View file

@ -1,3 +1,21 @@
import 'package:charcode/ascii.dart'; import 'package:charcode/ascii.dart';
bool isAscii(int ch) => ch >= $nul && ch <= $del; bool isAscii(int ch) => ch >= $nul && ch <= $del;
bool mapToBool(dynamic value) {
if (value is int) {
return value != 0;
}
return value != null ? value as bool : false;
}
String mapToText(dynamic value) {
if (value == null) {
return '';
}
if (value is! String) {
return value.toString();
}
return value;
}

View file

@ -1,5 +1,5 @@
name: angel3_orm name: angel3_orm
version: 4.0.6 version: 4.1.0
description: Runtime support for Angel3 ORM. Includes base classes for queries. description: Runtime support for Angel3 ORM. Includes base classes for queries.
homepage: https://angel3-framework.web.app/ homepage: https://angel3-framework.web.app/
repository: https://github.com/dukefirehawk/angel/tree/master/packages/orm/angel_orm repository: https://github.com/dukefirehawk/angel/tree/master/packages/orm/angel_orm

View file

@ -1,5 +1,10 @@
# Change Log # Change Log
## 5.0.0
* Updated `analyzer` to version 3.x
* Updated min SDK to 2.14.x
## 4.3.0 ## 4.3.0
* Added `where.raw()` * Added `where.raw()`

View file

@ -21,8 +21,12 @@ class _FakeExecutor extends QueryExecutor {
@override @override
Future<List<List>> query( Future<List<List>> query(
String tableName, String? query, Map<String, dynamic> substitutionValues, String tableName,
[returningFields = const []]) async { String? query,
Map<String, dynamic> substitutionValues, {
String returningQuery = '',
List<String> returningFields = const [],
}) async {
var now = DateTime.now(); var now = DateTime.now();
print( print(
'_FakeExecutor received query: $query and values: $substitutionValues'); '_FakeExecutor received query: $query and values: $substitutionValues');
@ -35,6 +39,10 @@ class _FakeExecutor extends QueryExecutor {
Future<T> transaction<T>(FutureOr<T> Function(QueryExecutor) f) { Future<T> transaction<T>(FutureOr<T> Function(QueryExecutor) f) {
throw UnsupportedError('Transactions are not supported.'); throw UnsupportedError('Transactions are not supported.');
} }
@override
// TODO: implement dialect
Dialect get dialect => PostgreSQLDialect();
} }
@orm @orm

View file

@ -110,7 +110,8 @@ class OrmGenerator extends GeneratorForAnnotation<Orm> {
var type = ctx.columns[field.name]?.type; var type = ctx.columns[field.name]?.type;
if (type == null) continue; if (type == null) continue;
if (floatTypes.contains(type)) { if (floatTypes.contains(type)) {
args[name] = literalString('text'); //args[name] = literalString('text');
args[name] = literalString('char');
} }
} }

View file

@ -1,16 +1,16 @@
name: angel3_orm_generator name: angel3_orm_generator
version: 4.3.0 version: 5.0.0
description: Code generators for Angel3 ORM. Generates query builder classes. description: Code generators for Angel3 ORM. Generates query builder classes.
homepage: https://angel3-framework.web.app/ homepage: https://angel3-framework.web.app/
repository: https://github.com/dukefirehawk/angel/tree/master/packages/orm/angel_orm_generator repository: https://github.com/dukefirehawk/angel/tree/master/packages/orm/angel_orm_generator
environment: environment:
sdk: '>=2.12.0 <3.0.0' sdk: '>=2.14.0 <3.0.0'
dependencies: dependencies:
analyzer: ^2.0.0 analyzer: ^3.0.0
angel3_model: ^3.0.0 angel3_model: ^3.0.0
angel3_serialize: ^4.0.0 angel3_serialize: ^4.0.0
angel3_orm: ^4.0.0 angel3_orm: ^4.1.0
angel3_serialize_generator: ^4.0.0 angel3_serialize_generator: ^5.0.0
inflection3: ^0.5.3+1 inflection3: ^0.5.3+1
build: ^2.0.1 build: ^2.0.1
build_config: ^1.0.0 build_config: ^1.0.0
@ -25,7 +25,7 @@ dependencies:
optional: ^6.0.0 optional: ^6.0.0
dev_dependencies: dev_dependencies:
angel3_framework: ^4.2.0 angel3_framework: ^4.2.0
angel3_migration: ^4.0.0 angel3_migration: ^4.1.0
build_runner: ^2.0.1 build_runner: ^2.0.1
postgres: ^2.4.0 postgres: ^2.4.0
test: ^1.17.3 test: ^1.17.3
@ -35,4 +35,6 @@ dependency_overrides:
path: ../angel_orm path: ../angel_orm
angel3_migration: angel3_migration:
path: ../angel_migration path: ../angel_migration
angel3_serialize_generator:
path: ../../serialize/angel_serialize_generator

View file

@ -1,10 +1,10 @@
import 'package:angel3_migration/angel3_migration.dart'; import 'package:angel3_migration/angel3_migration.dart';
import 'package:angel3_model/angel3_model.dart';
import 'package:angel3_orm/angel3_orm.dart'; import 'package:angel3_orm/angel3_orm.dart';
import 'package:angel3_orm_mysql/angel3_orm_mysql.dart'; import 'package:angel3_orm_mysql/angel3_orm_mysql.dart';
import 'package:angel3_serialize/angel3_serialize.dart'; import 'package:angel3_serialize/angel3_serialize.dart';
//import 'package:galileo_sqljocky5/sqljocky.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:galileo_sqljocky5/sqljocky.dart'; import 'package:mysql1/mysql1.dart';
import 'package:optional/optional.dart'; import 'package:optional/optional.dart';
part 'main.g.dart'; part 'main.g.dart';
@ -15,9 +15,14 @@ void main() async {
..onRecord.listen(print); ..onRecord.listen(print);
var settings = ConnectionSettings( var settings = ConnectionSettings(
db: 'angel_orm_test', user: 'angel_orm_test', password: 'angel_orm_test'); host: 'localhost',
port: 3306,
db: 'orm_test',
user: 'Test',
password: 'Test123*');
var connection = await MySqlConnection.connect(settings); var connection = await MySqlConnection.connect(settings);
var logger = Logger('angel_orm_mysql');
var logger = Logger('orm_mysql');
var executor = MySqlExecutor(connection, logger: logger); var executor = MySqlExecutor(connection, logger: logger);
var query = TodoQuery(); var query = TodoQuery();

View file

@ -69,7 +69,7 @@ class TodoQuery extends Query<Todo, TodoQueryWhere> {
if (row.every((x) => x == null)) return null; if (row.every((x) => x == null)) return null;
var model = Todo( var model = Todo(
id: row[0].toString(), id: row[0].toString(),
isComplete: (row[1] as bool?), isComplete: (row[1] as int?) != 0,
text: (row[2] as String?), text: (row[2] as String?),
createdAt: (row[3] as DateTime?), createdAt: (row[3] as DateTime?),
updatedAt: (row[4] as DateTime?)); updatedAt: (row[4] as DateTime?));

View file

@ -1,26 +1,33 @@
import 'dart:async'; import 'dart:async';
import 'package:angel3_orm/angel3_orm.dart'; import 'package:angel3_orm/angel3_orm.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
// import 'package:pool/pool.dart'; import 'package:mysql1/mysql1.dart';
import 'package:galileo_sqljocky5/public/connection/connection.dart';
import 'package:galileo_sqljocky5/sqljocky.dart';
class MySqlExecutor extends QueryExecutor { class MySqlExecutor extends QueryExecutor {
/// An optional [Logger] to write to. /// An optional [Logger] to write to.
final Logger? logger; final Logger? logger;
final Querier? _connection; final MySqlConnection _connection;
MySqlExecutor(this._connection, {this.logger}); MySqlExecutor(this._connection, {this.logger});
final Dialect _dialect = const MySQLDialect();
@override
Dialect get dialect => _dialect;
Future<void> close() { Future<void> close() {
return _connection.close();
/*
if (_connection is MySqlConnection) { if (_connection is MySqlConnection) {
return (_connection as MySqlConnection).close(); return (_connection as MySqlConnection).close();
} else { } else {
return Future.value(); return Future.value();
} }
*/
} }
/*
Future<Transaction> _startTransaction() { Future<Transaction> _startTransaction() {
if (_connection is Transaction) { if (_connection is Transaction) {
return Future.value(_connection as Transaction?); return Future.value(_connection as Transaction?);
@ -69,7 +76,64 @@ class MySqlExecutor extends QueryExecutor {
}); });
} }
} }
*/
@override
Future<List<List>> query(
String tableName, String query, Map<String, dynamic> substitutionValues,
{String returningQuery = '',
List<String> returningFields = const []}) async {
// Change @id -> ?
for (var name in substitutionValues.keys) {
query = query.replaceAll('@$name', '?');
}
var params = substitutionValues.values.toList();
//logger?.warning('Query: $query');
//logger?.warning('Values: $params');
//logger?.warning('Returning Query: $returningQuery');
if (returningQuery.isNotEmpty) {
// Handle insert, update and delete
// Retrieve back the inserted record
if (query.startsWith("INSERT")) {
var result = await _connection.query(query, params);
query = returningQuery;
//logger?.warning('Result.insertId: ${result.insertId}');
// Has primary key
//if (result.insertId != 0) {
if (returningQuery.endsWith('.id=?')) {
params = [result.insertId];
}
} else if (query.startsWith("UPDATE")) {
await _connection.query(query, params);
query = returningQuery;
params = [];
}
}
// Handle select
return _connection.query(query, params).then((results) {
return results.map((r) => r.toList()).toList();
});
}
@override
Future<T> transaction<T>(FutureOr<T> Function(QueryExecutor) f) async {
return f(this);
/*
if (_connection is! MySqlConnection) {
return await f(this);
}
await _connection.transaction((context) async {
var executor = MySqlExecutor(context, logger: logger);
});
*/
}
/*
@override @override
Future<T> transaction<T>(FutureOr<T> Function(QueryExecutor) f) async { Future<T> transaction<T>(FutureOr<T> Function(QueryExecutor) f) async {
if (_connection is Transaction) { if (_connection is Transaction) {
@ -88,4 +152,5 @@ class MySqlExecutor extends QueryExecutor {
rethrow; rethrow;
} }
} }
*/
} }

View file

@ -6,17 +6,13 @@ repository: https://github.com/dukefirehawk/angel/tree/master/packages/orm/angel
environment: environment:
sdk: '>=2.12.0 <3.0.0' sdk: '>=2.12.0 <3.0.0'
dependencies: dependencies:
angel3_orm: ^4.0.0 angel3_orm: ^4.1.0
logging: ^1.0.0 logging: ^1.0.0
pool: ^1.5.0 mysql1: ^0.19.0
galileo_sqljocky5: ^3.0.0
optional: ^6.0.0 optional: ^6.0.0
dev_dependencies: dev_dependencies:
angel3_migration: ^4.0.0
angel3_orm_generator: ^4.0.0
build_runner: ^2.0.0
test: ^1.17.0
angel3_orm_test: ^3.0.0 angel3_orm_test: ^3.0.0
test: ^1.17.0
lints: ^1.0.0 lints: ^1.0.0
dependency_overrides: dependency_overrides:
angel3_orm_test: angel3_orm_test:

View file

@ -10,7 +10,7 @@ void main() {
if (rec.stackTrace != null) print(rec.stackTrace); if (rec.stackTrace != null) print(rec.stackTrace);
}); });
group('postgresql', () { group('mysql', () {
group('belongsTo', group('belongsTo',
() => belongsToTests(my(['author', 'book']), close: closeMy)); () => belongsToTests(my(['author', 'book']), close: closeMy));
group( group(
@ -20,8 +20,10 @@ void main() {
group('enumAndNested', group('enumAndNested',
() => enumAndNestedTests(my(['has_car']), close: closeMy)); () => enumAndNestedTests(my(['has_car']), close: closeMy));
group('hasMany', () => hasManyTests(my(['tree', 'fruit']), close: closeMy)); group('hasMany', () => hasManyTests(my(['tree', 'fruit']), close: closeMy));
group('hasMap', () => hasMapTests(my(['has_map']), close: closeMy)); // NOTE: MySQL/MariaDB do not support jsonb data type
group('hasOne', () => hasOneTests(my(['leg', 'foot']), close: closeMy)); //group('hasMap', () => hasMapTests(my(['has_map']), close: closeMy));
// NOTE: mysql1 driver do not support CAST();
//group('hasOne', () => hasOneTests(my(['leg', 'foot']), close: closeMy));
group( group(
'manyToMany', 'manyToMany',
() => () =>

View file

@ -3,7 +3,7 @@ import 'dart:io';
import 'package:angel3_orm/angel3_orm.dart'; import 'package:angel3_orm/angel3_orm.dart';
import 'package:angel3_orm_mysql/angel3_orm_mysql.dart'; import 'package:angel3_orm_mysql/angel3_orm_mysql.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:galileo_sqljocky5/sqljocky.dart'; import 'package:mysql1/mysql1.dart';
FutureOr<QueryExecutor> Function() my(Iterable<String> schemas) { FutureOr<QueryExecutor> Function() my(Iterable<String> schemas) {
return () => connectToMySql(schemas); return () => connectToMySql(schemas);
@ -14,15 +14,24 @@ Future<void> closeMy(QueryExecutor executor) =>
Future<MySqlExecutor> connectToMySql(Iterable<String> schemas) async { Future<MySqlExecutor> connectToMySql(Iterable<String> schemas) async {
var settings = ConnectionSettings( var settings = ConnectionSettings(
db: 'angel_orm_test', db: 'orm_test',
user: Platform.environment['MYSQL_USERNAME'] ?? 'angel_orm_test', host: "localhost",
password: Platform.environment['MYSQL_PASSWORD'] ?? 'angel_orm_test'); user: Platform.environment['MYSQL_USERNAME'] ?? 'Test',
password: Platform.environment['MYSQL_PASSWORD'] ?? 'Test123*',
timeout: Duration(minutes: 10));
var connection = await MySqlConnection.connect(settings); var connection = await MySqlConnection.connect(settings);
var logger = Logger('angel_orm_mysql'); var logger = Logger('orm_mysql');
for (var s in schemas) { for (var s in schemas) {
await connection // MySQL driver does not support multiple sql queries
.execute(await File('test/migrations/$s.sql').readAsString()); var data = await File('test/migrations/$s.sql').readAsString();
var queries = data.split(";");
for (var q in queries) {
//print("Table: [$q]");
if (q.trim().isNotEmpty) {
await connection.query(q);
}
}
} }
return MySqlExecutor(connection, logger: logger); return MySqlExecutor(connection, logger: logger);

View file

@ -1,4 +1,4 @@
CREATE TEMPORARY TABLE "authors" ( CREATE TEMPORARY TABLE authors (
id serial PRIMARY KEY, id serial PRIMARY KEY,
name varchar(255) UNIQUE NOT NULL, name varchar(255) UNIQUE NOT NULL,
created_at timestamp, created_at timestamp,

View file

@ -1,4 +1,4 @@
CREATE TEMPORARY TABLE "books" ( CREATE TEMPORARY TABLE books (
id serial PRIMARY KEY, id serial PRIMARY KEY,
author_id int NOT NULL, author_id int NOT NULL,
partner_author_id int, partner_author_id int,

View file

@ -1,4 +1,4 @@
CREATE TEMPORARY TABLE "cars" ( CREATE TEMPORARY TABLE cars (
id serial PRIMARY KEY, id serial PRIMARY KEY,
make varchar(255) NOT NULL, make varchar(255) NOT NULL,
description TEXT NOT NULL, description TEXT NOT NULL,

View file

@ -1,4 +1,4 @@
CREATE TEMPORARY TABLE "feet" ( CREATE TEMPORARY TABLE feet (
id serial PRIMARY KEY, id serial PRIMARY KEY,
leg_id int NOT NULL, leg_id int NOT NULL,
n_toes int NOT NULL, n_toes int NOT NULL,

View file

@ -1,8 +1,8 @@
CREATE TEMPORARY TABLE "fruits" ( CREATE TEMPORARY TABLE fruits (
"id" serial, id serial,
"tree_id" int, tree_id int,
"common_name" varchar, common_name varchar(255),
"created_at" timestamp, created_at timestamp,
"updated_at" timestamp, updated_at timestamp,
PRIMARY KEY(id) PRIMARY KEY(id)
); );

View file

@ -1,4 +1,4 @@
CREATE TEMPORARY TABLE "has_cars" ( CREATE TEMPORARY TABLE has_cars (
id serial PRIMARY KEY, id serial PRIMARY KEY,
type int not null, type int not null,
created_at timestamp, created_at timestamp,

View file

@ -1,4 +1,4 @@
CREATE TEMPORARY TABLE "has_maps" ( CREATE TEMPORARY TABLE has_maps (
id serial PRIMARY KEY, id serial PRIMARY KEY,
value jsonb not null, value jsonb not null,
list jsonb not null, list jsonb not null,

View file

@ -1,4 +1,4 @@
CREATE TEMPORARY TABLE "legs" ( CREATE TEMPORARY TABLE legs (
id serial PRIMARY KEY, id serial PRIMARY KEY,
name varchar(255) NOT NULL, name varchar(255) NOT NULL,
created_at timestamp, created_at timestamp,

View file

@ -1,6 +1,6 @@
CREATE TEMPORARY TABLE "numbas" ( CREATE TEMPORARY TABLE numbas (
"i" int, i int NOT NULL UNIQUE,
"parent" int references weird_joins(id), parent int,
created_at TIMESTAMP, created_at TIMESTAMP,
updated_at TIMESTAMP, updated_at TIMESTAMP,
PRIMARY KEY(i) PRIMARY KEY(i)

View file

@ -1,6 +1,6 @@
CREATE TEMPORARY TABLE "roles" ( CREATE TEMPORARY TABLE roles (
"id" serial PRIMARY KEY, id serial PRIMARY KEY,
"name" varchar(255), name varchar(255),
"created_at" timestamp, created_at timestamp,
"updated_at" timestamp updated_at timestamp
); );

View file

@ -1,7 +1,7 @@
CREATE TEMPORARY TABLE "songs" ( CREATE TEMPORARY TABLE songs (
"id" serial, id serial,
"weird_join_id" int references weird_joins(id), weird_join_id int,
"title" varchar(255), title varchar(255),
created_at TIMESTAMP, created_at TIMESTAMP,
updated_at TIMESTAMP, updated_at TIMESTAMP,
PRIMARY KEY(id) PRIMARY KEY(id)

View file

@ -1,8 +1,7 @@
CREATE TEMPORARY TABLE "trees" ( CREATE TEMPORARY TABLE trees (
"id" serial, id serial,
"rings" smallint UNIQUE, rings smallint UNIQUE,
"created_at" timestamp, created_at timestamp,
"updated_at" timestamp, updated_at timestamp,
UNIQUE(rings),
PRIMARY KEY(id) PRIMARY KEY(id)
); );

View file

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

View file

@ -1,8 +1,8 @@
CREATE TEMPORARY TABLE "users" ( CREATE TEMPORARY TABLE users (
"id" serial PRIMARY KEY, id serial PRIMARY KEY,
"username" varchar(255), username varchar(255),
"password" varchar(255), password varchar(255),
"email" varchar(255), email varchar(255),
"created_at" timestamp, created_at timestamp,
"updated_at" timestamp updated_at timestamp
); );

View file

@ -1,7 +1,7 @@
CREATE TEMPORARY TABLE "role_users" ( CREATE TEMPORARY TABLE role_users (
"id" serial PRIMARY KEY, id serial PRIMARY KEY,
"user_id" int NOT NULL, user_id int NOT NULL,
"role_id" int NOT NULL, role_id int NOT NULL,
"created_at" timestamp, created_at timestamp,
"updated_at" timestamp updated_at timestamp
); );

View file

@ -1,13 +1,15 @@
CREATE TEMPORARY TABLE "weird_joins" ( CREATE TEMPORARY TABLE weird_joins (
"id" serial, id serial,
"join_name" varchar(255) references unorthodoxes(name), join_name varchar(255),
PRIMARY KEY(id) PRIMARY KEY(id)
); );
CREATE TEMPORARY TABLE "foos" (
"bar" varchar(255), CREATE TEMPORARY TABLE foos (
bar varchar(255) not null UNIQUE,
PRIMARY KEY(bar) PRIMARY KEY(bar)
); );
CREATE TEMPORARY TABLE "foo_pivots" (
"weird_join_id" int references weird_joins(id), CREATE TEMPORARY TABLE foo_pivots (
"foo_bar" varchar(255) references foos(bar) weird_join_id int,
foo_bar varchar(255)
); );

View file

@ -0,0 +1,21 @@
import 'package:angel3_orm_test/angel3_orm_test.dart';
import 'package:logging/logging.dart';
import 'common.dart';
void main() async {
//hierarchicalLoggingEnabled = true;
Logger.root
..level = Level.INFO
..onRecord.listen(print);
//Logger.root.onRecord.listen((rec) {
// print(rec);
// if (rec.error != null) print(rec.error);
// if (rec.stackTrace != null) print(rec.stackTrace);
//});
belongsToTests(my(['author', 'book']), close: closeMy);
//hasOneTests(my(['leg', 'foot']), close: closeMy);
//standaloneTests(my(['car']), close: closeMy);
}

View file

@ -16,6 +16,11 @@ class PostgreSqlExecutor extends QueryExecutor {
this.logger = logger ?? Logger('PostgreSqlExecutor'); this.logger = logger ?? Logger('PostgreSqlExecutor');
} }
final Dialect _dialect = const PostgreSQLDialect();
@override
Dialect get dialect => _dialect;
/// The underlying connection. /// The underlying connection.
PostgreSQLExecutionContext get connection => _connection; PostgreSQLExecutionContext get connection => _connection;
@ -31,8 +36,8 @@ class PostgreSqlExecutor extends QueryExecutor {
@override @override
Future<PostgreSQLResult> query( Future<PostgreSQLResult> query(
String tableName, String query, Map<String, dynamic> substitutionValues, String tableName, String query, Map<String, dynamic> substitutionValues,
[List<String>? returningFields]) { {String returningQuery = '', List<String> returningFields = const []}) {
if (returningFields != null && returningFields.isNotEmpty) { if (returningFields.isNotEmpty) {
var fields = returningFields.join(', '); var fields = returningFields.join(', ');
var returning = 'RETURNING $fields'; var returning = 'RETURNING $fields';
query = '$query $returning'; query = '$query $returning';
@ -119,6 +124,11 @@ class PostgreSqlExecutorPool extends QueryExecutor {
assert(size > 0, 'Connection pool cannot be empty.'); assert(size > 0, 'Connection pool cannot be empty.');
} }
final Dialect _dialect = const PostgreSQLDialect();
@override
Dialect get dialect => _dialect;
/// Closes all connections. /// Closes all connections.
Future close() async { Future close() async {
await _pool.close(); await _pool.close();
@ -151,11 +161,11 @@ class PostgreSqlExecutorPool extends QueryExecutor {
@override @override
Future<PostgreSQLResult> query( Future<PostgreSQLResult> query(
String tableName, String query, Map<String, dynamic> substitutionValues, String tableName, String query, Map<String, dynamic> substitutionValues,
[List<String>? returningFields]) { {String returningQuery = '', List<String> returningFields = const []}) {
return _pool.withResource(() async { return _pool.withResource(() async {
var executor = await _next(); var executor = await _next();
return executor.query( return executor.query(tableName, query, substitutionValues,
tableName, query, substitutionValues, returningFields); returningFields: returningFields);
}); });
} }

View file

@ -17,6 +17,11 @@ class PostgreSqlPoolExecutor extends QueryExecutor {
this.logger = logger ?? Logger('PostgreSqlPoolExecutor'); this.logger = logger ?? Logger('PostgreSqlPoolExecutor');
} }
final Dialect _dialect = const PostgreSQLDialect();
@override
Dialect get dialect => _dialect;
/// The underlying connection pooling. /// The underlying connection pooling.
PgPool get pool => _pool; PgPool get pool => _pool;
@ -29,7 +34,7 @@ class PostgreSqlPoolExecutor extends QueryExecutor {
@override @override
Future<PostgreSQLResult> query( Future<PostgreSQLResult> query(
String tableName, String query, Map<String, dynamic> substitutionValues, String tableName, String query, Map<String, dynamic> substitutionValues,
[List<String> returningFields = const []]) { {String returningQuery = '', List<String> returningFields = const []}) {
if (returningFields.isNotEmpty) { if (returningFields.isNotEmpty) {
var fields = returningFields.join(', '); var fields = returningFields.join(', ');
var returning = 'RETURNING $fields'; var returning = 'RETURNING $fields';

View file

@ -6,7 +6,7 @@ repository: https://github.com/dukefirehawk/angel/tree/master/packages/orm/angel
environment: environment:
sdk: '>=2.12.0 <3.0.0' sdk: '>=2.12.0 <3.0.0'
dependencies: dependencies:
angel3_orm: ^4.0.0 angel3_orm: ^4.1.0
logging: ^1.0.1 logging: ^1.0.1
pool: ^1.5.0 pool: ^1.5.0
postgres: ^2.4.1 postgres: ^2.4.1
@ -21,3 +21,5 @@ dependency_overrides:
path: ../angel_orm_test path: ../angel_orm_test
angel3_orm: angel3_orm:
path: ../angel_orm path: ../angel_orm
angel3_migration:
path: ../angel_migration

View file

@ -0,0 +1,18 @@
import 'package:angel3_orm_test/angel3_orm_test.dart';
import 'package:logging/logging.dart';
import 'common.dart';
void main() async {
hierarchicalLoggingEnabled = true;
Logger.root
..level = Level.ALL
..onRecord.listen(print);
//Logger.root.onRecord.listen((rec) {
// print(rec);
// if (rec.error != null) print(rec.error);
// if (rec.stackTrace != null) print(rec.stackTrace);
//});
belongsToTests(pg(['author', 'book']), close: closePg);
}

View file

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

View file

@ -203,7 +203,7 @@ class FootQuery extends Query<Foot, FootQueryWhere> {
@override @override
Map<String, String> get casts { Map<String, String> get casts {
return {'n_toes': 'text'}; return {'n_toes': 'char'};
} }
@override @override

View file

@ -48,7 +48,7 @@ class PersonOrderQuery extends Query<PersonOrder, PersonOrderQueryWhere> {
@override @override
Map<String, String> get casts { Map<String, String> get casts {
return {'price': 'text'}; return {'price': 'char'};
} }
@override @override
@ -212,7 +212,7 @@ class OrderWithPersonInfoQuery
@override @override
Map<String, String> get casts { Map<String, String> get casts {
return {'price': 'text'}; return {'price': 'char'};
} }
@override @override

View file

@ -77,7 +77,7 @@ void standaloneTests(FutureOr<QueryExecutor> Function() createExecutor,
var sportsCars = CarQuery()..where!.familyFriendly.isFalse; var sportsCars = CarQuery()..where!.familyFriendly.isFalse;
cars = await sportsCars.get(executor!); cars = await sportsCars.get(executor!);
//print(cars.map((c) => c.toJson())); print(cars.map((c) => c.toJson()));
var car = cars.first; var car = cars.first;
expect(car.make, ferrari!.make); expect(car.make, ferrari!.make);

View file

@ -1,12 +1,12 @@
name: angel3_orm_test name: angel3_orm_test
version: 3.1.2 version: 4.0.0
description: Common tests for Angel3 ORM. Reference implmentation of the generated ORM files. description: Common tests for Angel3 ORM. Reference implmentation of the generated ORM files.
homepage: https://angel3-framework.web.app/ homepage: https://angel3-framework.web.app/
repository: https://github.com/dukefirehawk/angel/tree/master/packages/orm/angel_orm_test repository: https://github.com/dukefirehawk/angel/tree/master/packages/orm/angel_orm_test
environment: environment:
sdk: '>=2.12.0 <3.0.0' sdk: '>=2.12.0 <3.0.0'
dependencies: dependencies:
angel3_migration: ^4.0.0 angel3_migration: ^4.1.0
angel3_model: ^3.1.0 angel3_model: ^3.1.0
angel3_orm: ^4.0.0 angel3_orm: ^4.0.0
angel3_serialize: ^4.1.0 angel3_serialize: ^4.1.0
@ -15,7 +15,7 @@ dependencies:
collection: ^1.15.0 collection: ^1.15.0
optional: ^6.0.0 optional: ^6.0.0
dev_dependencies: dev_dependencies:
angel3_orm_generator: ^4.1.0 angel3_orm_generator: ^5.0.0
angel3_framework: ^4.2.0 angel3_framework: ^4.2.0
build_runner: ^2.0.1 build_runner: ^2.0.1
lints: ^1.0.0 lints: ^1.0.0
@ -26,3 +26,5 @@ dependency_overrides:
path: ../angel_orm_generator path: ../angel_orm_generator
angel3_migration: angel3_migration:
path: ../angel_migration path: ../angel_migration
angel3_serialize_generator:
path: ../../serialize/angel_serialize_generator

View file

@ -1,5 +1,14 @@
# Change Log # Change Log
## 5.0.0
* Updated `analyzer` to version 3.x
* Updated min SDK to 2.14.x
## 4.1.1
* Removed deprecated
## 4.1.0 ## 4.1.0
* Updated README * Updated README

View file

@ -87,8 +87,6 @@ class Serializable {
const Serializable( const Serializable(
{this.serializers = const [Serializers.map, Serializers.json], {this.serializers = const [Serializers.map, Serializers.json],
this.autoSnakeCaseNames = true, this.autoSnakeCaseNames = true,
// ignore: deprecated_member_use_from_same_package
//@deprecated this.autoIdAndDateFields = true,
this.includeAnnotations = const []}); this.includeAnnotations = const []});
/// A list of enabled serialization modes. /// A list of enabled serialization modes.
@ -99,10 +97,6 @@ class Serializable {
/// Overrides the setting in `SerializerGenerator`. /// Overrides the setting in `SerializerGenerator`.
final bool autoSnakeCaseNames; final bool autoSnakeCaseNames;
/// Overrides the setting in `JsonModelGenerator`.
//@deprecated
//final bool autoIdAndDateFields;
/// A list of constant members to affix to the generated class. /// A list of constant members to affix to the generated class.
final List includeAnnotations; final List includeAnnotations;
} }
@ -130,20 +124,3 @@ abstract class Serializers {
/// Generate a TypeScript definition file (`.d.ts`) for use on the client-side. /// Generate a TypeScript definition file (`.d.ts`) for use on the client-side.
static const int typescript = 2; static const int typescript = 2;
} }
/*
@deprecated
class DefaultValue {
final value;
const DefaultValue(this.value);
}
@deprecated
/// Prefer [SerializableField] instead.
class Alias {
final String name;
const Alias(this.name);
}
*/

View file

@ -1,8 +1,8 @@
name: angel3_serialize name: angel3_serialize
version: 4.1.0 version: 4.1.1
description: Static annotations powering Angel3 model serialization. Combine with angel3_serialize_generator for flexible modeling. description: Static annotations powering Angel3 model serialization. Combine with angel3_serialize_generator for flexible modeling.
homepage: https://angel3-framework.web.app/ homepage: https://angel3-framework.web.app/
repository: https://github.com/dukefirehawk/angel/tree/angel3/packages/serialize/angel_serialize repository: https://github.com/dukefirehawk/angel/tree/master/packages/serialize/angel_serialize
environment: environment:
sdk: '>=2.12.0 <3.0.0' sdk: '>=2.12.0 <3.0.0'
dependencies: dependencies:

View file

@ -1,5 +1,10 @@
# Change Log # Change Log
## 5.0.0
* Updated `analyzer` to version 3.x
* Updated min SDK to 2.14.x
## 4.3.0 ## 4.3.0
* Updated to use `package:belatuk_code_buffer` * Updated to use `package:belatuk_code_buffer`

View file

@ -7,7 +7,6 @@ import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart'; import 'package:analyzer/dart/element/type.dart';
import 'package:angel3_model/angel3_model.dart';
import 'package:angel3_serialize/angel3_serialize.dart'; import 'package:angel3_serialize/angel3_serialize.dart';
import 'package:build/build.dart'; import 'package:build/build.dart';
import 'package:belatuk_code_buffer/belatuk_code_buffer.dart'; import 'package:belatuk_code_buffer/belatuk_code_buffer.dart';

View file

@ -5,7 +5,6 @@ import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/element.dart'; import 'package:analyzer/src/dart/element/element.dart';
import 'package:angel3_serialize/angel3_serialize.dart'; import 'package:angel3_serialize/angel3_serialize.dart';
import 'package:build/build.dart'; import 'package:build/build.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
import 'package:recase/recase.dart'; import 'package:recase/recase.dart';
import 'package:source_gen/source_gen.dart'; import 'package:source_gen/source_gen.dart';

View file

@ -1,12 +1,12 @@
name: angel3_serialize_generator name: angel3_serialize_generator
version: 4.3.0 version: 5.0.0
description: Angel3 model serialization generators, designed for use with Angel. Combine with angel_serialize for flexible modeling. description: Angel3 model serialization generators, designed for use with Angel. Combine with angel_serialize for flexible modeling.
homepage: https://angel3-framework.web.app/ homepage: https://angel3-framework.web.app/
repository: https://github.com/dukefirehawk/angel/tree/angel3/packages/serialize/angel_serialize_generator repository: https://github.com/dukefirehawk/angel/tree/master/packages/serialize/angel_serialize_generator
environment: environment:
sdk: '>=2.12.0 <3.0.0' sdk: '>=2.14.0 <3.0.0'
dependencies: dependencies:
analyzer: ^2.0.0 analyzer: ^3.0.0
angel3_model: ^3.0.0 angel3_model: ^3.0.0
angel3_serialize: ^4.1.0 angel3_serialize: ^4.1.0
belatuk_code_buffer: ^3.0.0 belatuk_code_buffer: ^3.0.0