Working on insert.

This commit is contained in:
thosakwe 2017-06-30 18:39:17 -04:00
parent a3eb1b8a32
commit f4dbca98fc
8 changed files with 160 additions and 47 deletions

View file

@ -1,4 +1,5 @@
export 'src/annotations.dart'; export 'src/annotations.dart';
export 'src/migration.dart'; export 'src/migration.dart';
export 'src/pool.dart';
export 'src/relations.dart'; export 'src/relations.dart';
export 'src/query.dart'; export 'src/query.dart';

View file

@ -52,6 +52,10 @@ PostgresBuildContext buildContext(
// Check for column annotation... // Check for column annotation...
var column = findAnnotation<Column>(field, Column); var column = findAnnotation<Column>(field, Column);
if (column == null && field.name == 'id' && ctx.shimmed['id'] == true) {
column = const Column(type: ColumnType.SERIAL);
}
if (column == null) { if (column == null) {
// Guess what kind of column this is... // Guess what kind of column this is...
switch (field.type.name) { switch (field.type.name) {

View file

@ -19,10 +19,10 @@ import 'postgres_build_context.dart';
// TODO: HasOne, HasMany, BelongsTo // TODO: HasOne, HasMany, BelongsTo
class PostgresORMGenerator extends GeneratorForAnnotation<ORM> { class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
/// 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; final bool autoSnakeCaseNames;
/// If `true` (default), then /// If "true" (default), then
final bool autoIdAndDateFields; final bool autoIdAndDateFields;
const PostgresORMGenerator( const PostgresORMGenerator(
@ -138,6 +138,21 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
MethodBuilder buildToSqlMethod(PostgresBuildContext ctx) { MethodBuilder buildToSqlMethod(PostgresBuildContext ctx) {
// TODO: Bake relations into SQL queries // TODO: Bake relations into SQL queries
var meth = new MethodBuilder('toSql', returnType: lib$core.String); 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; return meth;
} }
@ -145,6 +160,8 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
var meth = new MethodBuilder('parseRow', var meth = new MethodBuilder('parseRow',
returnType: new TypeBuilder(ctx.modelClassName)); returnType: new TypeBuilder(ctx.modelClassName));
meth.addPositional(parameter('row', [lib$core.List])); 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 row = reference('row');
var DATE_YMD_HMS = reference('DATE_YMD_HMS'); var DATE_YMD_HMS = reference('DATE_YMD_HMS');
@ -158,7 +175,7 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
var name = ctx.resolveFieldName(field.name); var name = ctx.resolveFieldName(field.name);
var rowKey = row[literal(i++)]; var rowKey = row[literal(i++)];
if (field.type.name == 'DateTime') { if (false && field.type.name == 'DateTime') {
// TODO: Handle DATE and not just DATETIME // TODO: Handle DATE and not just DATETIME
data[name] = DATE_YMD_HMS.invoke('parse', [rowKey]); data[name] = DATE_YMD_HMS.invoke('parse', [rowKey]);
} else if (field.name == 'id' && ctx.shimmed.containsKey('id')) { } else if (field.name == 'id' && ctx.shimmed.containsKey('id')) {
@ -210,7 +227,7 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
meth.addPositional( meth.addPositional(
parameter('connection', [new TypeBuilder('PostgreSQLConnection')])); parameter('connection', [new TypeBuilder('PostgreSQLConnection')]));
meth.addStatement(reference('connection').invoke('query', [ meth.addStatement(reference('connection').invoke('query', [
literal('SELECT * FROM `${ctx.tableName}` WHERE `id` = @id;') literal('SELECT * FROM "${ctx.tableName}" WHERE "id" = @id;')
], namedArguments: { ], namedArguments: {
'substitutionValues': map({'id': reference('id')}) 'substitutionValues': map({'id': reference('id')})
}).invoke('then', [ }).invoke('then', [
@ -255,23 +272,56 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
meth.addNamed(p); meth.addNamed(p);
}); });
var buf = new StringBuffer('INSERT INTO `${ctx.tableName}` ('); var buf = new StringBuffer('INSERT INTO "${ctx.tableName}" (');
for (int i = 0; i < ctx.fields.length; i++) { int i = 0;
if (i > 0) buf.write(', '); ctx.fields.forEach((field) {
var key = ctx.resolveFieldName(ctx.fields[i].name); if (field.name == 'id')
buf.write('`$key`'); return;
else {
if (i++ > 0) buf.write(', ');
var key = ctx.resolveFieldName(field.name);
buf.write('"$key"');
} }
});
buf.write(' VALUES ('); buf.write(') VALUES (');
for (int i = 0; i < ctx.fields.length; i++) { i = 0;
if (i > 0) buf.write(', '); ctx.fields.forEach((field) {
buf.write('@${ctx.fields[i].name}'); 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(');'); 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<String, ExpressionBuilder> substitutionValues = {}; Map<String, ExpressionBuilder> substitutionValues = {};
ctx.fields.forEach((field) { ctx.fields.forEach((field) {
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); substitutionValues[field.name] = reference(field.name);
}); });
@ -285,15 +335,15 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
// Create "INSERT INTO segment" // Create "INSERT INTO segment"
var fieldNames = ctx.fields var fieldNames = ctx.fields
.map((f) => ctx.resolveFieldName(f.name)) .map((f) => ctx.resolveFieldName(f.name))
.map((k) => '`$k`') .map((k) => '"$k"')
.join(', '); .join(', ');
var insertInto = var insertInto =
literal('INSERT INTO `${ctx.tableName}` ($fieldNames) VALUES ('); literal('INSERT INTO "${ctx.tableName}" ($fieldNames) VALUES (');
meth.addStatement(buf.invoke('write', [insertInto])); meth.addStatement(buf.invoke('write', [insertInto]));
// Write all fields // Write all fields
int i = 0; int i = 0;
var backtick = literal('`'); var backtick = literal('"');
var numType = ctx.typeProvider.numType; var numType = ctx.typeProvider.numType;
var boolType = ctx.typeProvider.boolType; var boolType = ctx.typeProvider.boolType;
ctx.fields.forEach((field) { ctx.fields.forEach((field) {
@ -385,7 +435,7 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
type: queryBuilderType, value: queryBuilderType.newInstance(args))); type: queryBuilderType, value: queryBuilderType.newInstance(args)));
}); });
// Create `toWhereClause()` // Create "toWhereClause()"
var toWhereClause = var toWhereClause =
new MethodBuilder('toWhereClause', returnType: lib$core.String); new MethodBuilder('toWhereClause', returnType: lib$core.String);
@ -401,7 +451,7 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
var queryBuilder = reference(field.name); var queryBuilder = reference(field.name);
var toAdd = field.type.name == 'DateTime' var toAdd = field.type.name == 'DateTime'
? queryBuilder.invoke('compile', []) ? queryBuilder.invoke('compile', [])
: (literal('`$name` ') + queryBuilder.invoke('compile', [])); : (literal('"$name" ') + queryBuilder.invoke('compile', []));
toWhereClause.addStatement(ifThen(queryBuilder.property('hasValue'), [ toWhereClause.addStatement(ifThen(queryBuilder.property('hasValue'), [
expressions.invoke('add', [toAdd]) expressions.invoke('add', [toAdd])

View file

@ -1,8 +1,9 @@
const List<String> SQL_RESERVED_WORDS = const [
'SELECT', 'UPDATE', 'INSERT', 'DELETE', 'FROM', 'ASC', 'DESC', 'VALUES', 'RETURNING', 'ORDER', 'BY',
];
/// Applies additional attributes to a database column. /// Applies additional attributes to a database column.
class Column { class Column {
/// If `true`, a SQL field will be auto-incremented.
final bool autoIncrement;
/// If `true`, a SQL field will be nullable. /// If `true`, a SQL field will be nullable.
final bool nullable; final bool nullable;
@ -19,8 +20,7 @@ class Column {
final defaultValue; final defaultValue;
const Column( const Column(
{this.autoIncrement: false, {this.nullable: true,
this.nullable: true,
this.length, this.length,
this.type, this.type,
this.index: IndexType.NONE, this.index: IndexType.NONE,
@ -28,8 +28,10 @@ class Column {
} }
class PrimaryKey extends Column { class PrimaryKey extends Column {
const PrimaryKey({bool autoIncrement: true}) const PrimaryKey({ColumnType columnType})
: super(autoIncrement: autoIncrement, index: IndexType.PRIMARY_KEY); : super(
type: columnType ?? ColumnType.SERIAL,
index: IndexType.PRIMARY_KEY);
} }
const Column primaryKey = const PrimaryKey(); const Column primaryKey = const PrimaryKey();
@ -56,6 +58,10 @@ class ColumnType {
final String name; final String name;
const ColumnType._(this.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 // Numbers
static const ColumnType BIG_INT = const ColumnType._('bigint'); static const ColumnType BIG_INT = const ColumnType._('bigint');
static const ColumnType INT = const ColumnType._('int'); static const ColumnType INT = const ColumnType._('int');

44
lib/src/pool.dart Normal file
View file

@ -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<PostgreSQLConnection> 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<PostgreSQLConnection> _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<T> run<T>(FutureOr<T> callback(PostgreSQLConnection connection)) {
return _pool.request().then((resx) {
return _connect().then((connection) {
return new Future<T>.sync(() => callback(connection)).whenComplete(resx.release);
});
});
}
}

View file

@ -9,6 +9,8 @@ dependencies:
id: ^1.0.0 id: ^1.0.0
inflection: ^0.4.1 inflection: ^0.4.1
intl: ^0.15.1 intl: ^0.15.1
pool: ^1.0.0
postgres: ">=0.9.5 <1.0.0"
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
@ -19,8 +21,4 @@ dev_dependencies:
angel_test: ">=1.0.0 <2.0.0" angel_test: ">=1.0.0 <2.0.0"
build_runner: ^0.3.0 build_runner: ^0.3.0
http: ">= 0.11.3 < 0.12.0" http: ">= 0.11.3 < 0.12.0"
postgres: ">=0.9.5 <1.0.0"
test: ">= 0.12.13 < 0.13.0" test: ">= 0.12.13 < 0.13.0"
dependency_overrides:
source_gen:
path: ../../Dart/source_gen

View file

@ -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) { static Car parseRow(List row) {
return new Car.fromJson({ return new Car.fromJson({
@ -48,9 +56,9 @@ class CarQuery {
'make': row[1], 'make': row[1],
'description': row[2], 'description': row[2],
'family_friendly': row[3] == 1, 'family_friendly': row[3] == 1,
'recalled_at': DATE_YMD_HMS.parse(row[4]), 'recalled_at': row[4],
'created_at': DATE_YMD_HMS.parse(row[5]), 'created_at': row[5],
'updated_at': DATE_YMD_HMS.parse(row[6]) 'updated_at': row[6]
}); });
} }
@ -64,7 +72,7 @@ class CarQuery {
} }
Future<Car> getOne(int id, PostgreSQLConnection connection) { Future<Car> 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)); substitutionValues: {'id': id}).then((rows) => parseRow(rows.first));
} }
@ -80,16 +88,18 @@ class CarQuery {
DateTime recalledAt, DateTime recalledAt,
DateTime createdAt, DateTime createdAt,
DateTime updatedAt}) async { 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( 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: { substitutionValues: {
'id': id,
'make': make, 'make': make,
'description': description, 'description': description,
'familyFriendly': familyFriendly, 'familyFriendly': familyFriendly,
'recalledAt': recalledAt, 'recalledAt': recalledAt,
'createdAt': createdAt, 'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt 'updatedAt': updatedAt != null ? updatedAt : __ormNow__
}); });
return parseRow(result); return parseRow(result);
} }
@ -121,16 +131,16 @@ class CarQueryWhere {
String toWhereClause() { String toWhereClause() {
final List<String> expressions = []; final List<String> expressions = [];
if (id.hasValue) { if (id.hasValue) {
expressions.add('`id` ' + id.compile()); expressions.add('"id" ' + id.compile());
} }
if (make.hasValue) { if (make.hasValue) {
expressions.add('`make` ' + make.compile()); expressions.add('"make" ' + make.compile());
} }
if (description.hasValue) { if (description.hasValue) {
expressions.add('`description` ' + description.compile()); expressions.add('"description" ' + description.compile());
} }
if (familyFriendly.hasValue) { if (familyFriendly.hasValue) {
expressions.add('`family_friendly` ' + familyFriendly.compile()); expressions.add('"family_friendly" ' + familyFriendly.compile());
} }
if (recalledAt.hasValue) { if (recalledAt.hasValue) {
expressions.add(recalledAt.compile()); expressions.add(recalledAt.compile());

View file

@ -1,5 +1,5 @@
CREATE TABLE "cars" ( CREATE TABLE "cars" (
"id" varchar, "id" serial,
"make" varchar, "make" varchar,
"description" varchar, "description" varchar,
"family_friendly" bit, "family_friendly" bit,