Split up query.dart into separate files
This commit is contained in:
parent
6bad589fde
commit
a0d3029ac0
13 changed files with 378 additions and 350 deletions
|
@ -1,5 +1,15 @@
|
|||
export 'src/annotations.dart';
|
||||
export 'src/builder.dart';
|
||||
export 'src/join_builder.dart';
|
||||
export 'src/join_on.dart';
|
||||
export 'src/map_query_values.dart';
|
||||
export 'src/migration.dart';
|
||||
export 'src/relations.dart';
|
||||
export 'src/order_by.dart';
|
||||
export 'src/query_base.dart';
|
||||
export 'src/query_executor.dart';
|
||||
export 'src/query_values.dart';
|
||||
export 'src/query_where.dart';
|
||||
export 'src/query.dart';
|
||||
export 'src/relations.dart';
|
||||
export 'src/union.dart';
|
||||
export 'src/util.dart';
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:charcode/ascii.dart';
|
||||
import 'package:intl/intl.dart' show DateFormat;
|
||||
import 'package:string_scanner/string_scanner.dart';
|
||||
import 'query.dart';
|
||||
import 'util.dart';
|
||||
|
||||
final DateFormat dateYmd = DateFormat('yyyy-MM-dd');
|
||||
final DateFormat dateYmdHms = DateFormat('yyyy-MM-dd HH:mm:ss');
|
||||
|
|
58
angel_orm/lib/src/join_builder.dart
Normal file
58
angel_orm/lib/src/join_builder.dart
Normal file
|
@ -0,0 +1,58 @@
|
|||
import 'annotations.dart';
|
||||
import 'query.dart';
|
||||
|
||||
/// Builds a SQL `JOIN` query.
|
||||
class JoinBuilder {
|
||||
final JoinType type;
|
||||
final Query from;
|
||||
final String to, key, value, op, alias;
|
||||
final List<String> additionalFields;
|
||||
|
||||
JoinBuilder(this.type, this.from, this.to, this.key, this.value,
|
||||
{this.op = '=', this.alias, this.additionalFields = const []}) {
|
||||
assert(to != null,
|
||||
'computation of this join threw an error, and returned null.');
|
||||
}
|
||||
|
||||
String get fieldName {
|
||||
var right = '$to.$value';
|
||||
if (alias != null) right = '$alias.$value';
|
||||
return right;
|
||||
}
|
||||
|
||||
String nameFor(String name) {
|
||||
var right = '$to.$name';
|
||||
if (alias != null) right = '$alias.$name';
|
||||
return right;
|
||||
}
|
||||
|
||||
String compile(Set<String> trampoline) {
|
||||
if (to == null) return null;
|
||||
var b = StringBuffer();
|
||||
var left = '${from.tableName}.$key';
|
||||
var right = fieldName;
|
||||
|
||||
switch (type) {
|
||||
case JoinType.inner:
|
||||
b.write(' INNER JOIN');
|
||||
break;
|
||||
case JoinType.left:
|
||||
b.write(' LEFT JOIN');
|
||||
break;
|
||||
case JoinType.right:
|
||||
b.write(' RIGHT JOIN');
|
||||
break;
|
||||
case JoinType.full:
|
||||
b.write(' FULL OUTER JOIN');
|
||||
break;
|
||||
case JoinType.self:
|
||||
b.write(' SELF JOIN');
|
||||
break;
|
||||
}
|
||||
|
||||
b.write(' $to');
|
||||
if (alias != null) b.write(' $alias');
|
||||
b.write(' ON $left$op$right');
|
||||
return b.toString();
|
||||
}
|
||||
}
|
8
angel_orm/lib/src/join_on.dart
Normal file
8
angel_orm/lib/src/join_on.dart
Normal file
|
@ -0,0 +1,8 @@
|
|||
import 'builder.dart';
|
||||
|
||||
class JoinOn {
|
||||
final SqlExpressionBuilder key;
|
||||
final SqlExpressionBuilder value;
|
||||
|
||||
JoinOn(this.key, this.value);
|
||||
}
|
0
angel_orm/lib/src/map_query_values.dart
Normal file
0
angel_orm/lib/src/map_query_values.dart
Normal file
8
angel_orm/lib/src/order_by.dart
Normal file
8
angel_orm/lib/src/order_by.dart
Normal file
|
@ -0,0 +1,8 @@
|
|||
class OrderBy {
|
||||
final String key;
|
||||
final bool descending;
|
||||
|
||||
const OrderBy(this.key, {this.descending = false});
|
||||
|
||||
String compile() => descending ? '$key DESC' : '$key ASC';
|
||||
}
|
|
@ -1,115 +1,11 @@
|
|||
import 'dart:async';
|
||||
import 'package:charcode/ascii.dart';
|
||||
import 'annotations.dart';
|
||||
import 'builder.dart';
|
||||
|
||||
bool isAscii(int ch) => ch >= $nul && ch <= $del;
|
||||
|
||||
/// A base class for objects that compile to SQL queries, typically within an ORM.
|
||||
abstract class QueryBase<T> {
|
||||
/// Casts to perform when querying the database.
|
||||
Map<String, String> get casts => {};
|
||||
|
||||
/// Values to insert into a prepared statement.
|
||||
final Map<String, dynamic> substitutionValues = {};
|
||||
|
||||
/// The table against which to execute this query.
|
||||
String get tableName;
|
||||
|
||||
/// The list of fields returned by this query.
|
||||
///
|
||||
/// If it's `null`, then this query will perform a `SELECT *`.
|
||||
List<String> get fields;
|
||||
|
||||
/// A String of all [fields], joined by a comma (`,`).
|
||||
String get fieldSet => fields.map((k) {
|
||||
var cast = casts[k];
|
||||
return cast == null ? k : 'CAST ($k AS $cast)';
|
||||
}).join(', ');
|
||||
|
||||
String compile(Set<String> trampoline,
|
||||
{bool includeTableName = false, String preamble, bool withFields = true});
|
||||
|
||||
T deserialize(List row);
|
||||
|
||||
Future<List<T>> get(QueryExecutor executor) async {
|
||||
var sql = compile(Set());
|
||||
return executor
|
||||
.query(tableName, sql, substitutionValues)
|
||||
.then((it) => it.map(deserialize).toList());
|
||||
}
|
||||
|
||||
Future<T> getOne(QueryExecutor executor) {
|
||||
return get(executor).then((it) => it.isEmpty ? null : it.first);
|
||||
}
|
||||
|
||||
Union<T> union(QueryBase<T> other) {
|
||||
return Union(this, other);
|
||||
}
|
||||
|
||||
Union<T> unionAll(QueryBase<T> other) {
|
||||
return Union(this, other, all: true);
|
||||
}
|
||||
}
|
||||
|
||||
class OrderBy {
|
||||
final String key;
|
||||
final bool descending;
|
||||
|
||||
const OrderBy(this.key, {this.descending = false});
|
||||
|
||||
String compile() => descending ? '$key DESC' : '$key ASC';
|
||||
}
|
||||
|
||||
/// The ORM prefers using substitution values, which allow for prepared queries,
|
||||
/// and prevent SQL injection attacks.
|
||||
@deprecated
|
||||
String toSql(Object obj, {bool withQuotes = true}) {
|
||||
if (obj is DateTime) {
|
||||
return withQuotes ? "'${dateYmdHms.format(obj)}'" : dateYmdHms.format(obj);
|
||||
} else if (obj is bool) {
|
||||
return obj ? 'TRUE' : 'FALSE';
|
||||
} else if (obj == null) {
|
||||
return 'NULL';
|
||||
} else if (obj is String) {
|
||||
var b = StringBuffer();
|
||||
var escaped = false;
|
||||
var it = obj.runes.iterator;
|
||||
|
||||
while (it.moveNext()) {
|
||||
if (it.current == $nul) {
|
||||
continue; // Skip null byte
|
||||
} else if (it.current == $single_quote) {
|
||||
escaped = true;
|
||||
b.write('\\x');
|
||||
b.write(it.current.toRadixString(16).padLeft(2, '0'));
|
||||
} else if (isAscii(it.current)) {
|
||||
b.writeCharCode(it.current);
|
||||
} else if (it.currentSize == 1) {
|
||||
escaped = true;
|
||||
b.write('\\u');
|
||||
b.write(it.current.toRadixString(16).padLeft(4, '0'));
|
||||
} else if (it.currentSize == 2) {
|
||||
escaped = true;
|
||||
b.write('\\U');
|
||||
b.write(it.current.toRadixString(16).padLeft(8, '0'));
|
||||
} else {
|
||||
throw UnsupportedError(
|
||||
'toSql() cannot encode a rune of size (${it.currentSize})');
|
||||
}
|
||||
}
|
||||
|
||||
if (!withQuotes) {
|
||||
return b.toString();
|
||||
} else if (escaped) {
|
||||
return "E'$b'";
|
||||
} else {
|
||||
return "'$b'";
|
||||
}
|
||||
} else {
|
||||
return obj.toString();
|
||||
}
|
||||
}
|
||||
import 'join_builder.dart';
|
||||
import 'order_by.dart';
|
||||
import 'query_base.dart';
|
||||
import 'query_executor.dart';
|
||||
import 'query_values.dart';
|
||||
import 'query_where.dart';
|
||||
|
||||
/// A SQL `SELECT` query builder.
|
||||
abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
|
||||
|
@ -424,241 +320,3 @@ abstract class Query<T, Where extends QueryWhere> extends QueryBase<T> {
|
|||
return update(executor).then((it) => it.isEmpty ? null : it.first);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class QueryValues {
|
||||
Map<String, String> get casts => {};
|
||||
|
||||
Map<String, dynamic> toMap();
|
||||
|
||||
String applyCast(String name, String sub) {
|
||||
if (casts.containsKey(name)) {
|
||||
var type = casts[name];
|
||||
return 'CAST ($sub as $type)';
|
||||
} else {
|
||||
return sub;
|
||||
}
|
||||
}
|
||||
|
||||
String compileInsert(Query query, String tableName) {
|
||||
var data = Map<String, dynamic>.from(toMap());
|
||||
var keys = data.keys.toList();
|
||||
keys.where((k) => !query.fields.contains(k)).forEach(data.remove);
|
||||
if (data.isEmpty) return null;
|
||||
|
||||
var fieldSet = data.keys.join(', ');
|
||||
var b = StringBuffer('INSERT INTO $tableName ($fieldSet) VALUES (');
|
||||
int i = 0;
|
||||
|
||||
for (var entry in data.entries) {
|
||||
if (i++ > 0) b.write(', ');
|
||||
|
||||
var name = query.reserveName(entry.key);
|
||||
var s = applyCast(entry.key, '@$name');
|
||||
query.substitutionValues[name] = entry.value;
|
||||
b.write(s);
|
||||
}
|
||||
|
||||
b.write(')');
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
String compileForUpdate(Query query) {
|
||||
var data = toMap();
|
||||
if (data.isEmpty) return null;
|
||||
var b = StringBuffer('SET');
|
||||
int i = 0;
|
||||
|
||||
for (var entry in data.entries) {
|
||||
if (i++ > 0) b.write(',');
|
||||
b.write(' ');
|
||||
b.write(entry.key);
|
||||
b.write('=');
|
||||
|
||||
var name = query.reserveName(entry.key);
|
||||
var s = applyCast(entry.key, '@$name');
|
||||
query.substitutionValues[name] = entry.value;
|
||||
b.write(s);
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/// A [QueryValues] implementation that simply writes to a [Map].
|
||||
class MapQueryValues extends QueryValues {
|
||||
final Map<String, dynamic> values = {};
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toMap() => values;
|
||||
}
|
||||
|
||||
/// Builds a SQL `WHERE` clause.
|
||||
abstract class QueryWhere {
|
||||
final Set<QueryWhere> _and = Set();
|
||||
final Set<QueryWhere> _not = Set();
|
||||
final Set<QueryWhere> _or = Set();
|
||||
|
||||
Iterable<SqlExpressionBuilder> get expressionBuilders;
|
||||
|
||||
void and(QueryWhere other) {
|
||||
_and.add(other);
|
||||
}
|
||||
|
||||
void not(QueryWhere other) {
|
||||
_not.add(other);
|
||||
}
|
||||
|
||||
void or(QueryWhere other) {
|
||||
_or.add(other);
|
||||
}
|
||||
|
||||
String compile({String tableName}) {
|
||||
var b = StringBuffer();
|
||||
int i = 0;
|
||||
|
||||
for (var builder in expressionBuilders) {
|
||||
var key = builder.columnName;
|
||||
if (tableName != null) key = '$tableName.$key';
|
||||
if (builder.hasValue) {
|
||||
if (i++ > 0) b.write(' AND ');
|
||||
if (builder is DateTimeSqlExpressionBuilder ||
|
||||
(builder is JsonSqlExpressionBuilder && builder.hasRaw)) {
|
||||
if (tableName != null) b.write('$tableName.');
|
||||
b.write(builder.compile());
|
||||
} else {
|
||||
b.write('$key ${builder.compile()}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var other in _and) {
|
||||
var sql = other.compile();
|
||||
if (sql.isNotEmpty) b.write(' AND $sql');
|
||||
}
|
||||
|
||||
for (var other in _not) {
|
||||
var sql = other.compile();
|
||||
if (sql.isNotEmpty) b.write(' NOT $sql');
|
||||
}
|
||||
|
||||
for (var other in _or) {
|
||||
var sql = other.compile();
|
||||
if (sql.isNotEmpty) b.write(' OR $sql');
|
||||
}
|
||||
|
||||
return b.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the `UNION` of two subqueries.
|
||||
class Union<T> extends QueryBase<T> {
|
||||
/// The subject(s) of this binary operation.
|
||||
final QueryBase<T> left, right;
|
||||
|
||||
/// Whether this is a `UNION ALL` operation.
|
||||
final bool all;
|
||||
|
||||
@override
|
||||
final String tableName;
|
||||
|
||||
Union(this.left, this.right, {this.all = false, String tableName})
|
||||
: this.tableName = tableName ?? left.tableName {
|
||||
substitutionValues
|
||||
..addAll(left.substitutionValues)
|
||||
..addAll(right.substitutionValues);
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> get fields => left.fields;
|
||||
|
||||
@override
|
||||
T deserialize(List row) => left.deserialize(row);
|
||||
|
||||
@override
|
||||
String compile(Set<String> trampoline,
|
||||
{bool includeTableName = false,
|
||||
String preamble,
|
||||
bool withFields = true}) {
|
||||
var selector = all == true ? 'UNION ALL' : 'UNION';
|
||||
var t1 = Set<String>.from(trampoline);
|
||||
var t2 = Set<String>.from(trampoline);
|
||||
return '(${left.compile(t1, includeTableName: includeTableName)}) $selector (${right.compile(t2, includeTableName: includeTableName)})';
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a SQL `JOIN` query.
|
||||
class JoinBuilder {
|
||||
final JoinType type;
|
||||
final Query from;
|
||||
final String to, key, value, op, alias;
|
||||
final List<String> additionalFields;
|
||||
|
||||
JoinBuilder(this.type, this.from, this.to, this.key, this.value,
|
||||
{this.op = '=', this.alias, this.additionalFields = const []}) {
|
||||
assert(to != null,
|
||||
'computation of this join threw an error, and returned null.');
|
||||
}
|
||||
|
||||
String get fieldName {
|
||||
var right = '$to.$value';
|
||||
if (alias != null) right = '$alias.$value';
|
||||
return right;
|
||||
}
|
||||
|
||||
String nameFor(String name) {
|
||||
var right = '$to.$name';
|
||||
if (alias != null) right = '$alias.$name';
|
||||
return right;
|
||||
}
|
||||
|
||||
String compile(Set<String> trampoline) {
|
||||
if (to == null) return null;
|
||||
var b = StringBuffer();
|
||||
var left = '${from.tableName}.$key';
|
||||
var right = fieldName;
|
||||
|
||||
switch (type) {
|
||||
case JoinType.inner:
|
||||
b.write(' INNER JOIN');
|
||||
break;
|
||||
case JoinType.left:
|
||||
b.write(' LEFT JOIN');
|
||||
break;
|
||||
case JoinType.right:
|
||||
b.write(' RIGHT JOIN');
|
||||
break;
|
||||
case JoinType.full:
|
||||
b.write(' FULL OUTER JOIN');
|
||||
break;
|
||||
case JoinType.self:
|
||||
b.write(' SELF JOIN');
|
||||
break;
|
||||
}
|
||||
|
||||
b.write(' $to');
|
||||
if (alias != null) b.write(' $alias');
|
||||
b.write(' ON $left$op$right');
|
||||
return b.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class JoinOn {
|
||||
final SqlExpressionBuilder key;
|
||||
final SqlExpressionBuilder value;
|
||||
|
||||
JoinOn(this.key, this.value);
|
||||
}
|
||||
|
||||
/// An abstract interface that performs queries.
|
||||
///
|
||||
/// This class should be implemented.
|
||||
abstract class QueryExecutor {
|
||||
const QueryExecutor();
|
||||
|
||||
/// Executes a single query.
|
||||
Future<List<List>> query(
|
||||
String tableName, String query, Map<String, dynamic> substitutionValues,
|
||||
[List<String> returningFields]);
|
||||
|
||||
/// Begins a database transaction.
|
||||
Future<T> transaction<T>(FutureOr<T> f());
|
||||
}
|
||||
|
|
50
angel_orm/lib/src/query_base.dart
Normal file
50
angel_orm/lib/src/query_base.dart
Normal file
|
@ -0,0 +1,50 @@
|
|||
import 'dart:async';
|
||||
import 'query_executor.dart';
|
||||
import 'union.dart';
|
||||
|
||||
/// A base class for objects that compile to SQL queries, typically within an ORM.
|
||||
abstract class QueryBase<T> {
|
||||
/// Casts to perform when querying the database.
|
||||
Map<String, String> get casts => {};
|
||||
|
||||
/// Values to insert into a prepared statement.
|
||||
final Map<String, dynamic> substitutionValues = {};
|
||||
|
||||
/// The table against which to execute this query.
|
||||
String get tableName;
|
||||
|
||||
/// The list of fields returned by this query.
|
||||
///
|
||||
/// If it's `null`, then this query will perform a `SELECT *`.
|
||||
List<String> get fields;
|
||||
|
||||
/// A String of all [fields], joined by a comma (`,`).
|
||||
String get fieldSet => fields.map((k) {
|
||||
var cast = casts[k];
|
||||
return cast == null ? k : 'CAST ($k AS $cast)';
|
||||
}).join(', ');
|
||||
|
||||
String compile(Set<String> trampoline,
|
||||
{bool includeTableName = false, String preamble, bool withFields = true});
|
||||
|
||||
T deserialize(List row);
|
||||
|
||||
Future<List<T>> get(QueryExecutor executor) async {
|
||||
var sql = compile(Set());
|
||||
return executor
|
||||
.query(tableName, sql, substitutionValues)
|
||||
.then((it) => it.map(deserialize).toList());
|
||||
}
|
||||
|
||||
Future<T> getOne(QueryExecutor executor) {
|
||||
return get(executor).then((it) => it.isEmpty ? null : it.first);
|
||||
}
|
||||
|
||||
Union<T> union(QueryBase<T> other) {
|
||||
return Union(this, other);
|
||||
}
|
||||
|
||||
Union<T> unionAll(QueryBase<T> other) {
|
||||
return Union(this, other, all: true);
|
||||
}
|
||||
}
|
16
angel_orm/lib/src/query_executor.dart
Normal file
16
angel_orm/lib/src/query_executor.dart
Normal file
|
@ -0,0 +1,16 @@
|
|||
import 'dart:async';
|
||||
|
||||
/// An abstract interface that performs queries.
|
||||
///
|
||||
/// This class should be implemented.
|
||||
abstract class QueryExecutor {
|
||||
const QueryExecutor();
|
||||
|
||||
/// Executes a single query.
|
||||
Future<List<List>> query(
|
||||
String tableName, String query, Map<String, dynamic> substitutionValues,
|
||||
[List<String> returningFields]);
|
||||
|
||||
/// Begins a database transaction.
|
||||
Future<T> transaction<T>(FutureOr<T> f());
|
||||
}
|
67
angel_orm/lib/src/query_values.dart
Normal file
67
angel_orm/lib/src/query_values.dart
Normal file
|
@ -0,0 +1,67 @@
|
|||
import 'query.dart';
|
||||
|
||||
abstract class QueryValues {
|
||||
Map<String, String> get casts => {};
|
||||
|
||||
Map<String, dynamic> toMap();
|
||||
|
||||
String applyCast(String name, String sub) {
|
||||
if (casts.containsKey(name)) {
|
||||
var type = casts[name];
|
||||
return 'CAST ($sub as $type)';
|
||||
} else {
|
||||
return sub;
|
||||
}
|
||||
}
|
||||
|
||||
String compileInsert(Query query, String tableName) {
|
||||
var data = Map<String, dynamic>.from(toMap());
|
||||
var keys = data.keys.toList();
|
||||
keys.where((k) => !query.fields.contains(k)).forEach(data.remove);
|
||||
if (data.isEmpty) return null;
|
||||
|
||||
var fieldSet = data.keys.join(', ');
|
||||
var b = StringBuffer('INSERT INTO $tableName ($fieldSet) VALUES (');
|
||||
int i = 0;
|
||||
|
||||
for (var entry in data.entries) {
|
||||
if (i++ > 0) b.write(', ');
|
||||
|
||||
var name = query.reserveName(entry.key);
|
||||
var s = applyCast(entry.key, '@$name');
|
||||
query.substitutionValues[name] = entry.value;
|
||||
b.write(s);
|
||||
}
|
||||
|
||||
b.write(')');
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
String compileForUpdate(Query query) {
|
||||
var data = toMap();
|
||||
if (data.isEmpty) return null;
|
||||
var b = StringBuffer('SET');
|
||||
int i = 0;
|
||||
|
||||
for (var entry in data.entries) {
|
||||
if (i++ > 0) b.write(',');
|
||||
b.write(' ');
|
||||
b.write(entry.key);
|
||||
b.write('=');
|
||||
|
||||
var name = query.reserveName(entry.key);
|
||||
var s = applyCast(entry.key, '@$name');
|
||||
query.substitutionValues[name] = entry.value;
|
||||
b.write(s);
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/// A [QueryValues] implementation that simply writes to a [Map].
|
||||
class MapQueryValues extends QueryValues {
|
||||
final Map<String, dynamic> values = {};
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toMap() => values;
|
||||
}
|
59
angel_orm/lib/src/query_where.dart
Normal file
59
angel_orm/lib/src/query_where.dart
Normal file
|
@ -0,0 +1,59 @@
|
|||
import 'builder.dart';
|
||||
|
||||
/// Builds a SQL `WHERE` clause.
|
||||
abstract class QueryWhere {
|
||||
final Set<QueryWhere> _and = Set();
|
||||
final Set<QueryWhere> _not = Set();
|
||||
final Set<QueryWhere> _or = Set();
|
||||
|
||||
Iterable<SqlExpressionBuilder> get expressionBuilders;
|
||||
|
||||
void and(QueryWhere other) {
|
||||
_and.add(other);
|
||||
}
|
||||
|
||||
void not(QueryWhere other) {
|
||||
_not.add(other);
|
||||
}
|
||||
|
||||
void or(QueryWhere other) {
|
||||
_or.add(other);
|
||||
}
|
||||
|
||||
String compile({String tableName}) {
|
||||
var b = StringBuffer();
|
||||
int i = 0;
|
||||
|
||||
for (var builder in expressionBuilders) {
|
||||
var key = builder.columnName;
|
||||
if (tableName != null) key = '$tableName.$key';
|
||||
if (builder.hasValue) {
|
||||
if (i++ > 0) b.write(' AND ');
|
||||
if (builder is DateTimeSqlExpressionBuilder ||
|
||||
(builder is JsonSqlExpressionBuilder && builder.hasRaw)) {
|
||||
if (tableName != null) b.write('$tableName.');
|
||||
b.write(builder.compile());
|
||||
} else {
|
||||
b.write('$key ${builder.compile()}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var other in _and) {
|
||||
var sql = other.compile();
|
||||
if (sql.isNotEmpty) b.write(' AND $sql');
|
||||
}
|
||||
|
||||
for (var other in _not) {
|
||||
var sql = other.compile();
|
||||
if (sql.isNotEmpty) b.write(' NOT $sql');
|
||||
}
|
||||
|
||||
for (var other in _or) {
|
||||
var sql = other.compile();
|
||||
if (sql.isNotEmpty) b.write(' OR $sql');
|
||||
}
|
||||
|
||||
return b.toString();
|
||||
}
|
||||
}
|
37
angel_orm/lib/src/union.dart
Normal file
37
angel_orm/lib/src/union.dart
Normal file
|
@ -0,0 +1,37 @@
|
|||
import 'query_base.dart';
|
||||
|
||||
/// Represents the `UNION` of two subqueries.
|
||||
class Union<T> extends QueryBase<T> {
|
||||
/// The subject(s) of this binary operation.
|
||||
final QueryBase<T> left, right;
|
||||
|
||||
/// Whether this is a `UNION ALL` operation.
|
||||
final bool all;
|
||||
|
||||
@override
|
||||
final String tableName;
|
||||
|
||||
Union(this.left, this.right, {this.all = false, String tableName})
|
||||
: this.tableName = tableName ?? left.tableName {
|
||||
substitutionValues
|
||||
..addAll(left.substitutionValues)
|
||||
..addAll(right.substitutionValues);
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> get fields => left.fields;
|
||||
|
||||
@override
|
||||
T deserialize(List row) => left.deserialize(row);
|
||||
|
||||
@override
|
||||
String compile(Set<String> trampoline,
|
||||
{bool includeTableName = false,
|
||||
String preamble,
|
||||
bool withFields = true}) {
|
||||
var selector = all == true ? 'UNION ALL' : 'UNION';
|
||||
var t1 = Set<String>.from(trampoline);
|
||||
var t2 = Set<String>.from(trampoline);
|
||||
return '(${left.compile(t1, includeTableName: includeTableName)}) $selector (${right.compile(t2, includeTableName: includeTableName)})';
|
||||
}
|
||||
}
|
57
angel_orm/lib/src/util.dart
Normal file
57
angel_orm/lib/src/util.dart
Normal file
|
@ -0,0 +1,57 @@
|
|||
import 'dart:async';
|
||||
import 'package:charcode/ascii.dart';
|
||||
import 'annotations.dart';
|
||||
import 'builder.dart';
|
||||
import 'query_base.dart';
|
||||
|
||||
bool isAscii(int ch) => ch >= $nul && ch <= $del;
|
||||
|
||||
/// The ORM prefers using substitution values, which allow for prepared queries,
|
||||
/// and prevent SQL injection attacks.
|
||||
@deprecated
|
||||
String toSql(Object obj, {bool withQuotes = true}) {
|
||||
if (obj is DateTime) {
|
||||
return withQuotes ? "'${dateYmdHms.format(obj)}'" : dateYmdHms.format(obj);
|
||||
} else if (obj is bool) {
|
||||
return obj ? 'TRUE' : 'FALSE';
|
||||
} else if (obj == null) {
|
||||
return 'NULL';
|
||||
} else if (obj is String) {
|
||||
var b = StringBuffer();
|
||||
var escaped = false;
|
||||
var it = obj.runes.iterator;
|
||||
|
||||
while (it.moveNext()) {
|
||||
if (it.current == $nul) {
|
||||
continue; // Skip null byte
|
||||
} else if (it.current == $single_quote) {
|
||||
escaped = true;
|
||||
b.write('\\x');
|
||||
b.write(it.current.toRadixString(16).padLeft(2, '0'));
|
||||
} else if (isAscii(it.current)) {
|
||||
b.writeCharCode(it.current);
|
||||
} else if (it.currentSize == 1) {
|
||||
escaped = true;
|
||||
b.write('\\u');
|
||||
b.write(it.current.toRadixString(16).padLeft(4, '0'));
|
||||
} else if (it.currentSize == 2) {
|
||||
escaped = true;
|
||||
b.write('\\U');
|
||||
b.write(it.current.toRadixString(16).padLeft(8, '0'));
|
||||
} else {
|
||||
throw UnsupportedError(
|
||||
'toSql() cannot encode a rune of size (${it.currentSize})');
|
||||
}
|
||||
}
|
||||
|
||||
if (!withQuotes) {
|
||||
return b.toString();
|
||||
} else if (escaped) {
|
||||
return "E'$b'";
|
||||
} else {
|
||||
return "'$b'";
|
||||
}
|
||||
} else {
|
||||
return obj.toString();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue