From f4dbca98fc12faea7c41c071c99d5b10a6517c4a Mon Sep 17 00:00:00 2001 From: thosakwe Date: Fri, 30 Jun 2017 18:39:17 -0400 Subject: [PATCH] Working on insert. --- lib/angel_orm.dart | 1 + lib/src/builder/postgres/build_context.dart | 4 + lib/src/builder/postgres/postgres.dart | 92 ++++++++++++++++----- lib/src/migration.dart | 20 +++-- lib/src/pool.dart | 44 ++++++++++ pubspec.yaml | 8 +- test/models/car.orm.g.dart | 36 +++++--- test/models/car.up.g.sql | 2 +- 8 files changed, 160 insertions(+), 47 deletions(-) create mode 100644 lib/src/pool.dart diff --git a/lib/angel_orm.dart b/lib/angel_orm.dart index 1af57ece..7afd1e53 100644 --- a/lib/angel_orm.dart +++ b/lib/angel_orm.dart @@ -1,4 +1,5 @@ export 'src/annotations.dart'; export 'src/migration.dart'; +export 'src/pool.dart'; export 'src/relations.dart'; export 'src/query.dart'; \ No newline at end of file diff --git a/lib/src/builder/postgres/build_context.dart b/lib/src/builder/postgres/build_context.dart index 5a678305..48fc5735 100644 --- a/lib/src/builder/postgres/build_context.dart +++ b/lib/src/builder/postgres/build_context.dart @@ -52,6 +52,10 @@ PostgresBuildContext buildContext( // Check for column annotation... var column = findAnnotation(field, Column); + if (column == null && field.name == 'id' && ctx.shimmed['id'] == true) { + column = const Column(type: ColumnType.SERIAL); + } + if (column == null) { // Guess what kind of column this is... switch (field.type.name) { diff --git a/lib/src/builder/postgres/postgres.dart b/lib/src/builder/postgres/postgres.dart index d33c9171..d61fe854 100644 --- a/lib/src/builder/postgres/postgres.dart +++ b/lib/src/builder/postgres/postgres.dart @@ -19,10 +19,10 @@ import 'postgres_build_context.dart'; // TODO: HasOne, HasMany, BelongsTo class PostgresORMGenerator extends GeneratorForAnnotation { - /// If `true` (default), then field names will automatically be (de)serialized as snake_case. + /// If "true" (default), then field names will automatically be (de)serialized as snake_case. final bool autoSnakeCaseNames; - /// If `true` (default), then + /// If "true" (default), then final bool autoIdAndDateFields; const PostgresORMGenerator( @@ -138,6 +138,21 @@ class PostgresORMGenerator extends GeneratorForAnnotation { MethodBuilder buildToSqlMethod(PostgresBuildContext ctx) { // TODO: Bake relations into SQL queries var meth = new MethodBuilder('toSql', returnType: lib$core.String); + meth.addStatement(varField('buf', + value: lib$core.StringBuffer + .newInstance([literal('SELECT * FROM "${ctx.tableName}"')]))); + meth.addStatement(varField('whereClause', + value: reference('where').invoke('toWhereClause', []))); + var buf = reference('buf'); + var whereClause = reference('whereClause'); + + meth.addStatement(ifThen(whereClause.notEquals(literal(null)), [ + buf.invoke('write', [literal(' ') + whereClause]) + ])); + + meth.addStatement(buf.invoke('write', [literal(';')])); + meth.addStatement(buf.invoke('toString', []).asReturn()); + return meth; } @@ -145,6 +160,8 @@ class PostgresORMGenerator extends GeneratorForAnnotation { var meth = new MethodBuilder('parseRow', returnType: new TypeBuilder(ctx.modelClassName)); meth.addPositional(parameter('row', [lib$core.List])); + //meth.addStatement(lib$core.print.call( + // [literal('ROW MAP: ') + reference('row').invoke('toString', [])])); var row = reference('row'); var DATE_YMD_HMS = reference('DATE_YMD_HMS'); @@ -158,7 +175,7 @@ class PostgresORMGenerator extends GeneratorForAnnotation { var name = ctx.resolveFieldName(field.name); var rowKey = row[literal(i++)]; - if (field.type.name == 'DateTime') { + if (false && field.type.name == 'DateTime') { // TODO: Handle DATE and not just DATETIME data[name] = DATE_YMD_HMS.invoke('parse', [rowKey]); } else if (field.name == 'id' && ctx.shimmed.containsKey('id')) { @@ -210,7 +227,7 @@ class PostgresORMGenerator extends GeneratorForAnnotation { meth.addPositional( parameter('connection', [new TypeBuilder('PostgreSQLConnection')])); meth.addStatement(reference('connection').invoke('query', [ - literal('SELECT * FROM `${ctx.tableName}` WHERE `id` = @id;') + literal('SELECT * FROM "${ctx.tableName}" WHERE "id" = @id;') ], namedArguments: { 'substitutionValues': map({'id': reference('id')}) }).invoke('then', [ @@ -255,24 +272,57 @@ class PostgresORMGenerator extends GeneratorForAnnotation { meth.addNamed(p); }); - var buf = new StringBuffer('INSERT INTO `${ctx.tableName}` ('); - for (int i = 0; i < ctx.fields.length; i++) { - if (i > 0) buf.write(', '); - var key = ctx.resolveFieldName(ctx.fields[i].name); - buf.write('`$key`'); - } + var buf = new StringBuffer('INSERT INTO "${ctx.tableName}" ('); + int i = 0; + ctx.fields.forEach((field) { + if (field.name == 'id') + return; + else { + if (i++ > 0) buf.write(', '); + var key = ctx.resolveFieldName(field.name); + buf.write('"$key"'); + } + }); - buf.write(' VALUES ('); - for (int i = 0; i < ctx.fields.length; i++) { - if (i > 0) buf.write(', '); - buf.write('@${ctx.fields[i].name}'); - } + buf.write(') VALUES ('); + i = 0; + ctx.fields.forEach((field) { + if (field.name == 'id') + return; + else { + if (i++ > 0) buf.write(', '); + buf.write('@${field.name}'); + } + }); + + buf.write(')'); + + buf.write(' RETURNING ('); + i = 0; + ctx.fields.forEach((field) { + if (i++ > 0) buf.write(', '); + var name = ctx.resolveFieldName(field.name); + buf.write('"$name"'); + }); buf.write(');'); + meth.addStatement(lib$core.print.call([literal(buf.toString())])); + + if (ctx.fields.any((f) => f.name == 'createdAt' || f.name == 'updatedAt')) { + meth.addStatement(varField('__ormNow__', + value: lib$core.DateTime.newInstance([], constructor: 'now'))); + } Map substitutionValues = {}; ctx.fields.forEach((field) { - substitutionValues[field.name] = reference(field.name); + if (field.name == 'id') + return; + else if (field.name == 'createdAt' || field.name == 'updatedAt') { + var ref = reference(field.name); + substitutionValues[field.name] = + ref.notEquals(literal(null)).ternary(ref, reference('__ormNow__')); + } else + substitutionValues[field.name] = reference(field.name); }); /* @@ -285,15 +335,15 @@ class PostgresORMGenerator extends GeneratorForAnnotation { // Create "INSERT INTO segment" var fieldNames = ctx.fields .map((f) => ctx.resolveFieldName(f.name)) - .map((k) => '`$k`') + .map((k) => '"$k"') .join(', '); var insertInto = - literal('INSERT INTO `${ctx.tableName}` ($fieldNames) VALUES ('); + literal('INSERT INTO "${ctx.tableName}" ($fieldNames) VALUES ('); meth.addStatement(buf.invoke('write', [insertInto])); // Write all fields int i = 0; - var backtick = literal('`'); + var backtick = literal('"'); var numType = ctx.typeProvider.numType; var boolType = ctx.typeProvider.boolType; ctx.fields.forEach((field) { @@ -385,7 +435,7 @@ class PostgresORMGenerator extends GeneratorForAnnotation { type: queryBuilderType, value: queryBuilderType.newInstance(args))); }); - // Create `toWhereClause()` + // Create "toWhereClause()" var toWhereClause = new MethodBuilder('toWhereClause', returnType: lib$core.String); @@ -401,7 +451,7 @@ class PostgresORMGenerator extends GeneratorForAnnotation { var queryBuilder = reference(field.name); var toAdd = field.type.name == 'DateTime' ? queryBuilder.invoke('compile', []) - : (literal('`$name` ') + queryBuilder.invoke('compile', [])); + : (literal('"$name" ') + queryBuilder.invoke('compile', [])); toWhereClause.addStatement(ifThen(queryBuilder.property('hasValue'), [ expressions.invoke('add', [toAdd]) diff --git a/lib/src/migration.dart b/lib/src/migration.dart index 512a25c6..497d9a6d 100644 --- a/lib/src/migration.dart +++ b/lib/src/migration.dart @@ -1,8 +1,9 @@ +const List SQL_RESERVED_WORDS = const [ + 'SELECT', 'UPDATE', 'INSERT', 'DELETE', 'FROM', 'ASC', 'DESC', 'VALUES', 'RETURNING', 'ORDER', 'BY', +]; + /// 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; @@ -19,8 +20,7 @@ class Column { final defaultValue; const Column( - {this.autoIncrement: false, - this.nullable: true, + {this.nullable: true, this.length, this.type, this.index: IndexType.NONE, @@ -28,8 +28,10 @@ class Column { } class PrimaryKey extends Column { - const PrimaryKey({bool autoIncrement: true}) - : super(autoIncrement: autoIncrement, index: IndexType.PRIMARY_KEY); + const PrimaryKey({ColumnType columnType}) + : super( + type: columnType ?? ColumnType.SERIAL, + index: IndexType.PRIMARY_KEY); } const Column primaryKey = const PrimaryKey(); @@ -56,6 +58,10 @@ class ColumnType { final String name; const ColumnType._(this.name); + static const ColumnType SMALL_SERIAL = const ColumnType._('smallserial'); + static const ColumnType SERIAL = const ColumnType._('serial'); + static const ColumnType BIG_SERIAL = const ColumnType._('bigserial'); + // Numbers static const ColumnType BIG_INT = const ColumnType._('bigint'); static const ColumnType INT = const ColumnType._('int'); diff --git a/lib/src/pool.dart b/lib/src/pool.dart new file mode 100644 index 00000000..8d0f35f2 --- /dev/null +++ b/lib/src/pool.dart @@ -0,0 +1,44 @@ +import 'dart:async'; +import 'package:pool/pool.dart'; +import 'package:postgres/postgres.dart'; + +/// Connects to a PostgreSQL database, whether synchronously or asynchronously. +typedef FutureOr PostgreSQLConnector(); + +/// Pools connections to a PostgreSQL database. +class PostgreSQLConnectionPool { + Pool _pool; + + /// The maximum number of concurrent connections to the database. + /// + /// Default: `5` + final int concurrency; + + /// An optional timeout for pooled connections to execute. + final Duration timeout; + + /// A function that connects this pool to the database, on-demand. + final PostgreSQLConnector connector; + + PostgreSQLConnectionPool(this.connector, + {this.concurrency: 5, this.timeout}) { + _pool = new Pool(concurrency, timeout: timeout); + } + + Future _connect() async { + var connection = await connector() as PostgreSQLConnection; + await connection.open(); + return connection; + } + + /// Connects to the database, and then executes the [callback]. + /// + /// Returns the result of [callback]. + Future run(FutureOr callback(PostgreSQLConnection connection)) { + return _pool.request().then((resx) { + return _connect().then((connection) { + return new Future.sync(() => callback(connection)).whenComplete(resx.release); + }); + }); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index c1b09fa9..28c29e7e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,6 +9,8 @@ dependencies: id: ^1.0.0 inflection: ^0.4.1 intl: ^0.15.1 + pool: ^1.0.0 + postgres: ">=0.9.5 <1.0.0" query_builder_sql: ^1.0.0-alpha recase: ^1.0.0 source_gen: ^0.5.8 @@ -19,8 +21,4 @@ dev_dependencies: angel_test: ">=1.0.0 <2.0.0" build_runner: ^0.3.0 http: ">= 0.11.3 < 0.12.0" - postgres: ">=0.9.5 <1.0.0" - test: ">= 0.12.13 < 0.13.0" -dependency_overrides: - source_gen: - path: ../../Dart/source_gen \ No newline at end of file + test: ">= 0.12.13 < 0.13.0" \ No newline at end of file diff --git a/test/models/car.orm.g.dart b/test/models/car.orm.g.dart index 4b9945b4..5b335825 100644 --- a/test/models/car.orm.g.dart +++ b/test/models/car.orm.g.dart @@ -40,7 +40,15 @@ class CarQuery { } } - String toSql() {} + String toSql() { + var buf = new StringBuffer('SELECT * FROM "cars"'); + var whereClause = where.toWhereClause(); + if (whereClause != null) { + buf.write(' ' + whereClause); + } + buf.write(';'); + return buf.toString(); + } static Car parseRow(List row) { return new Car.fromJson({ @@ -48,9 +56,9 @@ class CarQuery { 'make': row[1], 'description': row[2], 'family_friendly': row[3] == 1, - 'recalled_at': DATE_YMD_HMS.parse(row[4]), - 'created_at': DATE_YMD_HMS.parse(row[5]), - 'updated_at': DATE_YMD_HMS.parse(row[6]) + 'recalled_at': row[4], + 'created_at': row[5], + 'updated_at': row[6] }); } @@ -64,7 +72,7 @@ class CarQuery { } Future getOne(int id, PostgreSQLConnection connection) { - return connection.query('SELECT * FROM `cars` WHERE `id` = @id;', + return connection.query('SELECT * FROM "cars" WHERE "id" = @id;', substitutionValues: {'id': id}).then((rows) => parseRow(rows.first)); } @@ -80,16 +88,18 @@ class CarQuery { DateTime recalledAt, DateTime createdAt, DateTime updatedAt}) async { + print( + 'INSERT INTO "cars" ("make", "description", "family_friendly", "recalled_at", "created_at", "updated_at") VALUES (@make, @description, @familyFriendly, @recalledAt, @createdAt, @updatedAt) RETURNING ("id", "make", "description", "family_friendly", "recalled_at", "created_at", "updated_at");'); + var __ormNow__ = new DateTime.now(); var result = await connection.query( - 'INSERT INTO `cars` (`id`, `make`, `description`, `family_friendly`, `recalled_at`, `created_at`, `updated_at` VALUES (@id, @make, @description, @familyFriendly, @recalledAt, @createdAt, @updatedAt);', + 'INSERT INTO "cars" ("make", "description", "family_friendly", "recalled_at", "created_at", "updated_at") VALUES (@make, @description, @familyFriendly, @recalledAt, @createdAt, @updatedAt) RETURNING ("id", "make", "description", "family_friendly", "recalled_at", "created_at", "updated_at");', substitutionValues: { - 'id': id, 'make': make, 'description': description, 'familyFriendly': familyFriendly, 'recalledAt': recalledAt, - 'createdAt': createdAt, - 'updatedAt': updatedAt + 'createdAt': createdAt != null ? createdAt : __ormNow__, + 'updatedAt': updatedAt != null ? updatedAt : __ormNow__ }); return parseRow(result); } @@ -121,16 +131,16 @@ class CarQueryWhere { String toWhereClause() { final List expressions = []; if (id.hasValue) { - expressions.add('`id` ' + id.compile()); + expressions.add('"id" ' + id.compile()); } if (make.hasValue) { - expressions.add('`make` ' + make.compile()); + expressions.add('"make" ' + make.compile()); } if (description.hasValue) { - expressions.add('`description` ' + description.compile()); + expressions.add('"description" ' + description.compile()); } if (familyFriendly.hasValue) { - expressions.add('`family_friendly` ' + familyFriendly.compile()); + expressions.add('"family_friendly" ' + familyFriendly.compile()); } if (recalledAt.hasValue) { expressions.add(recalledAt.compile()); diff --git a/test/models/car.up.g.sql b/test/models/car.up.g.sql index b801d466..9fae05af 100644 --- a/test/models/car.up.g.sql +++ b/test/models/car.up.g.sql @@ -1,5 +1,5 @@ CREATE TABLE "cars" ( - "id" varchar, + "id" serial, "make" varchar, "description" varchar, "family_friendly" bit,