Working on insert.
This commit is contained in:
parent
a3eb1b8a32
commit
f4dbca98fc
8 changed files with 160 additions and 47 deletions
|
@ -1,4 +1,5 @@
|
|||
export 'src/annotations.dart';
|
||||
export 'src/migration.dart';
|
||||
export 'src/pool.dart';
|
||||
export 'src/relations.dart';
|
||||
export 'src/query.dart';
|
|
@ -52,6 +52,10 @@ PostgresBuildContext buildContext(
|
|||
// Check for column annotation...
|
||||
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) {
|
||||
// Guess what kind of column this is...
|
||||
switch (field.type.name) {
|
||||
|
|
|
@ -19,10 +19,10 @@ 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.
|
||||
/// 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<ORM> {
|
|||
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<ORM> {
|
|||
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<ORM> {
|
|||
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<ORM> {
|
|||
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<ORM> {
|
|||
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<String, ExpressionBuilder> 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<ORM> {
|
|||
// 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<ORM> {
|
|||
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<ORM> {
|
|||
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])
|
||||
|
|
|
@ -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.
|
||||
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');
|
||||
|
|
44
lib/src/pool.dart
Normal file
44
lib/src/pool.dart
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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
|
||||
test: ">= 0.12.13 < 0.13.0"
|
|
@ -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<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));
|
||||
}
|
||||
|
||||
|
@ -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<String> 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());
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
CREATE TABLE "cars" (
|
||||
"id" varchar,
|
||||
"id" serial,
|
||||
"make" varchar,
|
||||
"description" varchar,
|
||||
"family_friendly" bit,
|
||||
|
|
Loading…
Reference in a new issue