Work on queries...
This commit is contained in:
parent
df51277676
commit
421099ee94
17 changed files with 1105 additions and 549 deletions
84
README.md
84
README.md
|
@ -21,19 +21,23 @@ Your model, courtesy of `package:angel_serialize`:
|
||||||
library angel_orm.test.models.car;
|
library angel_orm.test.models.car;
|
||||||
|
|
||||||
import 'package:angel_framework/common.dart';
|
import 'package:angel_framework/common.dart';
|
||||||
import 'package:angel_orm/angel_orm.dart' as orm;
|
import 'package:angel_orm/angel_orm.dart';
|
||||||
import 'package:angel_serialize/angel_serialize.dart';
|
import 'package:angel_serialize/angel_serialize.dart';
|
||||||
part 'car.g.dart';
|
part 'car.g.dart';
|
||||||
|
|
||||||
@serializable
|
@serializable
|
||||||
@orm.model
|
@orm
|
||||||
class _Car extends Model {
|
class _Car extends Model {
|
||||||
String manufacturer;
|
String make;
|
||||||
int year;
|
String description;
|
||||||
|
bool familyFriendly;
|
||||||
|
DateTime recalledAt;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
After building, you'll have access to a `Repository` class with strongly-typed methods that
|
Models can still use the `@Alias()` annotation. `package:angel_orm` obeys it.
|
||||||
|
|
||||||
|
After building, you'll have access to a `Query` class with strongly-typed methods that
|
||||||
allow to run asynchronous queries without a headache.
|
allow to run asynchronous queries without a headache.
|
||||||
You can run complex queries like:
|
You can run complex queries like:
|
||||||
|
|
||||||
|
@ -46,33 +50,67 @@ import 'car.orm.g.dart';
|
||||||
/// Returns an Angel plug-in that connects to a PostgreSQL database, and sets up a controller connected to it...
|
/// Returns an Angel plug-in that connects to a PostgreSQL database, and sets up a controller connected to it...
|
||||||
AngelConfigurer connectToCarsTable(PostgreSQLConnection connection) {
|
AngelConfigurer connectToCarsTable(PostgreSQLConnection connection) {
|
||||||
return (Angel app) async {
|
return (Angel app) async {
|
||||||
// Instantiate a Car repository, which is auto-generated. This class helps us build fluent queries easily.
|
// Register the connection with Angel's dependency injection system.
|
||||||
var cars = new CarRepository(connection);
|
|
||||||
|
|
||||||
// Register it with Angel's dependency injection system.
|
|
||||||
//
|
//
|
||||||
// This means that we can use it as a parameter in routes and controllers.
|
// This means that we can use it as a parameter in routes and controllers.
|
||||||
app.container.singleton(cars);
|
app.container.singleton(connection);
|
||||||
|
|
||||||
// Attach the controller we create below
|
// Attach the controller we create below
|
||||||
await app.configure(new CarService(cars));
|
await app.configure(new CarService(connection));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Expose('/cars')
|
@Expose('/cars')
|
||||||
class CarService extends Controller {
|
class CarService extends Controller {
|
||||||
/// `manufacturerId` and `CarRepository` in this case would be dependency-injected. :)
|
// The `connection` will be injected.
|
||||||
@Expose('/:manufacturerId/years')
|
@Expose('/recalled_since_2008')
|
||||||
getAllYearsForManufacturer(String manufacturerId, CarRepository cars) {
|
carsRecalledSince2008(PostgreSQLConnection connection) {
|
||||||
return
|
// Instantiate a Car query, which is auto-generated. This class helps us build fluent queries easily.
|
||||||
cars
|
var cars = new CarQuery(connection);
|
||||||
.whereManufacturer(manufacturerId)
|
cars.where
|
||||||
.get()
|
..familyFriendly.equals(false)
|
||||||
.map((Car car) {
|
..recalledAt.year.greaterThanOrEqualTo(2008);
|
||||||
// Cars are deserialized automatically, woohoo!
|
|
||||||
return car.year;
|
// Shorter syntax we could use instead...
|
||||||
})
|
cars.where.recalledAt.year <= 2008;
|
||||||
.toList();
|
|
||||||
|
// `get()` returns a Stream.
|
||||||
|
// `get().toList()` returns a Future.
|
||||||
|
return cars.get().toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Expose('/create', method: 'POST')
|
||||||
|
createCar(PostgreSQLConnection connection) async {
|
||||||
|
// `package:angel_orm` generates a strongly-typed `insert` function on the query class.
|
||||||
|
// Say goodbye to typos!!!
|
||||||
|
var car = await CarQuery.insert(connection, familyFriendly: true, make: 'Honda');
|
||||||
|
|
||||||
|
// Auto-serialized using code generated by `package:angel_serialize`
|
||||||
|
return car;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# Relations
|
||||||
|
**NOTE**: This is not yet implemented.
|
||||||
|
|
||||||
|
* `@HasOne()`
|
||||||
|
* `@HasMany()`
|
||||||
|
* `@BelongsTo()`
|
||||||
|
|
||||||
|
```dart
|
||||||
|
@serializable
|
||||||
|
@orm
|
||||||
|
abstract class _Author extends Model {
|
||||||
|
@hasMany // Use the defaults, and auto-compute `foreignKey`
|
||||||
|
List<Book> books;
|
||||||
|
|
||||||
|
// Also supports parameters...
|
||||||
|
@HasMany(localKey: 'id', foreignKey: 'author_id', cascadeOnDelete: true)
|
||||||
|
List<Book> books;
|
||||||
|
|
||||||
|
@Alias('writing_utensil')
|
||||||
|
@hasOne
|
||||||
|
Pen pen;
|
||||||
|
}
|
||||||
|
```
|
|
@ -1,13 +1,3 @@
|
||||||
class Model {
|
export 'src/annotations.dart';
|
||||||
final String tableName;
|
export 'src/migration.dart';
|
||||||
const Model([this.tableName]);
|
export 'src/query.dart';
|
||||||
}
|
|
||||||
|
|
||||||
const Model model = const Model();
|
|
||||||
|
|
||||||
class Column {
|
|
||||||
final String name;
|
|
||||||
const Column([this.name]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Column column = const Column();
|
|
297
lib/builder.dart
297
lib/builder.dart
|
@ -1,296 +1 @@
|
||||||
import 'dart:async';
|
export 'src/builder/postgres/postgres.dart';
|
||||||
import 'dart:mirrors';
|
|
||||||
import 'package:analyzer/dart/element/element.dart';
|
|
||||||
import 'package:angel_serialize/angel_serialize.dart';
|
|
||||||
import 'package:build/build.dart';
|
|
||||||
import 'package:code_builder/dart/async.dart';
|
|
||||||
import 'package:code_builder/dart/core.dart';
|
|
||||||
import 'package:code_builder/code_builder.dart';
|
|
||||||
import 'package:inflection/inflection.dart';
|
|
||||||
import 'package:path/path.dart' as p;
|
|
||||||
import 'package:recase/recase.dart';
|
|
||||||
import 'package:source_gen/src/annotation.dart';
|
|
||||||
import 'package:source_gen/source_gen.dart';
|
|
||||||
import 'package:query_builder_sql/query_builder_sql.dart';
|
|
||||||
import 'angel_orm.dart';
|
|
||||||
|
|
||||||
// TODO: whereXLessThan, greaterThan, etc.
|
|
||||||
|
|
||||||
final RegExp _leadingDot = new RegExp(r'^\.+');
|
|
||||||
|
|
||||||
const List<String> QUERY_DO_NOT_OVERRIDE = const ['when'];
|
|
||||||
|
|
||||||
typedef Iterable<ExpressionBuilder> SuperArgumentProvider(
|
|
||||||
Model model, ClassElement clazz);
|
|
||||||
|
|
||||||
class AngelQueryBuilderGenerator extends GeneratorForAnnotation<Model> {
|
|
||||||
ClassMirror _baseRepositoryClassMirror;
|
|
||||||
final List<String> _imports = [
|
|
||||||
'dart:async',
|
|
||||||
'package:query_builder/query_builder.dart'
|
|
||||||
];
|
|
||||||
|
|
||||||
final Map<String, TypeBuilder> _constructorParams = {};
|
|
||||||
SuperArgumentProvider _superArgProvider;
|
|
||||||
|
|
||||||
AngelQueryBuilderGenerator(Type baseRepositoryQueryClass,
|
|
||||||
{Iterable<String> additonalImports: const [],
|
|
||||||
Map<String, TypeBuilder> constructorParams: const {},
|
|
||||||
SuperArgumentProvider superArgProvider}) {
|
|
||||||
_baseRepositoryClassMirror = reflectClass(baseRepositoryQueryClass);
|
|
||||||
_imports.addAll(additonalImports ?? []);
|
|
||||||
_constructorParams.addAll(constructorParams ?? {});
|
|
||||||
_superArgProvider = superArgProvider ??
|
|
||||||
(annotation, clazz) => [
|
|
||||||
literal(annotation.tableName?.isNotEmpty == true
|
|
||||||
? annotation.tableName
|
|
||||||
: pluralize(new ReCase(clazz.name.substring(1)).snakeCase))
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
factory AngelQueryBuilderGenerator.postgresql() =>
|
|
||||||
new AngelQueryBuilderGenerator(SqlRepositoryQuery, constructorParams: {
|
|
||||||
'connection': new TypeBuilder('PostgreSQLConnection')
|
|
||||||
}, additonalImports: [
|
|
||||||
'package:postgres/postgres.dart',
|
|
||||||
'package:query_builder_sql/query_builder_sql.dart'
|
|
||||||
]);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<String> generateForAnnotatedElement(
|
|
||||||
Element element, Model annotation, BuildStep buildStep) async {
|
|
||||||
if (element.kind != ElementKind.CLASS)
|
|
||||||
throw 'Only classes may be annotated with @model.';
|
|
||||||
var lib = generatePostgresLibrary(element, annotation, buildStep.inputId);
|
|
||||||
return prettyToSource(lib.buildAst());
|
|
||||||
}
|
|
||||||
|
|
||||||
LibraryBuilder generatePostgresLibrary(
|
|
||||||
ClassElement clazz, Model annotation, AssetId inputId) {
|
|
||||||
if (!clazz.name.startsWith('_'))
|
|
||||||
throw 'Classes annotated with @model must have names starting with an underscore.';
|
|
||||||
var lib = new LibraryBuilder();
|
|
||||||
lib.addDirectives(_imports.map((p) => new ImportBuilder(p)));
|
|
||||||
lib.addDirective(new ImportBuilder(p.basename(inputId.path)));
|
|
||||||
|
|
||||||
// Find all aliases...
|
|
||||||
Map<String, String> aliases = {};
|
|
||||||
clazz.fields.forEach((field) {
|
|
||||||
var aliasAnnotation = field.metadata
|
|
||||||
.firstWhere((ann) => matchAnnotation(Alias, ann), orElse: () => null);
|
|
||||||
if (aliasAnnotation != null) {
|
|
||||||
var alias = instantiateAnnotation(aliasAnnotation) as Alias;
|
|
||||||
aliases[field.name] = alias.name;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
lib.addMember(generateRepositoryClass(clazz, aliases));
|
|
||||||
lib.addMember(generateRepositoryQueryClass(clazz, annotation, aliases));
|
|
||||||
return lib;
|
|
||||||
}
|
|
||||||
|
|
||||||
ClassBuilder generateRepositoryClass(
|
|
||||||
ClassElement clazz, Map<String, String> aliases) {
|
|
||||||
var genClassName = clazz.name.substring(1) + 'Repository';
|
|
||||||
var genQueryClassName = genClassName + 'Query';
|
|
||||||
var genClass = new ClassBuilder(genClassName);
|
|
||||||
var genQueryType = new TypeBuilder(genQueryClassName);
|
|
||||||
|
|
||||||
// Add `connection` field + constructor
|
|
||||||
|
|
||||||
var genConstructor = new ConstructorBuilder();
|
|
||||||
_constructorParams.forEach((name, type) {
|
|
||||||
genClass.addField(varFinal(name, type: type));
|
|
||||||
genConstructor.addPositional(parameter(name), asField: true);
|
|
||||||
});
|
|
||||||
genClass.addConstructor(genConstructor);
|
|
||||||
|
|
||||||
// Add an all method
|
|
||||||
genClass.addMethod(new MethodBuilder('all',
|
|
||||||
returnType: new TypeBuilder(genQueryClassName),
|
|
||||||
returns: new TypeBuilder(genQueryClassName)
|
|
||||||
.newInstance([reference('connection')])));
|
|
||||||
|
|
||||||
// For each field, add a whereX() method...
|
|
||||||
clazz.fields
|
|
||||||
.map((field) => generateWhereFieldMethod(
|
|
||||||
field, reference('all').call([]), genQueryType, aliases))
|
|
||||||
.forEach(genClass.addMethod);
|
|
||||||
return genClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
ClassBuilder generateRepositoryQueryClass(
|
|
||||||
ClassElement clazz, Model annotation, Map<String, String> aliases) {
|
|
||||||
var modelClassName = clazz.name.substring(1);
|
|
||||||
var genClassName = clazz.name.substring(1) + 'RepositoryQuery';
|
|
||||||
var genClass = new ClassBuilder(genClassName,
|
|
||||||
asExtends: new TypeBuilder(
|
|
||||||
MirrorSystem.getName(_baseRepositoryClassMirror.simpleName),
|
|
||||||
genericTypes: [new TypeBuilder(modelClassName)]));
|
|
||||||
var genQueryType = new TypeBuilder(genClassName);
|
|
||||||
|
|
||||||
// Add `connection` field + constructor
|
|
||||||
|
|
||||||
var genConstructor = new ConstructorBuilder(
|
|
||||||
invokeSuper: _superArgProvider(annotation, clazz));
|
|
||||||
_constructorParams.forEach((name, type) {
|
|
||||||
genClass.addField(varFinal(name, type: type));
|
|
||||||
genConstructor.addPositional(parameter(name), asField: true);
|
|
||||||
});
|
|
||||||
genClass.addConstructor(genConstructor);
|
|
||||||
|
|
||||||
// For each field, add a whereX() method...
|
|
||||||
clazz.fields
|
|
||||||
.map((field) => generateWhereFieldMethod(
|
|
||||||
field, explicitThis, genQueryType, aliases))
|
|
||||||
.forEach(genClass.addMethod);
|
|
||||||
|
|
||||||
// Add orWhereX()
|
|
||||||
clazz.fields
|
|
||||||
.map((f) => generateOrWhereFieldMethod(genQueryType, f))
|
|
||||||
.forEach(genClass.addMethod);
|
|
||||||
|
|
||||||
// Override any query methods
|
|
||||||
_baseRepositoryClassMirror.instanceMembers.forEach((sym, method) {
|
|
||||||
// Skip setters, etc.
|
|
||||||
if (!method.isRegularMethod) return;
|
|
||||||
|
|
||||||
// Only if return type contains 'RepositoryQuery'
|
|
||||||
var methodName = MirrorSystem.getName(sym);
|
|
||||||
|
|
||||||
if (QUERY_DO_NOT_OVERRIDE.contains(methodName)) return;
|
|
||||||
|
|
||||||
var returnTypeName = MirrorSystem.getName(method.returnType.simpleName);
|
|
||||||
|
|
||||||
if (returnTypeName.contains('RepositoryQuery')) {
|
|
||||||
var overriddenMethod =
|
|
||||||
new MethodBuilder(methodName, returnType: genQueryType);
|
|
||||||
// Add @override
|
|
||||||
overriddenMethod.addAnnotation(lib$core.override);
|
|
||||||
|
|
||||||
// Find all positional and named args
|
|
||||||
List<String> args = [];
|
|
||||||
List<String> named = [];
|
|
||||||
|
|
||||||
method.parameters.forEach((param) {
|
|
||||||
var paramName = MirrorSystem.getName(param.simpleName);
|
|
||||||
var typeName = MirrorSystem.getName(param.type.simpleName);
|
|
||||||
var paramType = new TypeBuilder(typeName);
|
|
||||||
var genParam = parameter(paramName, [paramType]);
|
|
||||||
|
|
||||||
if (!param.isNamed) {
|
|
||||||
args.add(paramName);
|
|
||||||
overriddenMethod.addPositional(
|
|
||||||
param.isOptional ? genParam.asOptional() : genParam);
|
|
||||||
} else {
|
|
||||||
overriddenMethod.addNamed(genParam);
|
|
||||||
named.add(paramName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Invoke super
|
|
||||||
overriddenMethod.addStatement(reference('super')
|
|
||||||
.invoke(methodName, args.map(reference),
|
|
||||||
namedArguments: named.fold<Map<String, ExpressionBuilder>>(
|
|
||||||
{}, (out, k) => out..[k] = reference(k)))
|
|
||||||
.asReturn());
|
|
||||||
|
|
||||||
genClass.addMethod(overriddenMethod);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Override toSql to put keys in desired order
|
|
||||||
// TODO: Override toSql
|
|
||||||
|
|
||||||
// Add get()
|
|
||||||
genClass.addMethod(generateGetMethod(clazz, modelClassName, aliases));
|
|
||||||
|
|
||||||
return genClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
MethodBuilder generateGetMethod(
|
|
||||||
ClassElement clazz, String modelClassName, Map<String, String> aliases) {
|
|
||||||
var meth = new MethodBuilder('get')..addAnnotation(lib$core.override);
|
|
||||||
|
|
||||||
// Map rows to model...
|
|
||||||
var mapRowsToModel = new MethodBuilder.closure()
|
|
||||||
..addPositional(parameter('rows'));
|
|
||||||
|
|
||||||
// First, figure out which rows we fetched...
|
|
||||||
//
|
|
||||||
// var requestedKeys = whereFields.keys.isNotEmpty ? whereFields.keys : [<all fields...>];
|
|
||||||
|
|
||||||
var allModelFields = clazz.fields
|
|
||||||
.map((f) => aliases.containsKey(f.name) ? aliases[f.name] : f.name);
|
|
||||||
var whereFieldsKeys = reference('whereFields').property('keys');
|
|
||||||
|
|
||||||
// return new Stream<>.fromFuture(...)
|
|
||||||
meth.addStatement(lib$async.Stream.newInstance([
|
|
||||||
reference('connection')
|
|
||||||
.invoke('query', [reference('toSql').call([])]).invoke(
|
|
||||||
'then', [mapRowsToModel])
|
|
||||||
], constructor: 'fromFuture').asReturn());
|
|
||||||
|
|
||||||
return meth;
|
|
||||||
}
|
|
||||||
|
|
||||||
MethodBuilder generateWhereFieldMethod(
|
|
||||||
FieldElement field,
|
|
||||||
ExpressionBuilder baseQuery,
|
|
||||||
TypeBuilder returnType,
|
|
||||||
Map<String, String> aliases) {
|
|
||||||
var rc = new ReCase(field.name);
|
|
||||||
var whereMethod =
|
|
||||||
new MethodBuilder('where${rc.pascalCase}', returnType: returnType);
|
|
||||||
var columnName =
|
|
||||||
aliases.containsKey(field.name) ? aliases[field.name] : field.name;
|
|
||||||
whereMethod.addPositional(
|
|
||||||
parameter(field.name, [new TypeBuilder(field.type.displayName)]));
|
|
||||||
|
|
||||||
if (field.type.name == 'DateTime') {
|
|
||||||
// Add named `{time: true}`
|
|
||||||
whereMethod.addNamed(
|
|
||||||
parameter('time', [lib$core.bool]).asOptional(literal(true)));
|
|
||||||
// return all().whereDate('x', x, time: time != false);
|
|
||||||
// return all().where('x', x);
|
|
||||||
whereMethod.addStatement(baseQuery.invoke('whereDate', [
|
|
||||||
literal(columnName),
|
|
||||||
reference(field.name)
|
|
||||||
], namedArguments: {
|
|
||||||
'time': reference('time').notEquals(literal(false))
|
|
||||||
}).asReturn());
|
|
||||||
} else {
|
|
||||||
// return all().where('x', x);
|
|
||||||
whereMethod.addStatement(baseQuery.invoke(
|
|
||||||
'where', [literal(columnName), reference(field.name)]).asReturn());
|
|
||||||
}
|
|
||||||
|
|
||||||
return whereMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
MethodBuilder generateOrWhereFieldMethod(
|
|
||||||
TypeBuilder genQueryClassType, FieldElement field) {
|
|
||||||
var rc = new ReCase(field.name);
|
|
||||||
var orWhereMethod = new MethodBuilder('orWhere' + rc.pascalCase,
|
|
||||||
returnType: genQueryClassType);
|
|
||||||
orWhereMethod.addPositional(
|
|
||||||
parameter(field.name, [new TypeBuilder(field.type.displayName)]));
|
|
||||||
|
|
||||||
if (field.type.name == 'DateTime') {
|
|
||||||
orWhereMethod.addNamed(parameter('time', [lib$core.bool]));
|
|
||||||
orWhereMethod.addStatement(reference('or').call([
|
|
||||||
reference('where' + rc.pascalCase).call([
|
|
||||||
reference(field.name)
|
|
||||||
], namedArguments: {
|
|
||||||
'time': reference('time').notEquals(literal(false))
|
|
||||||
})
|
|
||||||
]).asReturn());
|
|
||||||
} else {
|
|
||||||
orWhereMethod.addStatement(reference('or').call([
|
|
||||||
reference('where' + rc.pascalCase).call([reference(field.name)])
|
|
||||||
]).asReturn());
|
|
||||||
}
|
|
||||||
|
|
||||||
return orWhereMethod;
|
|
||||||
}
|
|
||||||
}
|
|
6
lib/src/annotations.dart
Normal file
6
lib/src/annotations.dart
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
class ORM {
|
||||||
|
final String tableName;
|
||||||
|
const ORM([this.tableName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ORM orm = const ORM();
|
8
lib/src/builder/find_annotation.dart
Normal file
8
lib/src/builder/find_annotation.dart
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
|
import 'package:source_gen/src/annotation.dart';
|
||||||
|
|
||||||
|
T findAnnotation<T>(FieldElement field, Type outType) {
|
||||||
|
var first = field.metadata
|
||||||
|
.firstWhere((ann) => matchAnnotation(outType, ann), orElse: () => null);
|
||||||
|
return first == null ? null : instantiateAnnotation(first);
|
||||||
|
}
|
70
lib/src/builder/postgres/build_context.dart
Normal file
70
lib/src/builder/postgres/build_context.dart
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
|
import 'package:angel_serialize/angel_serialize.dart';
|
||||||
|
import 'package:build/build.dart';
|
||||||
|
import 'package:inflection/inflection.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:recase/recase.dart';
|
||||||
|
import '../../annotations.dart';
|
||||||
|
import '../../migration.dart';
|
||||||
|
import '../find_annotation.dart';
|
||||||
|
import 'postgres_build_context.dart';
|
||||||
|
|
||||||
|
// TODO: Should add id, createdAt, updatedAt...
|
||||||
|
PostgresBuildContext buildContext(ClassElement clazz, ORM annotation,
|
||||||
|
BuildStep buildStep, bool autoSnakeCaseNames) {
|
||||||
|
var ctx = new PostgresBuildContext(annotation, clazz.fields,
|
||||||
|
originalClassName: clazz.name,
|
||||||
|
tableName: annotation.tableName?.isNotEmpty == true
|
||||||
|
? annotation.tableName
|
||||||
|
: pluralize(new ReCase(clazz.name).snakeCase),
|
||||||
|
sourceFilename: p.basename(buildStep.inputId.path));
|
||||||
|
|
||||||
|
for (var field in clazz.fields) {
|
||||||
|
if (field.getter != null && field.setter != null) {
|
||||||
|
// Check for alias
|
||||||
|
var alias = findAnnotation<Alias>(field, Alias);
|
||||||
|
|
||||||
|
if (alias?.name?.isNotEmpty == true) {
|
||||||
|
ctx.aliases[field.name] = alias.name;
|
||||||
|
} else if (autoSnakeCaseNames != false) {
|
||||||
|
ctx.aliases[field.name] = new ReCase(field.name).snakeCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for column annotation...
|
||||||
|
var column = findAnnotation<Column>(field, Column);
|
||||||
|
|
||||||
|
if (column == null) {
|
||||||
|
// Guess what kind of column this is...
|
||||||
|
switch (field.type.name) {
|
||||||
|
case 'String':
|
||||||
|
column = const Column(type: ColumnType.VAR_CHAR);
|
||||||
|
break;
|
||||||
|
case 'int':
|
||||||
|
column = const Column(type: ColumnType.INT);
|
||||||
|
break;
|
||||||
|
case 'double':
|
||||||
|
column = const Column(type: ColumnType.DECIMAL);
|
||||||
|
break;
|
||||||
|
case 'num':
|
||||||
|
column = const Column(type: ColumnType.NUMERIC);
|
||||||
|
break;
|
||||||
|
case 'num':
|
||||||
|
column = const Column(type: ColumnType.NUMERIC);
|
||||||
|
break;
|
||||||
|
case 'bool':
|
||||||
|
column = const Column(type: ColumnType.BIT);
|
||||||
|
break;
|
||||||
|
case 'DateTime':
|
||||||
|
column = const Column(type: ColumnType.DATE_TIME);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (column == null)
|
||||||
|
throw 'Cannot infer SQL column type for field "${field.name}" with type "${field.type.name}".';
|
||||||
|
ctx.columnInfo[field.name] = column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
}
|
224
lib/src/builder/postgres/postgres.dart
Normal file
224
lib/src/builder/postgres/postgres.dart
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
|
import 'package:angel_serialize/angel_serialize.dart';
|
||||||
|
import 'package:build/build.dart';
|
||||||
|
import 'package:code_builder/dart/async.dart';
|
||||||
|
import 'package:code_builder/dart/core.dart';
|
||||||
|
import 'package:code_builder/code_builder.dart';
|
||||||
|
import 'package:inflection/inflection.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:recase/recase.dart';
|
||||||
|
import 'package:source_gen/source_gen.dart';
|
||||||
|
import '../../annotations.dart';
|
||||||
|
import '../../migration.dart';
|
||||||
|
import '../find_annotation.dart';
|
||||||
|
import 'build_context.dart';
|
||||||
|
import 'postgres_build_context.dart';
|
||||||
|
|
||||||
|
// TODO: HasOne, HasMany, BelongsTo
|
||||||
|
class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
|
||||||
|
/// If `true` (default), then field names will automatically be (de)serialized as snake_case.
|
||||||
|
final bool autoSnakeCaseNames;
|
||||||
|
|
||||||
|
const PostgresORMGenerator({this.autoSnakeCaseNames: true});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> generateForAnnotatedElement(
|
||||||
|
Element element, ORM annotation, BuildStep buildStep) {
|
||||||
|
if (element is! ClassElement)
|
||||||
|
throw 'Only classes can be annotated with @model.';
|
||||||
|
var context =
|
||||||
|
buildContext(element, annotation, buildStep, autoSnakeCaseNames);
|
||||||
|
return new Future<String>.value(
|
||||||
|
prettyToSource(generateOrmLibrary(context).buildAst()));
|
||||||
|
}
|
||||||
|
|
||||||
|
LibraryBuilder generateOrmLibrary(PostgresBuildContext ctx) {
|
||||||
|
var lib = new LibraryBuilder();
|
||||||
|
lib.addDirective(new ImportBuilder('dart:async'));
|
||||||
|
lib.addDirective(new ImportBuilder('package:angel_orm/angel_orm.dart'));
|
||||||
|
lib.addDirective(new ImportBuilder('package:postgres/postgres.dart'));
|
||||||
|
lib.addDirective(new ImportBuilder(ctx.sourceFilename));
|
||||||
|
lib.addMember(buildQueryClass(ctx));
|
||||||
|
lib.addMember(buildWhereClass(ctx));
|
||||||
|
return lib;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassBuilder buildQueryClass(PostgresBuildContext ctx) {
|
||||||
|
var clazz = new ClassBuilder(ctx.queryClassName);
|
||||||
|
|
||||||
|
// Add or + not
|
||||||
|
for (var relation in ['and', 'or', 'not']) {
|
||||||
|
clazz.addField(varFinal('_$relation',
|
||||||
|
type: new TypeBuilder('List', genericTypes: [lib$core.String]),
|
||||||
|
value: list([])));
|
||||||
|
var relationMethod =
|
||||||
|
new MethodBuilder(relation, returnType: lib$core.$void);
|
||||||
|
relationMethod.addPositional(
|
||||||
|
parameter('other', [new TypeBuilder(ctx.queryClassName)]));
|
||||||
|
var otherWhere = reference('other').property('where');
|
||||||
|
var compiled = reference('compiled');
|
||||||
|
relationMethod.addStatement(
|
||||||
|
varField('compiled', value: otherWhere.invoke('toWhereClause', [])));
|
||||||
|
relationMethod.addStatement(ifThen(compiled.notEquals(literal(null)), [
|
||||||
|
reference('_$relation').invoke('add', [compiled])
|
||||||
|
]));
|
||||||
|
clazz.addMethod(relationMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add _buildSelectQuery()
|
||||||
|
|
||||||
|
// Add where...
|
||||||
|
clazz.addField(varFinal('where',
|
||||||
|
type: new TypeBuilder(ctx.whereClassName),
|
||||||
|
value: new TypeBuilder(ctx.whereClassName).newInstance([])));
|
||||||
|
|
||||||
|
// Add get()...
|
||||||
|
clazz.addMethod(buildGetMethod(ctx));
|
||||||
|
|
||||||
|
// Add getOne()...
|
||||||
|
clazz.addMethod(buildGetOneMethod(ctx));
|
||||||
|
|
||||||
|
// Add update()...
|
||||||
|
clazz.addMethod(buildUpdateMethod(ctx));
|
||||||
|
|
||||||
|
// Add remove()...
|
||||||
|
clazz.addMethod(buildDeleteMethod(ctx));
|
||||||
|
|
||||||
|
// Add insert()...
|
||||||
|
clazz.addMethod(buildInsertMethod(ctx), asStatic: true);
|
||||||
|
|
||||||
|
// Add getAll() => new TodoQuery().get();
|
||||||
|
clazz.addMethod(
|
||||||
|
new MethodBuilder('getAll',
|
||||||
|
returnType: new TypeBuilder('Stream',
|
||||||
|
genericTypes: [new TypeBuilder(ctx.modelClassName)]),
|
||||||
|
returns: new TypeBuilder(ctx.queryClassName)
|
||||||
|
.newInstance([]).invoke('get', [])),
|
||||||
|
asStatic: true);
|
||||||
|
|
||||||
|
return clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodBuilder buildGetMethod(PostgresBuildContext ctx) {
|
||||||
|
var meth = new MethodBuilder('get',
|
||||||
|
returnType: new TypeBuilder('Stream',
|
||||||
|
genericTypes: [new TypeBuilder(ctx.modelClassName)]));
|
||||||
|
return meth;
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodBuilder buildGetOneMethod(PostgresBuildContext ctx) {
|
||||||
|
var meth = new MethodBuilder('getOne',
|
||||||
|
returnType: new TypeBuilder('Future',
|
||||||
|
genericTypes: [new TypeBuilder(ctx.modelClassName)]));
|
||||||
|
return meth;
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodBuilder buildUpdateMethod(PostgresBuildContext ctx) {
|
||||||
|
var meth = new MethodBuilder('update',
|
||||||
|
returnType: new TypeBuilder('Future',
|
||||||
|
genericTypes: [new TypeBuilder(ctx.modelClassName)]));
|
||||||
|
return meth;
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodBuilder buildDeleteMethod(PostgresBuildContext ctx) {
|
||||||
|
var meth = new MethodBuilder('delete',
|
||||||
|
returnType: new TypeBuilder('Future',
|
||||||
|
genericTypes: [new TypeBuilder(ctx.modelClassName)]));
|
||||||
|
return meth;
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodBuilder buildInsertMethod(PostgresBuildContext ctx) {
|
||||||
|
var meth = new MethodBuilder('insert',
|
||||||
|
returnType: new TypeBuilder('Future',
|
||||||
|
genericTypes: [new TypeBuilder(ctx.modelClassName)]));
|
||||||
|
meth.addPositional(
|
||||||
|
parameter('connection', [new TypeBuilder('PostgreSQLConnection')]));
|
||||||
|
|
||||||
|
// Add all named params
|
||||||
|
ctx.fields.forEach((field) {
|
||||||
|
var p = new ParameterBuilder(field.name,
|
||||||
|
type: new TypeBuilder(field.type.name));
|
||||||
|
var column = ctx.columnInfo[field.name];
|
||||||
|
if (column?.defaultValue != null)
|
||||||
|
p = p.asOptional(literal(column.defaultValue));
|
||||||
|
meth.addNamed(p);
|
||||||
|
});
|
||||||
|
|
||||||
|
return meth;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassBuilder buildWhereClass(PostgresBuildContext ctx) {
|
||||||
|
var clazz = new ClassBuilder(ctx.whereClassName);
|
||||||
|
|
||||||
|
ctx.fields.forEach((field) {
|
||||||
|
TypeBuilder queryBuilderType;
|
||||||
|
List<ExpressionBuilder> args = [];
|
||||||
|
|
||||||
|
switch (field.type.name) {
|
||||||
|
case 'String':
|
||||||
|
queryBuilderType = new TypeBuilder('StringSqlExpressionBuilder');
|
||||||
|
break;
|
||||||
|
case 'int':
|
||||||
|
queryBuilderType = new TypeBuilder('NumericSqlExpressionBuilder',
|
||||||
|
genericTypes: [lib$core.int]);
|
||||||
|
break;
|
||||||
|
case 'double':
|
||||||
|
queryBuilderType = new TypeBuilder('NumericSqlExpressionBuilder',
|
||||||
|
genericTypes: [new TypeBuilder('double')]);
|
||||||
|
break;
|
||||||
|
case 'num':
|
||||||
|
queryBuilderType = new TypeBuilder('NumericSqlExpressionBuilder');
|
||||||
|
break;
|
||||||
|
case 'bool':
|
||||||
|
queryBuilderType = new TypeBuilder('BooleanSqlExpressionBuilder');
|
||||||
|
break;
|
||||||
|
case 'DateTime':
|
||||||
|
queryBuilderType = new TypeBuilder('DateTimeSqlExpressionBuilder');
|
||||||
|
args.add(literal(ctx.resolveFieldName(field.name)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryBuilderType == null)
|
||||||
|
throw 'Could not resolve query builder type for field "${field.name}" of type "${field.type.name}".';
|
||||||
|
clazz.addField(varFinal(field.name,
|
||||||
|
type: queryBuilderType, value: queryBuilderType.newInstance(args)));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create `toWhereClause()`
|
||||||
|
var toWhereClause =
|
||||||
|
new MethodBuilder('toWhereClause', returnType: lib$core.String);
|
||||||
|
|
||||||
|
// List<String> expressions = [];
|
||||||
|
toWhereClause.addStatement(varFinal('expressions',
|
||||||
|
type: new TypeBuilder('List', genericTypes: [lib$core.String]),
|
||||||
|
value: list([])));
|
||||||
|
var expressions = reference('expressions');
|
||||||
|
|
||||||
|
// Add all expressions...
|
||||||
|
ctx.fields.forEach((field) {
|
||||||
|
var name = ctx.resolveFieldName(field.name);
|
||||||
|
var queryBuilder = reference(field.name);
|
||||||
|
var toAdd = field.type.name == 'DateTime'
|
||||||
|
? queryBuilder.invoke('compile', [])
|
||||||
|
: (literal('`$name` ') + queryBuilder.invoke('compile', []));
|
||||||
|
|
||||||
|
toWhereClause.addStatement(ifThen(queryBuilder.property('hasValue'), [
|
||||||
|
expressions.invoke('add', [toAdd])
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
// return expressions.isEmpty ? null : ('WHERE ' + expressions.join(' AND '));
|
||||||
|
toWhereClause.addStatement(expressions
|
||||||
|
.property('isEmpty')
|
||||||
|
.ternary(
|
||||||
|
literal(null),
|
||||||
|
(literal('WHERE ') + expressions.invoke('join', [literal(' AND ')]))
|
||||||
|
.parentheses())
|
||||||
|
.asReturn());
|
||||||
|
|
||||||
|
clazz.addMethod(toWhereClause);
|
||||||
|
|
||||||
|
return clazz;
|
||||||
|
}
|
||||||
|
}
|
27
lib/src/builder/postgres/postgres_build_context.dart
Normal file
27
lib/src/builder/postgres/postgres_build_context.dart
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
|
import '../../annotations.dart';
|
||||||
|
import '../../migration.dart';
|
||||||
|
|
||||||
|
class PostgresBuildContext {
|
||||||
|
final Map<String, String> aliases = {};
|
||||||
|
final Map<String, Column> columnInfo = {};
|
||||||
|
final Map<String, IndexType> indices = {};
|
||||||
|
final String originalClassName, tableName, sourceFilename;
|
||||||
|
final ORM annotation;
|
||||||
|
// Todo: We can use analyzer to copy straight from Model class
|
||||||
|
final List<FieldElement> fields;
|
||||||
|
String primaryKeyName = 'id';
|
||||||
|
|
||||||
|
PostgresBuildContext(this.annotation, this.fields,
|
||||||
|
{this.originalClassName, this.tableName, this.sourceFilename});
|
||||||
|
|
||||||
|
String get modelClassName => originalClassName.startsWith('_')
|
||||||
|
? originalClassName.substring(1)
|
||||||
|
: originalClassName;
|
||||||
|
|
||||||
|
String get queryClassName => modelClassName + 'Query';
|
||||||
|
String get whereClassName => queryClassName + 'Where';
|
||||||
|
|
||||||
|
String resolveFieldName(String name) =>
|
||||||
|
aliases.containsKey(name) ? aliases[name] : name;
|
||||||
|
}
|
296
lib/src/builder/repository.dart
Normal file
296
lib/src/builder/repository.dart
Normal file
|
@ -0,0 +1,296 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:mirrors';
|
||||||
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
|
import 'package:angel_serialize/angel_serialize.dart';
|
||||||
|
import 'package:build/build.dart';
|
||||||
|
import 'package:code_builder/dart/async.dart';
|
||||||
|
import 'package:code_builder/dart/core.dart';
|
||||||
|
import 'package:code_builder/code_builder.dart';
|
||||||
|
import 'package:inflection/inflection.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:recase/recase.dart';
|
||||||
|
import 'package:source_gen/src/annotation.dart';
|
||||||
|
import 'package:source_gen/source_gen.dart';
|
||||||
|
import 'package:query_builder_sql/query_builder_sql.dart';
|
||||||
|
import '../annotations.dart';
|
||||||
|
|
||||||
|
// TODO: whereXLessThan, greaterThan, etc.
|
||||||
|
|
||||||
|
final RegExp _leadingDot = new RegExp(r'^\.+');
|
||||||
|
|
||||||
|
const List<String> QUERY_DO_NOT_OVERRIDE = const ['when'];
|
||||||
|
|
||||||
|
typedef Iterable<ExpressionBuilder> SuperArgumentProvider(
|
||||||
|
ORM model, ClassElement clazz);
|
||||||
|
|
||||||
|
class AngelQueryBuilderGenerator extends GeneratorForAnnotation<ORM> {
|
||||||
|
ClassMirror _baseRepositoryClassMirror;
|
||||||
|
final List<String> _imports = [
|
||||||
|
'dart:async',
|
||||||
|
'package:query_builder/query_builder.dart'
|
||||||
|
];
|
||||||
|
|
||||||
|
final Map<String, TypeBuilder> _constructorParams = {};
|
||||||
|
SuperArgumentProvider _superArgProvider;
|
||||||
|
|
||||||
|
AngelQueryBuilderGenerator(Type baseRepositoryQueryClass,
|
||||||
|
{Iterable<String> additonalImports: const [],
|
||||||
|
Map<String, TypeBuilder> constructorParams: const {},
|
||||||
|
SuperArgumentProvider superArgProvider}) {
|
||||||
|
_baseRepositoryClassMirror = reflectClass(baseRepositoryQueryClass);
|
||||||
|
_imports.addAll(additonalImports ?? []);
|
||||||
|
_constructorParams.addAll(constructorParams ?? {});
|
||||||
|
_superArgProvider = superArgProvider ??
|
||||||
|
(annotation, clazz) => [
|
||||||
|
literal(annotation.tableName?.isNotEmpty == true
|
||||||
|
? annotation.tableName
|
||||||
|
: pluralize(new ReCase(clazz.name.substring(1)).snakeCase))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
factory AngelQueryBuilderGenerator.postgresql() =>
|
||||||
|
new AngelQueryBuilderGenerator(SqlRepositoryQuery, constructorParams: {
|
||||||
|
'connection': new TypeBuilder('PostgreSQLConnection')
|
||||||
|
}, additonalImports: [
|
||||||
|
'package:postgres/postgres.dart',
|
||||||
|
'package:query_builder_sql/query_builder_sql.dart'
|
||||||
|
]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> generateForAnnotatedElement(
|
||||||
|
Element element, ORM annotation, BuildStep buildStep) async {
|
||||||
|
if (element.kind != ElementKind.CLASS)
|
||||||
|
throw 'Only classes may be annotated with @model.';
|
||||||
|
var lib = generatePostgresLibrary(element, annotation, buildStep.inputId);
|
||||||
|
return prettyToSource(lib.buildAst());
|
||||||
|
}
|
||||||
|
|
||||||
|
LibraryBuilder generatePostgresLibrary(
|
||||||
|
ClassElement clazz, ORM annotation, AssetId inputId) {
|
||||||
|
if (!clazz.name.startsWith('_'))
|
||||||
|
throw 'Classes annotated with @model must have names starting with an underscore.';
|
||||||
|
var lib = new LibraryBuilder();
|
||||||
|
lib.addDirectives(_imports.map((p) => new ImportBuilder(p)));
|
||||||
|
lib.addDirective(new ImportBuilder(p.basename(inputId.path)));
|
||||||
|
|
||||||
|
// Find all aliases...
|
||||||
|
Map<String, String> aliases = {};
|
||||||
|
clazz.fields.forEach((field) {
|
||||||
|
var aliasAnnotation = field.metadata
|
||||||
|
.firstWhere((ann) => matchAnnotation(Alias, ann), orElse: () => null);
|
||||||
|
if (aliasAnnotation != null) {
|
||||||
|
var alias = instantiateAnnotation(aliasAnnotation) as Alias;
|
||||||
|
aliases[field.name] = alias.name;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
lib.addMember(generateRepositoryClass(clazz, aliases));
|
||||||
|
lib.addMember(generateRepositoryQueryClass(clazz, annotation, aliases));
|
||||||
|
return lib;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassBuilder generateRepositoryClass(
|
||||||
|
ClassElement clazz, Map<String, String> aliases) {
|
||||||
|
var genClassName = clazz.name.substring(1) + 'Repository';
|
||||||
|
var genQueryClassName = genClassName + 'Query';
|
||||||
|
var genClass = new ClassBuilder(genClassName);
|
||||||
|
var genQueryType = new TypeBuilder(genQueryClassName);
|
||||||
|
|
||||||
|
// Add `connection` field + constructor
|
||||||
|
|
||||||
|
var genConstructor = new ConstructorBuilder();
|
||||||
|
_constructorParams.forEach((name, type) {
|
||||||
|
genClass.addField(varFinal(name, type: type));
|
||||||
|
genConstructor.addPositional(parameter(name), asField: true);
|
||||||
|
});
|
||||||
|
genClass.addConstructor(genConstructor);
|
||||||
|
|
||||||
|
// Add an all method
|
||||||
|
genClass.addMethod(new MethodBuilder('all',
|
||||||
|
returnType: new TypeBuilder(genQueryClassName),
|
||||||
|
returns: new TypeBuilder(genQueryClassName)
|
||||||
|
.newInstance([reference('connection')])));
|
||||||
|
|
||||||
|
// For each field, add a whereX() method...
|
||||||
|
clazz.fields
|
||||||
|
.map((field) => generateWhereFieldMethod(
|
||||||
|
field, reference('all').call([]), genQueryType, aliases))
|
||||||
|
.forEach(genClass.addMethod);
|
||||||
|
return genClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassBuilder generateRepositoryQueryClass(
|
||||||
|
ClassElement clazz, ORM annotation, Map<String, String> aliases) {
|
||||||
|
var modelClassName = clazz.name.substring(1);
|
||||||
|
var genClassName = clazz.name.substring(1) + 'RepositoryQuery';
|
||||||
|
var genClass = new ClassBuilder(genClassName,
|
||||||
|
asExtends: new TypeBuilder(
|
||||||
|
MirrorSystem.getName(_baseRepositoryClassMirror.simpleName),
|
||||||
|
genericTypes: [new TypeBuilder(modelClassName)]));
|
||||||
|
var genQueryType = new TypeBuilder(genClassName);
|
||||||
|
|
||||||
|
// Add `connection` field + constructor
|
||||||
|
|
||||||
|
var genConstructor = new ConstructorBuilder(
|
||||||
|
invokeSuper: _superArgProvider(annotation, clazz));
|
||||||
|
_constructorParams.forEach((name, type) {
|
||||||
|
genClass.addField(varFinal(name, type: type));
|
||||||
|
genConstructor.addPositional(parameter(name), asField: true);
|
||||||
|
});
|
||||||
|
genClass.addConstructor(genConstructor);
|
||||||
|
|
||||||
|
// For each field, add a whereX() method...
|
||||||
|
clazz.fields
|
||||||
|
.map((field) => generateWhereFieldMethod(
|
||||||
|
field, explicitThis, genQueryType, aliases))
|
||||||
|
.forEach(genClass.addMethod);
|
||||||
|
|
||||||
|
// Add orWhereX()
|
||||||
|
clazz.fields
|
||||||
|
.map((f) => generateOrWhereFieldMethod(genQueryType, f))
|
||||||
|
.forEach(genClass.addMethod);
|
||||||
|
|
||||||
|
// Override any query methods
|
||||||
|
_baseRepositoryClassMirror.instanceMembers.forEach((sym, method) {
|
||||||
|
// Skip setters, etc.
|
||||||
|
if (!method.isRegularMethod) return;
|
||||||
|
|
||||||
|
// Only if return type contains 'RepositoryQuery'
|
||||||
|
var methodName = MirrorSystem.getName(sym);
|
||||||
|
|
||||||
|
if (QUERY_DO_NOT_OVERRIDE.contains(methodName)) return;
|
||||||
|
|
||||||
|
var returnTypeName = MirrorSystem.getName(method.returnType.simpleName);
|
||||||
|
|
||||||
|
if (returnTypeName.contains('RepositoryQuery')) {
|
||||||
|
var overriddenMethod =
|
||||||
|
new MethodBuilder(methodName, returnType: genQueryType);
|
||||||
|
// Add @override
|
||||||
|
overriddenMethod.addAnnotation(lib$core.override);
|
||||||
|
|
||||||
|
// Find all positional and named args
|
||||||
|
List<String> args = [];
|
||||||
|
List<String> named = [];
|
||||||
|
|
||||||
|
method.parameters.forEach((param) {
|
||||||
|
var paramName = MirrorSystem.getName(param.simpleName);
|
||||||
|
var typeName = MirrorSystem.getName(param.type.simpleName);
|
||||||
|
var paramType = new TypeBuilder(typeName);
|
||||||
|
var genParam = parameter(paramName, [paramType]);
|
||||||
|
|
||||||
|
if (!param.isNamed) {
|
||||||
|
args.add(paramName);
|
||||||
|
overriddenMethod.addPositional(
|
||||||
|
param.isOptional ? genParam.asOptional() : genParam);
|
||||||
|
} else {
|
||||||
|
overriddenMethod.addNamed(genParam);
|
||||||
|
named.add(paramName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Invoke super
|
||||||
|
overriddenMethod.addStatement(reference('super')
|
||||||
|
.invoke(methodName, args.map(reference),
|
||||||
|
namedArguments: named.fold<Map<String, ExpressionBuilder>>(
|
||||||
|
{}, (out, k) => out..[k] = reference(k)))
|
||||||
|
.asReturn());
|
||||||
|
|
||||||
|
genClass.addMethod(overriddenMethod);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Override toSql to put keys in desired order
|
||||||
|
// TODO: Override toSql
|
||||||
|
|
||||||
|
// Add get()
|
||||||
|
genClass.addMethod(generateGetMethod(clazz, modelClassName, aliases));
|
||||||
|
|
||||||
|
return genClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodBuilder generateGetMethod(
|
||||||
|
ClassElement clazz, String modelClassName, Map<String, String> aliases) {
|
||||||
|
var meth = new MethodBuilder('get')..addAnnotation(lib$core.override);
|
||||||
|
|
||||||
|
// Map rows to model...
|
||||||
|
var mapRowsToModel = new MethodBuilder.closure()
|
||||||
|
..addPositional(parameter('rows'));
|
||||||
|
|
||||||
|
// First, figure out which rows we fetched...
|
||||||
|
//
|
||||||
|
// var requestedKeys = whereFields.keys.isNotEmpty ? whereFields.keys : [<all fields...>];
|
||||||
|
|
||||||
|
var allModelFields = clazz.fields
|
||||||
|
.map((f) => aliases.containsKey(f.name) ? aliases[f.name] : f.name);
|
||||||
|
var whereFieldsKeys = reference('whereFields').property('keys');
|
||||||
|
|
||||||
|
// return new Stream<>.fromFuture(...)
|
||||||
|
meth.addStatement(lib$async.Stream.newInstance([
|
||||||
|
reference('connection')
|
||||||
|
.invoke('query', [reference('toSql').call([])]).invoke(
|
||||||
|
'then', [mapRowsToModel])
|
||||||
|
], constructor: 'fromFuture').asReturn());
|
||||||
|
|
||||||
|
return meth;
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodBuilder generateWhereFieldMethod(
|
||||||
|
FieldElement field,
|
||||||
|
ExpressionBuilder baseQuery,
|
||||||
|
TypeBuilder returnType,
|
||||||
|
Map<String, String> aliases) {
|
||||||
|
var rc = new ReCase(field.name);
|
||||||
|
var whereMethod =
|
||||||
|
new MethodBuilder('where${rc.pascalCase}', returnType: returnType);
|
||||||
|
var columnName =
|
||||||
|
aliases.containsKey(field.name) ? aliases[field.name] : field.name;
|
||||||
|
whereMethod.addPositional(
|
||||||
|
parameter(field.name, [new TypeBuilder(field.type.displayName)]));
|
||||||
|
|
||||||
|
if (field.type.name == 'DateTime') {
|
||||||
|
// Add named `{time: true}`
|
||||||
|
whereMethod.addNamed(
|
||||||
|
parameter('time', [lib$core.bool]).asOptional(literal(true)));
|
||||||
|
// return all().whereDate('x', x, time: time != false);
|
||||||
|
// return all().where('x', x);
|
||||||
|
whereMethod.addStatement(baseQuery.invoke('whereDate', [
|
||||||
|
literal(columnName),
|
||||||
|
reference(field.name)
|
||||||
|
], namedArguments: {
|
||||||
|
'time': reference('time').notEquals(literal(false))
|
||||||
|
}).asReturn());
|
||||||
|
} else {
|
||||||
|
// return all().where('x', x);
|
||||||
|
whereMethod.addStatement(baseQuery.invoke(
|
||||||
|
'where', [literal(columnName), reference(field.name)]).asReturn());
|
||||||
|
}
|
||||||
|
|
||||||
|
return whereMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodBuilder generateOrWhereFieldMethod(
|
||||||
|
TypeBuilder genQueryClassType, FieldElement field) {
|
||||||
|
var rc = new ReCase(field.name);
|
||||||
|
var orWhereMethod = new MethodBuilder('orWhere' + rc.pascalCase,
|
||||||
|
returnType: genQueryClassType);
|
||||||
|
orWhereMethod.addPositional(
|
||||||
|
parameter(field.name, [new TypeBuilder(field.type.displayName)]));
|
||||||
|
|
||||||
|
if (field.type.name == 'DateTime') {
|
||||||
|
orWhereMethod.addNamed(parameter('time', [lib$core.bool]));
|
||||||
|
orWhereMethod.addStatement(reference('or').call([
|
||||||
|
reference('where' + rc.pascalCase).call([
|
||||||
|
reference(field.name)
|
||||||
|
], namedArguments: {
|
||||||
|
'time': reference('time').notEquals(literal(false))
|
||||||
|
})
|
||||||
|
]).asReturn());
|
||||||
|
} else {
|
||||||
|
orWhereMethod.addStatement(reference('or').call([
|
||||||
|
reference('where' + rc.pascalCase).call([reference(field.name)])
|
||||||
|
]).asReturn());
|
||||||
|
}
|
||||||
|
|
||||||
|
return orWhereMethod;
|
||||||
|
}
|
||||||
|
}
|
104
lib/src/migration.dart
Normal file
104
lib/src/migration.dart
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
/// Applies additional attributes to a database column.
|
||||||
|
class Column {
|
||||||
|
/// If `true`, a SQL field will be auto-incremented.
|
||||||
|
final bool autoIncrement;
|
||||||
|
|
||||||
|
/// If `true`, a SQL field will be nullable.
|
||||||
|
final bool nullable;
|
||||||
|
|
||||||
|
/// Specifies the length of a `VARCHAR`.
|
||||||
|
final int length;
|
||||||
|
|
||||||
|
/// Explicitly defines a SQL type for this column.
|
||||||
|
final ColumnType type;
|
||||||
|
|
||||||
|
/// Specifies what kind of index this column is, if any.
|
||||||
|
final IndexType index;
|
||||||
|
|
||||||
|
/// The default value of this field.
|
||||||
|
final defaultValue;
|
||||||
|
|
||||||
|
const Column(
|
||||||
|
{this.autoIncrement: false,
|
||||||
|
this.nullable: true,
|
||||||
|
this.length,
|
||||||
|
this.type,
|
||||||
|
this.index: IndexType.NONE,
|
||||||
|
this.defaultValue});
|
||||||
|
}
|
||||||
|
|
||||||
|
class PrimaryKey extends Column {
|
||||||
|
const PrimaryKey({bool autoIncrement: true})
|
||||||
|
: super(autoIncrement: autoIncrement, index: IndexType.PRIMARY_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Column primaryKey = const PrimaryKey();
|
||||||
|
|
||||||
|
/// Maps to SQL index types.
|
||||||
|
enum IndexType {
|
||||||
|
NONE,
|
||||||
|
|
||||||
|
/// Standard index.
|
||||||
|
INDEX,
|
||||||
|
|
||||||
|
/// A primary key.
|
||||||
|
PRIMARY_KEY,
|
||||||
|
|
||||||
|
/// A *unique* index.
|
||||||
|
UNIQUE
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps to SQL data types.
|
||||||
|
///
|
||||||
|
/// Features all types from this list: http://www.tutorialspoint.com/sql/sql-data-types.htm
|
||||||
|
class ColumnType {
|
||||||
|
/// The name of this data type.
|
||||||
|
final String name;
|
||||||
|
const ColumnType._(this.name);
|
||||||
|
|
||||||
|
// Numbers
|
||||||
|
static const ColumnType BIG_INT = const ColumnType._('bigint');
|
||||||
|
static const ColumnType INT = const ColumnType._('int');
|
||||||
|
static const ColumnType SMALL_INT = const ColumnType._('smallint');
|
||||||
|
static const ColumnType TINY_INT = const ColumnType._('tinyint');
|
||||||
|
static const ColumnType BIT = const ColumnType._('bit');
|
||||||
|
static const ColumnType DECIMAL = const ColumnType._('decimal');
|
||||||
|
static const ColumnType NUMERIC = const ColumnType._('numeric');
|
||||||
|
static const ColumnType MONEY = const ColumnType._('money');
|
||||||
|
static const ColumnType SMALL_MONEY = const ColumnType._('smallmoney');
|
||||||
|
static const ColumnType FLOAT = const ColumnType._('float');
|
||||||
|
static const ColumnType REAL = const ColumnType._('real');
|
||||||
|
|
||||||
|
// Dates and times
|
||||||
|
static const ColumnType DATE_TIME = const ColumnType._('datetime');
|
||||||
|
static const ColumnType SMALL_DATE_TIME = const ColumnType._('smalldatetime');
|
||||||
|
static const ColumnType DATE = const ColumnType._('date');
|
||||||
|
static const ColumnType TIME = const ColumnType._('time');
|
||||||
|
static const ColumnType TIME_STAMP = const ColumnType._('timestamp');
|
||||||
|
|
||||||
|
// Strings
|
||||||
|
static const ColumnType CHAR = const ColumnType._('char');
|
||||||
|
static const ColumnType VAR_CHAR = const ColumnType._('varchar');
|
||||||
|
static const ColumnType VAR_CHAR_MAX = const ColumnType._('varchar(max)');
|
||||||
|
static const ColumnType TEXT = const ColumnType._('text');
|
||||||
|
|
||||||
|
// Unicode strings
|
||||||
|
static const ColumnType NCHAR = const ColumnType._('nchar');
|
||||||
|
static const ColumnType NVAR_CHAR = const ColumnType._('nvarchar');
|
||||||
|
static const ColumnType NVAR_CHAR_MAX = const ColumnType._('nvarchar(max)');
|
||||||
|
static const ColumnType NTEXT = const ColumnType._('ntext');
|
||||||
|
|
||||||
|
// Binary
|
||||||
|
static const ColumnType BINARY = const ColumnType._('binary');
|
||||||
|
static const ColumnType VAR_BINARY = const ColumnType._('varbinary');
|
||||||
|
static const ColumnType VAR_BINARY_MAX = const ColumnType._('varbinary(max)');
|
||||||
|
static const ColumnType IMAGE = const ColumnType._('image');
|
||||||
|
|
||||||
|
// Misc.
|
||||||
|
static const ColumnType SQL_VARIANT = const ColumnType._('sql_variant');
|
||||||
|
static const ColumnType UNIQUE_IDENTIFIER =
|
||||||
|
const ColumnType._('uniqueidentifier');
|
||||||
|
static const ColumnType XML = const ColumnType._('xml');
|
||||||
|
static const ColumnType CURSOR = const ColumnType._('cursor');
|
||||||
|
static const ColumnType TABLE = const ColumnType._('table');
|
||||||
|
}
|
194
lib/src/query.dart
Normal file
194
lib/src/query.dart
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
abstract class SqlExpressionBuilder {
|
||||||
|
bool get hasValue;
|
||||||
|
String compile();
|
||||||
|
}
|
||||||
|
|
||||||
|
class NumericSqlExpressionBuilder<T extends num>
|
||||||
|
implements SqlExpressionBuilder {
|
||||||
|
bool _hasValue = false;
|
||||||
|
String _op = '=';
|
||||||
|
T _value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get hasValue => _hasValue;
|
||||||
|
|
||||||
|
bool _change(String op, T value) {
|
||||||
|
_op = op;
|
||||||
|
_value = value;
|
||||||
|
return _hasValue = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String compile() {
|
||||||
|
if (_value == null) return null;
|
||||||
|
return '$_op $_value';
|
||||||
|
}
|
||||||
|
|
||||||
|
operator <(T value) => _change('<', value);
|
||||||
|
operator >(T value) => _change('>', value);
|
||||||
|
operator <=(T value) => _change('<=', value);
|
||||||
|
operator >=(T value) => _change('>=', value);
|
||||||
|
|
||||||
|
void lessThan(T value) {
|
||||||
|
_change('<', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lessThanOrEqualTo(T value) {
|
||||||
|
_change('<=', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void greaterThan(T value) {
|
||||||
|
_change('>', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void greaterThanOrEqualTo(T value) {
|
||||||
|
_change('>=', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void equals(T value) {
|
||||||
|
_change('=', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void notEquals(T value) {
|
||||||
|
_change('!=', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Escape SQL Strings
|
||||||
|
class StringSqlExpressionBuilder implements SqlExpressionBuilder {
|
||||||
|
bool _hasValue = false;
|
||||||
|
String _op = '=';
|
||||||
|
String _value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get hasValue => _hasValue;
|
||||||
|
|
||||||
|
bool _change(String op, String value) {
|
||||||
|
_op = op;
|
||||||
|
_value = value;
|
||||||
|
return _hasValue = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String compile() {
|
||||||
|
if (_value == null) return null;
|
||||||
|
return '$_op `$_value`';
|
||||||
|
}
|
||||||
|
|
||||||
|
void isEmpty() => equals('');
|
||||||
|
|
||||||
|
void equals(String value) {
|
||||||
|
_change('=', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void notEquals(String value) {
|
||||||
|
_change('!=', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void like(String value) {
|
||||||
|
_change('LIKE', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BooleanSqlExpressionBuilder implements SqlExpressionBuilder {
|
||||||
|
bool _hasValue = false;
|
||||||
|
String _op = '=';
|
||||||
|
bool _value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get hasValue => _hasValue;
|
||||||
|
|
||||||
|
bool _change(String op, bool value) {
|
||||||
|
_op = op;
|
||||||
|
_value = value;
|
||||||
|
return _hasValue = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String compile() {
|
||||||
|
if (_value == null) return null;
|
||||||
|
var v = _value ? 1 : 0;
|
||||||
|
return '$_op $v';
|
||||||
|
}
|
||||||
|
|
||||||
|
void equals(bool value) {
|
||||||
|
_change('=', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void notEquals(bool value) {
|
||||||
|
_change('!=', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DateTimeSqlExpressionBuilder implements SqlExpressionBuilder {
|
||||||
|
static final DateFormat _ymd = new DateFormat('yy-MM-dd');
|
||||||
|
static final DateFormat _ymdHms = new DateFormat('yy-MM-dd HH:mm:ss');
|
||||||
|
final NumericSqlExpressionBuilder<int> year =
|
||||||
|
new NumericSqlExpressionBuilder<int>(),
|
||||||
|
month = new NumericSqlExpressionBuilder<int>(),
|
||||||
|
day = new NumericSqlExpressionBuilder<int>(),
|
||||||
|
hour = new NumericSqlExpressionBuilder<int>(),
|
||||||
|
minute = new NumericSqlExpressionBuilder<int>(),
|
||||||
|
second = new NumericSqlExpressionBuilder<int>();
|
||||||
|
final String columnName;
|
||||||
|
String _raw;
|
||||||
|
|
||||||
|
DateTimeSqlExpressionBuilder(this.columnName);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get hasValue =>
|
||||||
|
_raw?.isNotEmpty == true ||
|
||||||
|
year.hasValue ||
|
||||||
|
month.hasValue ||
|
||||||
|
day.hasValue ||
|
||||||
|
hour.hasValue ||
|
||||||
|
minute.hasValue ||
|
||||||
|
second.hasValue;
|
||||||
|
|
||||||
|
bool _change(String _op, DateTime dt, bool time) {
|
||||||
|
var dateString = time ? _ymdHms.format(dt) : _ymd.format(dt);
|
||||||
|
_raw = '`$columnName` $_op \'$dateString\'';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator <(DateTime value) => _change('<', value, true);
|
||||||
|
operator <=(DateTime value) => _change('<=', value, true);
|
||||||
|
operator >(DateTime value) => _change('>', value, true);
|
||||||
|
operator >=(DateTime value) => _change('>=', value, true);
|
||||||
|
|
||||||
|
void equals(DateTime value, {bool includeTime: true}) {
|
||||||
|
_change('=', value, includeTime != false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lessThan(DateTime value, {bool includeTime: true}) {
|
||||||
|
_change('<', value, includeTime != false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lessThanOrEqualTo(DateTime value, {bool includeTime: true}) {
|
||||||
|
_change('<=', value, includeTime != false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void greaterThan(DateTime value, {bool includeTime: true}) {
|
||||||
|
_change('>', value, includeTime != false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void greaterThanOrEqualTo(DateTime value, {bool includeTime: true}) {
|
||||||
|
_change('>=', value, includeTime != false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String compile() {
|
||||||
|
if (_raw?.isNotEmpty == true) return _raw;
|
||||||
|
List<String> parts = [];
|
||||||
|
if (year.hasValue) parts.add('YEAR(`$columnName`) ${year.compile()}');
|
||||||
|
if (month.hasValue) parts.add('MONTH(`$columnName`) ${month.compile()}');
|
||||||
|
if (day.hasValue) parts.add('DAY(`$columnName`) ${day.compile()}');
|
||||||
|
if (hour.hasValue) parts.add('HOUR(`$columnName`) ${hour.compile()}');
|
||||||
|
if (minute.hasValue) parts.add('MINUTE(`$columnName`) ${minute.compile()}');
|
||||||
|
if (second.hasValue) parts.add('SECOND(`$columnName`) ${second.compile()}');
|
||||||
|
|
||||||
|
return parts.isEmpty ? null : parts.join(' AND ');
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ dependencies:
|
||||||
code_builder: ^1.0.0
|
code_builder: ^1.0.0
|
||||||
id: ^1.0.0
|
id: ^1.0.0
|
||||||
inflection: ^0.4.1
|
inflection: ^0.4.1
|
||||||
|
intl: ^0.15.1
|
||||||
query_builder_sql: ^1.0.0-alpha
|
query_builder_sql: ^1.0.0-alpha
|
||||||
recase: ^1.0.0
|
recase: ^1.0.0
|
||||||
source_gen: ^0.5.8
|
source_gen: ^0.5.8
|
||||||
|
|
22
test/car_test.dart
Normal file
22
test/car_test.dart
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'models/car.dart';
|
||||||
|
import 'models/car.orm.g.dart';
|
||||||
|
|
||||||
|
final DateTime MILENNIUM = new DateTime.utc(2000, 1, 1);
|
||||||
|
|
||||||
|
main() {
|
||||||
|
test('to where', () {
|
||||||
|
var query = new CarQuery();
|
||||||
|
query.where
|
||||||
|
..familyFriendly.equals(true)
|
||||||
|
..recalledAt.lessThanOrEqualTo(MILENNIUM, includeTime: false);
|
||||||
|
var whereClause = query.where.toWhereClause();
|
||||||
|
print('Where clause: $whereClause');
|
||||||
|
expect(whereClause, "WHERE `family_friendly` = 1 AND `recalled_at` <= '00-01-01'");
|
||||||
|
});
|
||||||
|
|
||||||
|
test('insert', () async {
|
||||||
|
var car = await CarQuery.insert(make: 'Mazda', familyFriendly: false);
|
||||||
|
print(car.toJson());
|
||||||
|
}, skip: 'Insert not yet implemented');
|
||||||
|
}
|
|
@ -1,21 +1,15 @@
|
||||||
library angel_orm.test.models.car;
|
library angel_orm.test.models.car;
|
||||||
|
|
||||||
import 'package:angel_framework/common.dart';
|
import 'package:angel_framework/common.dart';
|
||||||
import 'package:angel_orm/angel_orm.dart' as orm;
|
import 'package:angel_orm/angel_orm.dart';
|
||||||
import 'package:angel_serialize/angel_serialize.dart';
|
import 'package:angel_serialize/angel_serialize.dart';
|
||||||
part 'car.g.dart';
|
part 'car.g.dart';
|
||||||
|
|
||||||
@serializable
|
@serializable
|
||||||
@orm.model
|
@orm
|
||||||
class _Car extends Model {
|
class _Car extends Model {
|
||||||
@override
|
String make;
|
||||||
String id;
|
String description;
|
||||||
|
bool familyFriendly;
|
||||||
@override
|
DateTime recalledAt;
|
||||||
@Alias('created_at')
|
|
||||||
DateTime createdAt;
|
|
||||||
|
|
||||||
@override
|
|
||||||
@Alias('updated_at')
|
|
||||||
DateTime updatedAt;
|
|
||||||
}
|
}
|
|
@ -9,35 +9,36 @@ part of angel_orm.test.models.car;
|
||||||
|
|
||||||
class Car extends _Car {
|
class Car extends _Car {
|
||||||
@override
|
@override
|
||||||
String id;
|
String make;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DateTime createdAt;
|
String description;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DateTime updatedAt;
|
bool familyFriendly;
|
||||||
|
|
||||||
Car({this.id, this.createdAt, this.updatedAt});
|
@override
|
||||||
|
DateTime recalledAt;
|
||||||
|
|
||||||
|
Car({this.make, this.description, this.familyFriendly, this.recalledAt});
|
||||||
|
|
||||||
factory Car.fromJson(Map data) {
|
factory Car.fromJson(Map data) {
|
||||||
return new Car(
|
return new Car(
|
||||||
id: data['id'],
|
make: data['make'],
|
||||||
createdAt: data['created_at'] is DateTime
|
description: data['description'],
|
||||||
? data['created_at']
|
familyFriendly: data['familyFriendly'],
|
||||||
: (data['created_at'] is String
|
recalledAt: data['recalledAt'] is DateTime
|
||||||
? DateTime.parse(data['created_at'])
|
? data['recalledAt']
|
||||||
: null),
|
: (data['recalledAt'] is String
|
||||||
updatedAt: data['updated_at'] is DateTime
|
? DateTime.parse(data['recalledAt'])
|
||||||
? data['updated_at']
|
|
||||||
: (data['updated_at'] is String
|
|
||||||
? DateTime.parse(data['updated_at'])
|
|
||||||
: null));
|
: null));
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
'id': id,
|
'make': make,
|
||||||
'created_at': createdAt == null ? null : createdAt.toIso8601String(),
|
'description': description,
|
||||||
'updated_at': updatedAt == null ? null : updatedAt.toIso8601String()
|
'familyFriendly': familyFriendly,
|
||||||
|
'recalledAt': recalledAt == null ? null : recalledAt.toIso8601String()
|
||||||
};
|
};
|
||||||
|
|
||||||
static Car parse(Map map) => new Car.fromJson(map);
|
static Car parse(Map map) => new Car.fromJson(map);
|
||||||
|
|
|
@ -1,212 +1,88 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
// Generator: AngelQueryBuilderGenerator
|
// Generator: PostgresORMGenerator
|
||||||
// Target: class _Car
|
// Target: class _Car
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:query_builder/query_builder.dart';
|
import 'package:angel_orm/angel_orm.dart';
|
||||||
import 'package:postgres/postgres.dart';
|
import 'package:postgres/postgres.dart';
|
||||||
import 'package:query_builder_sql/query_builder_sql.dart';
|
|
||||||
import 'car.dart';
|
import 'car.dart';
|
||||||
|
|
||||||
class CarRepository {
|
class CarQuery {
|
||||||
final PostgreSQLConnection connection;
|
final List<String> _and = [];
|
||||||
|
|
||||||
CarRepository(this.connection);
|
final List<String> _or = [];
|
||||||
|
|
||||||
CarRepositoryQuery all() => new CarRepositoryQuery(connection);
|
final List<String> _not = [];
|
||||||
|
|
||||||
CarRepositoryQuery whereId(String id) {
|
final CarQueryWhere where = new CarQueryWhere();
|
||||||
return all().where('id', id);
|
|
||||||
}
|
|
||||||
|
|
||||||
CarRepositoryQuery whereCreatedAt(DateTime createdAt, {bool time: true}) {
|
void and(CarQuery other) {
|
||||||
return all().whereDate('created_at', createdAt, time: time != false);
|
var compiled = other.where.toWhereClause();
|
||||||
}
|
if (compiled != null) {
|
||||||
|
_and.add(compiled);
|
||||||
CarRepositoryQuery whereUpdatedAt(DateTime updatedAt, {bool time: true}) {
|
|
||||||
return all().whereDate('updated_at', updatedAt, time: time != false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CarRepositoryQuery extends SqlRepositoryQuery<Car> {
|
void or(CarQuery other) {
|
||||||
final PostgreSQLConnection connection;
|
var compiled = other.where.toWhereClause();
|
||||||
|
if (compiled != null) {
|
||||||
CarRepositoryQuery(this.connection) : super('cars');
|
_or.add(compiled);
|
||||||
|
}
|
||||||
CarRepositoryQuery whereId(String id) {
|
}
|
||||||
return this.where('id', id);
|
|
||||||
}
|
void not(CarQuery other) {
|
||||||
|
var compiled = other.where.toWhereClause();
|
||||||
CarRepositoryQuery whereCreatedAt(DateTime createdAt, {bool time: true}) {
|
if (compiled != null) {
|
||||||
return this.whereDate('created_at', createdAt, time: time != false);
|
_not.add(compiled);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
CarRepositoryQuery whereUpdatedAt(DateTime updatedAt, {bool time: true}) {
|
|
||||||
return this.whereDate('updated_at', updatedAt, time: time != false);
|
Stream<Car> get() {}
|
||||||
}
|
|
||||||
|
Future<Car> getOne() {}
|
||||||
CarRepositoryQuery orWhereId(String id) {
|
|
||||||
return or(whereId(id));
|
Future<Car> update() {}
|
||||||
}
|
|
||||||
|
Future<Car> delete() {}
|
||||||
CarRepositoryQuery orWhereCreatedAt(DateTime createdAt, {bool time}) {
|
|
||||||
return or(whereCreatedAt(createdAt, time: time != false));
|
static Future<Car> insert(PostgreSQLConnection connection,
|
||||||
}
|
{String make,
|
||||||
|
String description,
|
||||||
CarRepositoryQuery orWhereUpdatedAt(DateTime updatedAt, {bool time}) {
|
bool familyFriendly,
|
||||||
return or(whereUpdatedAt(updatedAt, time: time != false));
|
DateTime recalledAt}) {}
|
||||||
}
|
|
||||||
|
static Stream<Car> getAll() => new CarQuery().get();
|
||||||
@override
|
}
|
||||||
CarRepositoryQuery latest([String fieldName]) {
|
|
||||||
return super.latest(fieldName);
|
class CarQueryWhere {
|
||||||
}
|
final StringSqlExpressionBuilder make = new StringSqlExpressionBuilder();
|
||||||
|
|
||||||
@override
|
final StringSqlExpressionBuilder description =
|
||||||
CarRepositoryQuery oldest([String fieldName]) {
|
new StringSqlExpressionBuilder();
|
||||||
return super.oldest(fieldName);
|
|
||||||
}
|
final BooleanSqlExpressionBuilder familyFriendly =
|
||||||
|
new BooleanSqlExpressionBuilder();
|
||||||
@override
|
|
||||||
CarRepositoryQuery where(String fieldName, dynamic value) {
|
final DateTimeSqlExpressionBuilder recalledAt =
|
||||||
return super.where(fieldName, value);
|
new DateTimeSqlExpressionBuilder('recalled_at');
|
||||||
}
|
|
||||||
|
String toWhereClause() {
|
||||||
@override
|
final List<String> expressions = [];
|
||||||
CarRepositoryQuery whereNot(String fieldName, dynamic value) {
|
if (make.hasValue) {
|
||||||
return super.whereNot(fieldName, value);
|
expressions.add('`make` ' + make.compile());
|
||||||
}
|
}
|
||||||
|
if (description.hasValue) {
|
||||||
@override
|
expressions.add('`description` ' + description.compile());
|
||||||
CarRepositoryQuery whereNull(String fieldName) {
|
}
|
||||||
return super.whereNull(fieldName);
|
if (familyFriendly.hasValue) {
|
||||||
}
|
expressions.add('`family_friendly` ' + familyFriendly.compile());
|
||||||
|
}
|
||||||
@override
|
if (recalledAt.hasValue) {
|
||||||
CarRepositoryQuery whereNotNull(String fieldName) {
|
expressions.add(recalledAt.compile());
|
||||||
return super.whereNotNull(fieldName);
|
}
|
||||||
}
|
return expressions.isEmpty ? null : ('WHERE ' + expressions.join(' AND '));
|
||||||
|
|
||||||
@override
|
|
||||||
CarRepositoryQuery distinct(Iterable fieldNames) {
|
|
||||||
return super.distinct(fieldNames);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CarRepositoryQuery groupBy(String fieldName) {
|
|
||||||
return super.groupBy(fieldName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CarRepositoryQuery inRandomOrder() {
|
|
||||||
return super.inRandomOrder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CarRepositoryQuery orderBy(String fieldName, [OrderBy orderBy]) {
|
|
||||||
return super.orderBy(fieldName, orderBy);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CarRepositoryQuery select(Iterable selectors) {
|
|
||||||
return super.select(selectors);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CarRepositoryQuery skip(int count) {
|
|
||||||
return super.skip(count);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CarRepositoryQuery take(int count) {
|
|
||||||
return super.take(count);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CarRepositoryQuery join(
|
|
||||||
String otherTable, String nearColumn, String farColumn,
|
|
||||||
[JoinType joinType]) {
|
|
||||||
return super.join(otherTable, nearColumn, farColumn, joinType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CarRepositoryQuery union(RepositoryQuery other, [UnionType type]) {
|
|
||||||
return super.union(other, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CarRepositoryQuery whereBetween(
|
|
||||||
String fieldName, dynamic lower, dynamic upper) {
|
|
||||||
return super.whereBetween(fieldName, lower, upper);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CarRepositoryQuery whereDate(String fieldName, DateTime date, {bool time}) {
|
|
||||||
return super.whereDate(fieldName, date, time: time);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CarRepositoryQuery whereDay(String fieldName, int day) {
|
|
||||||
return super.whereDay(fieldName, day);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CarRepositoryQuery whereEquality(
|
|
||||||
String fieldName, dynamic value, Equality equality) {
|
|
||||||
return super.whereEquality(fieldName, value, equality);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CarRepositoryQuery whereIn(String fieldName, Iterable values) {
|
|
||||||
return super.whereIn(fieldName, values);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CarRepositoryQuery whereLike(String fieldName, dynamic value) {
|
|
||||||
return super.whereLike(fieldName, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CarRepositoryQuery whereMonth(String fieldName, int month) {
|
|
||||||
return super.whereMonth(fieldName, month);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CarRepositoryQuery whereNotBetween(
|
|
||||||
String fieldName, dynamic lower, dynamic upper) {
|
|
||||||
return super.whereNotBetween(fieldName, lower, upper);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CarRepositoryQuery whereNotIn(String fieldName, Iterable values) {
|
|
||||||
return super.whereNotIn(fieldName, values);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CarRepositoryQuery whereYear(String fieldName, int year) {
|
|
||||||
return super.whereYear(fieldName, year);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CarRepositoryQuery selfJoin(String t1, String t2) {
|
|
||||||
return super.selfJoin(t1, t2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CarRepositoryQuery or(RepositoryQuery other) {
|
|
||||||
return super.or(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CarRepositoryQuery not(RepositoryQuery other) {
|
|
||||||
return super.not(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
get() {
|
|
||||||
return new Stream.fromFuture(connection.query(toSql()).then((rows) {}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,6 @@ final PhaseGroup PHASES = new PhaseGroup()
|
||||||
new InputSet('angel_orm', const ['test/models/*.dart'])))
|
new InputSet('angel_orm', const ['test/models/*.dart'])))
|
||||||
..addPhase(new Phase()
|
..addPhase(new Phase()
|
||||||
..addAction(
|
..addAction(
|
||||||
new GeneratorBuilder([new AngelQueryBuilderGenerator.postgresql()],
|
new GeneratorBuilder([new PostgresORMGenerator()],
|
||||||
isStandalone: true, generatedExtension: '.orm.g.dart'),
|
isStandalone: true, generatedExtension: '.orm.g.dart'),
|
||||||
new InputSet('angel_orm', const ['test/models/*.dart'])));
|
new InputSet('angel_orm', const ['test/models/*.dart'])));
|
||||||
|
|
Loading…
Reference in a new issue