Always escape column names

This avoids the performance impact of using a regex in `escapeIfNeeded`.
Closes #2071.
This commit is contained in:
Simon Binder 2022-09-22 16:55:36 +02:00
parent be61af5111
commit 28c90b6ffb
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
33 changed files with 307 additions and 290 deletions

View File

@ -1,3 +1,8 @@
## 2.2.0-dev
- Always escape column names, avoiding the costs or using a regular expression
to check whether they need to be escaped.
## 2.1.0
- Improve stack traces when using `watchSingle()` with a stream emitting a non-

View File

@ -386,7 +386,7 @@ class $TodoItemsTable extends TodoItems
'category_id', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: true,
defaultConstraints: 'REFERENCES todo_categories (id)');
defaultConstraints: 'REFERENCES "todo_categories" ("id")');
final VerificationMeta _generatedTextMeta =
const VerificationMeta('generatedText');
@override

View File

@ -1,4 +1,6 @@
/// Provides utilities around sql keywords, like optional escaping etc.
@Deprecated('Drift is no longer using this library and will remove it in the '
'next breaking release')
library drift.sqlite_keywords;
import 'package:drift/drift.dart';

View File

@ -38,8 +38,12 @@ abstract class Column<T extends Object> extends Expression<T> {
/// needed.
String get name;
/// [name], but escaped if it's an sql keyword.
String get escapedName => escapeIfNeeded(name);
/// [name], but wrapped in double quotes to escape it as a a same identifier.
///
/// In the past, this getter only used to add double-quotes when that is
/// really needed (for instance because [name] is also a reserved keyword).
/// For performance reasons, we unconditionally escape names now.
String get escapedName => '"$name"';
}
/// A column that stores int values.

View File

@ -1,5 +1,4 @@
import 'package:drift/drift.dart';
import 'package:drift/sqlite_keywords.dart';
import 'package:meta/meta.dart';
import 'package:meta/meta_meta.dart';

View File

@ -77,4 +77,8 @@ class GenerationContext {
/// Shortcut to add a single space to the buffer because it's used very often.
void writeWhitespace() => buffer.write(' ');
/// Turns [columnName] into a safe SQL identifier by wrapping it in double
/// quotes.
String identifier(String columnName) => '"$columnName"';
}

View File

@ -227,15 +227,16 @@ class Migrator {
expr.writeInto(context);
first = false;
}
context.buffer.write(' FROM ${escapeIfNeeded(tableName)};');
context.buffer.write(' FROM ${context.identifier(tableName)};');
await _issueCustomQuery(context.sql, context.introducedVariables);
// Step 6: Drop the old table
await _issueCustomQuery('DROP TABLE ${escapeIfNeeded(tableName)}');
await _issueCustomQuery('DROP TABLE ${context.identifier(tableName)}');
// Step 7: Rename the new table to the old name
await _issueCustomQuery('ALTER TABLE ${escapeIfNeeded(temporaryName)} '
'RENAME TO ${escapeIfNeeded(tableName)}');
await _issueCustomQuery(
'ALTER TABLE ${context.identifier(temporaryName)} '
'RENAME TO ${context.identifier(tableName)}');
// Step 8: Re-create associated indexes, triggers and views
for (final stmt in createAffected) {
@ -253,7 +254,7 @@ class Migrator {
void _writeCreateTable(TableInfo table, GenerationContext context) {
context.buffer.write('CREATE TABLE IF NOT EXISTS '
'${escapeIfNeeded(table.aliasedName, context.dialect)} (');
'${context.identifier(table.aliasedName)} (');
var hasAutoIncrement = false;
for (var i = 0; i < table.$columns.length; i++) {
@ -281,7 +282,7 @@ class Migrator {
for (var i = 0; i < pkList.length; i++) {
final column = pkList[i];
context.buffer.write(escapeIfNeeded(column.$name));
context.buffer.write(column.escapedName);
if (i != pkList.length - 1) context.buffer.write(', ');
}
@ -295,7 +296,7 @@ class Migrator {
for (var i = 0; i < uqList.length; i++) {
final column = uqList[i];
context.buffer.write(escapeIfNeeded(column.name));
context.buffer.write(column.escapedName);
if (i != uqList.length - 1) context.buffer.write(', ');
}
@ -327,7 +328,7 @@ class Migrator {
void _writeCreateVirtual(VirtualTableInfo table, GenerationContext context) {
context.buffer
..write('CREATE VIRTUAL TABLE IF NOT EXISTS ')
..write(escapeIfNeeded(table.aliasedName))
..write(context.identifier(table.aliasedName))
..write(' USING ')
..write(table.moduleAndArgs)
..write(';');
@ -353,8 +354,8 @@ class Migrator {
final columnNames = view.$columns.map((e) => e.escapedName).join(', ');
context.generatingForView = view.entityName;
context.buffer.write(
'CREATE VIEW IF NOT EXISTS ${view.entityName} ($columnNames) AS ');
context.buffer.write('CREATE VIEW IF NOT EXISTS '
'${context.identifier(view.entityName)} ($columnNames) AS ');
view.query!.writeInto(context);
await _issueCustomQuery(context.sql, const []);
}
@ -362,7 +363,8 @@ class Migrator {
/// Drops a table, trigger or index.
Future<void> drop(DatabaseSchemaEntity entity) async {
final escapedName = escapeIfNeeded(entity.entityName);
final context = _createContext();
final escapedName = context.identifier(entity.entityName);
String kind;
@ -385,15 +387,17 @@ class Migrator {
/// Deletes the table with the given name. Note that this function does not
/// escape the [name] parameter.
Future<void> deleteTable(String name) async {
return _issueCustomQuery('DROP TABLE IF EXISTS $name;');
final context = _createContext();
return _issueCustomQuery(
'DROP TABLE IF EXISTS ${context.identifier(name)};');
}
/// Adds the given column to the specified table.
Future<void> addColumn(TableInfo table, GeneratedColumn column) async {
final context = _createContext();
context.buffer
.write('ALTER TABLE ${escapeIfNeeded(table.aliasedName)} ADD COLUMN ');
context.buffer.write(
'ALTER TABLE ${context.identifier(table.aliasedName)} ADD COLUMN ');
column.writeColumnDefinition(context);
context.buffer.write(';');
@ -421,8 +425,8 @@ class Migrator {
TableInfo table, String oldName, GeneratedColumn column) async {
final context = _createContext();
context.buffer
..write('ALTER TABLE ${escapeIfNeeded(table.aliasedName)} ')
..write('RENAME COLUMN ${escapeIfNeeded(oldName)} ')
..write('ALTER TABLE ${context.identifier(table.aliasedName)} ')
..write('RENAME COLUMN ${context.identifier(oldName)} ')
..write('TO ${column.escapedName};');
return _issueCustomQuery(context.sql);
@ -436,8 +440,8 @@ class Migrator {
/// databases.
Future<void> renameTable(TableInfo table, String oldName) async {
final context = _createContext();
context.buffer.write('ALTER TABLE ${escapeIfNeeded(oldName)} '
'RENAME TO ${escapeIfNeeded(table.actualTableName)};');
context.buffer.write('ALTER TABLE ${context.identifier(oldName)} '
'RENAME TO ${context.identifier(table.actualTableName)};');
return _issueCustomQuery(context.sql);
}

View File

@ -5,7 +5,6 @@ import 'dart:async';
import 'package:collection/collection.dart';
import 'package:drift/drift.dart';
import 'package:drift/sqlite_keywords.dart';
import 'package:drift/src/runtime/executor/stream_queries.dart';
import 'package:drift/src/utils/single_transformer.dart';
import 'package:meta/meta.dart';

View File

@ -175,7 +175,7 @@ class GeneratedColumn<T extends Object> extends Column<T> {
} else {
if (context.hasMultipleTables) {
context.buffer
..write(tableName)
..write(context.identifier(tableName))
..write('.');
}
context.buffer.write(ignoreEscape ? $name : escapedName);

View File

@ -140,9 +140,9 @@ extension NameWithAlias on ResultSetImplementation<dynamic, dynamic> {
/// for a table called users that has been aliased as "u".
String get tableWithAlias {
if (aliasedName == entityName) {
return entityName;
return '"$entityName"';
} else {
return '$entityName $aliasedName';
return '"$entityName" "$aliasedName"';
}
}
}

View File

@ -156,7 +156,7 @@ class InsertStatement<T extends Table, D> {
..write(_insertKeywords[
ctx.dialect == SqlDialect.postgres ? InsertMode.insert : mode])
..write(' INTO ')
..write(table.aliasedName)
..write(ctx.identifier(table.aliasedName))
..write(' ');
if (map.isEmpty) {
@ -210,7 +210,7 @@ class InsertStatement<T extends Table, D> {
first = true;
for (final update in updateSet.entries) {
final column = escapeIfNeeded(update.key);
final column = ctx.identifier(update.key);
if (!first) ctx.buffer.write(', ');
ctx.buffer.write('$column = ');
@ -260,7 +260,7 @@ class InsertStatement<T extends Table, D> {
/// Writes column names and values from the [map].
@internal
void writeInsertable(GenerationContext ctx, Map<String, Expression> map) {
final columns = map.keys.map(escapeIfNeeded);
final columns = map.keys.map(ctx.identifier);
ctx.buffer
..write('(')

View File

@ -24,7 +24,7 @@ class UpdateStatement<T extends Table, D> extends Query<T, D>
}
ctx.buffer
..write(escapeIfNeeded(columnName))
..write(ctx.identifier(columnName))
..write(' = ');
variable.writeInto(ctx);

View File

@ -52,12 +52,12 @@ void main() {
transaction.runBatched(
BatchedStatements(
[
'INSERT INTO todos (content) VALUES (?)',
'UPDATE users SET name = ?;',
'UPDATE users SET name = ? WHERE name = ?;',
'UPDATE categories SET "desc" = ?, priority = 0 WHERE id = ?;',
'DELETE FROM categories WHERE 1;',
'DELETE FROM todos WHERE id = ?;',
'INSERT INTO "todos" ("content") VALUES (?)',
'UPDATE "users" SET "name" = ?;',
'UPDATE "users" SET "name" = ? WHERE "name" = ?;',
'UPDATE "categories" SET "desc" = ?, "priority" = 0 WHERE "id" = ?;',
'DELETE FROM "categories" WHERE 1;',
'DELETE FROM "todos" WHERE "id" = ?;',
'some custom statement',
],
[
@ -90,8 +90,8 @@ void main() {
verify(executor.transactions.runBatched(BatchedStatements(
[
('INSERT INTO categories ("desc") VALUES (?) '
'ON CONFLICT(id) DO UPDATE SET id = ?')
('INSERT INTO "categories" ("desc") VALUES (?) '
'ON CONFLICT("id") DO UPDATE SET "id" = ?')
],
[
ArgumentsForBatchedStatement(0, ['description', 42])
@ -111,8 +111,8 @@ void main() {
verify(executor.transactions.runBatched(BatchedStatements(
[
('INSERT INTO categories ("desc") VALUES (?) '
'ON CONFLICT(id) DO UPDATE SET "desc" = ?')
('INSERT INTO "categories" ("desc") VALUES (?) '
'ON CONFLICT("id") DO UPDATE SET "desc" = ?')
],
[
ArgumentsForBatchedStatement(0, ['first', 'first']),
@ -145,9 +145,9 @@ void main() {
verify(executor.transactions.runBatched(BatchedStatements(
[
('INSERT INTO categories ("desc") VALUES (?) ON CONFLICT(id) '
'DO UPDATE SET "desc" = categories."desc", '
'priority = excluded.priority WHERE categories.id >= excluded.id')
('INSERT INTO "categories" ("desc") VALUES (?) ON CONFLICT("id") '
'DO UPDATE SET "desc" = "categories"."desc", '
'"priority" = "excluded"."priority" WHERE "categories"."id" >= "excluded"."id"')
],
[
ArgumentsForBatchedStatement(0, ['first']),
@ -179,7 +179,7 @@ void main() {
});
verify(executor.transactions.runBatched(BatchedStatements(
['INSERT INTO categories ("desc") VALUES (?)'],
['INSERT INTO "categories" ("desc") VALUES (?)'],
[
ArgumentsForBatchedStatement(0, ['first']),
ArgumentsForBatchedStatement(0, ['second']),

View File

@ -13,7 +13,8 @@ void main() {
expect(
existsExpression,
generates('EXISTS (SELECT * FROM users WHERE users.is_awesome = ?)', [1]),
generates(
'EXISTS (SELECT * FROM "users" WHERE "users"."is_awesome" = ?)', [1]),
);
});
@ -23,7 +24,7 @@ void main() {
expect(
notExistsExpression,
generates('NOT EXISTS (SELECT * FROM users)'),
generates('NOT EXISTS (SELECT * FROM "users")'),
);
});
}

View File

@ -27,8 +27,7 @@ void main() {
});
test('generates parentheses for OR in AND', () {
final c =
GeneratedColumn<String>('c', 't', false, type: DriftSqlType.string);
final c = CustomExpression<String>('c', precedence: Precedence.primary);
final expr =
(c.equals('A') | c.equals('B')) & (c.equals('C') | c.equals(''));
expect(
@ -54,7 +53,7 @@ void main() {
expect(
subqueryExpression<String>(
db.selectOnly(db.users)..addColumns([db.users.name])),
generates('(SELECT users.name AS "users.name" FROM users)'));
generates('(SELECT "users"."name" AS "users.name" FROM "users")'));
});
test('does not allow subqueries with more than one column', () {
@ -77,8 +76,8 @@ void main() {
innerJoin(db.categories, db.categories.id.equalsExp(db.users.id),
useColumns: false)
])),
generates('(SELECT users.name AS "users.name" FROM users '
'INNER JOIN categories ON categories.id = users.id)'),
generates('(SELECT "users"."name" AS "users.name" FROM "users" '
'INNER JOIN "categories" ON "categories"."id" = "users"."id")'),
);
});
@ -94,7 +93,7 @@ void main() {
});
test('generates a rowid expression', () {
expect(TodoDb().categories.rowId, generates('_rowid_'));
expect(TodoDb().categories.rowId, generates('"_rowid_"'));
});
test('generates an aliased rowid expression when needed', () async {
@ -108,7 +107,7 @@ void main() {
await query.get();
verify(executor
.runSelect(argThat(contains('ON categories._rowid_ = ?')), [3]));
.runSelect(argThat(contains('ON "categories"."_rowid_" = ?')), [3]));
});
});

View File

@ -28,8 +28,10 @@ void main() {
final isInExpression = innerExpression
.isInQuery(db.selectOnly(db.users)..addColumns([db.users.name]));
expect(isInExpression,
generates('name IN (SELECT users.name AS "users.name" FROM users)'));
expect(
isInExpression,
generates(
'name IN (SELECT "users"."name" AS "users.name" FROM "users")'));
final ctx = stubContext();
isInExpression.writeInto(ctx);
@ -43,7 +45,7 @@ void main() {
expect(
isInExpression,
generates(
'name NOT IN (SELECT users.name AS "users.name" FROM users)'));
'name NOT IN (SELECT "users"."name" AS "users.name" FROM "users")'));
});
});
}

View File

@ -24,7 +24,7 @@ void main() {
test('without any constraints', () async {
await db.delete(db.users).go();
verify(executor.runDelete('DELETE FROM users;', argThat(isEmpty)));
verify(executor.runDelete('DELETE FROM "users";', argThat(isEmpty)));
});
test('for complex components', () async {
@ -33,7 +33,8 @@ void main() {
.go();
verify(executor.runDelete(
'DELETE FROM users WHERE NOT is_awesome OR id < ?;', const [100]));
'DELETE FROM "users" WHERE NOT "is_awesome" OR "id" < ?;',
const [100]));
});
test('to delete an entity via a dataclasss', () async {
@ -42,7 +43,7 @@ void main() {
.delete(const SharedTodo(todo: 3, user: 2));
verify(executor.runDelete(
'DELETE FROM shared_todos WHERE todo = ? AND user = ?;',
'DELETE FROM "shared_todos" WHERE "todo" = ? AND "user" = ?;',
const [3, 2],
));
});
@ -59,8 +60,8 @@ void main() {
.delete(db.todosTable)
.deleteReturning(const TodosTableCompanion(id: Value(10)));
verify(executor
.runSelect('DELETE FROM todos WHERE id = ? RETURNING *;', [10]));
verify(executor.runSelect(
'DELETE FROM "todos" WHERE "id" = ? RETURNING *;', [10]));
verify(streamQueries.handleTableUpdates(
{TableUpdate.onTable(db.todosTable, kind: UpdateKind.delete)}));
expect(returnedValue, const TodoEntry(id: 10, content: 'Content'));
@ -70,7 +71,7 @@ void main() {
final rows = await db.delete(db.users).goAndReturn();
expect(rows, isEmpty);
verify(executor.runSelect('DELETE FROM users RETURNING *;', []));
verify(executor.runSelect('DELETE FROM "users" RETURNING *;', []));
verifyNever(streamQueries.handleTableUpdates(any));
});
});
@ -107,19 +108,21 @@ void main() {
test('delete()', () async {
await db.users.delete().go();
verify(executor.runDelete('DELETE FROM users;', const []));
verify(executor.runDelete('DELETE FROM "users";', const []));
});
test('deleteOne()', () async {
await db.users.deleteOne(const UsersCompanion(id: Value(3)));
verify(executor.runDelete('DELETE FROM users WHERE id = ?;', const [3]));
verify(
executor.runDelete('DELETE FROM "users" WHERE "id" = ?;', const [3]));
});
test('deleteWhere', () async {
await db.users.deleteWhere((tbl) => tbl.id.isSmallerThanValue(3));
verify(executor.runDelete('DELETE FROM users WHERE id < ?;', const [3]));
verify(
executor.runDelete('DELETE FROM "users" WHERE "id" < ?;', const [3]));
});
});
}

View File

@ -24,7 +24,7 @@ void main() {
title: Value.absent(),
));
verify(executor.runInsert('INSERT INTO todos (content) VALUES (?)',
verify(executor.runInsert('INSERT INTO "todos" ("content") VALUES (?)',
['Implement insert statements']));
});
@ -35,8 +35,8 @@ void main() {
.toInsertable());
verify(executor.runInsert(
'INSERT INTO table_without_p_k '
'(not_really_an_id, some_float, web_safe_int, custom) '
'INSERT INTO "table_without_p_k" '
'("not_really_an_id", "some_float", "web_safe_int", "custom") '
'VALUES (?, ?, ?, ?)',
[42, 3.1415, isNull, anything]));
});
@ -47,8 +47,8 @@ void main() {
.toInsertable());
verify(executor.runInsert(
'INSERT INTO table_without_p_k '
'(not_really_an_id, some_float, web_safe_int, custom) '
'INSERT INTO "table_without_p_k" '
'("not_really_an_id", "some_float", "web_safe_int", "custom") '
'VALUES (?, ?, ?, ?)',
[42, 0.0, BigInt.one, anything]));
});
@ -62,14 +62,15 @@ void main() {
mode: InsertMode.insertOrReplace);
verify(executor.runInsert(
'INSERT OR REPLACE INTO todos (id, content) VALUES (?, ?)',
'INSERT OR REPLACE INTO "todos" ("id", "content") VALUES (?, ?)',
[113, 'Done']));
});
test('generates DEFAULT VALUES statement when otherwise empty', () async {
await db.into(db.pureDefaults).insert(const PureDefaultsCompanion());
verify(executor.runInsert('INSERT INTO pure_defaults DEFAULT VALUES', []));
verify(
executor.runInsert('INSERT INTO "pure_defaults" DEFAULT VALUES', []));
});
test('notifies stream queries on inserts', () async {
@ -156,7 +157,7 @@ void main() {
TodosTableCompanion.insert(content: 'content', title: Value(null)));
verify(executor.runInsert(
'INSERT INTO todos (title, content) VALUES (?, ?)',
'INSERT INTO "todos" ("title", "content") VALUES (?, ?)',
[null, 'content']));
});
});
@ -181,7 +182,7 @@ void main() {
r'[0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12}');
verify(executor.runInsert(
'INSERT INTO table_without_p_k (not_really_an_id, some_float, custom) '
'INSERT INTO "table_without_p_k" ("not_really_an_id", "some_float", "custom") '
'VALUES (?, ?, ?)',
[3, 3.14, matches(uuidRegex)],
));
@ -191,8 +192,8 @@ void main() {
await db.into(db.pureDefaults).insert(
PureDefaultsCompanion.insert(txt: Value(MyCustomObject('foo'))));
verify(executor
.runInsert('INSERT INTO pure_defaults ("insert") VALUES (?)', ['foo']));
verify(executor.runInsert(
'INSERT INTO "pure_defaults" ("insert") VALUES (?)', ['foo']));
});
test('can insert custom companions', () async {
@ -204,7 +205,7 @@ void main() {
verify(
executor.runInsert(
'INSERT INTO users (name, is_awesome, profile_picture, creation_time) '
'INSERT INTO "users" ("name", "is_awesome", "profile_picture", "creation_time") '
'VALUES (?, 1, _custom_, '
"CAST(strftime('%s', CURRENT_TIMESTAMP) AS INTEGER))",
['User name'],
@ -222,8 +223,8 @@ void main() {
);
verify(executor.runInsert(
'INSERT INTO todos (content) VALUES (?) '
'ON CONFLICT(id) DO UPDATE SET content = ? || content',
'INSERT INTO "todos" ("content") VALUES (?) '
'ON CONFLICT("id") DO UPDATE SET "content" = ? || "content"',
argThat(equals(['my content', 'important: '])),
));
});
@ -238,9 +239,9 @@ void main() {
);
verify(executor.runInsert(
'INSERT INTO todos (content) VALUES (?) '
'ON CONFLICT(id) DO UPDATE SET content = ? || content '
'WHERE category = ?',
'INSERT INTO "todos" ("content") VALUES (?) '
'ON CONFLICT("id") DO UPDATE SET "content" = ? || "content" '
'WHERE "category" = ?',
argThat(equals(['my content', 'important: ', 1])),
));
});
@ -268,9 +269,9 @@ void main() {
]));
verify(executor.runInsert(
'INSERT INTO todos (content) VALUES (?) '
'ON CONFLICT(id) DO UPDATE SET content = ? || content '
'ON CONFLICT(content) DO UPDATE SET content = ? || content',
'INSERT INTO "todos" ("content") VALUES (?) '
'ON CONFLICT("id") DO UPDATE SET "content" = ? || "content" '
'ON CONFLICT("content") DO UPDATE SET "content" = ? || "content"',
argThat(equals(['my content', 'important: ', 'second: '])),
));
},
@ -296,11 +297,11 @@ void main() {
]));
verify(executor.runInsert(
'INSERT INTO todos (content) VALUES (?) '
'ON CONFLICT(id) DO UPDATE SET content = ? || content '
'WHERE category = ? '
'ON CONFLICT(content) DO UPDATE SET content = ? || content '
'WHERE category = ?',
'INSERT INTO "todos" ("content") VALUES (?) '
'ON CONFLICT("id") DO UPDATE SET "content" = ? || "content" '
'WHERE "category" = ? '
'ON CONFLICT("content") DO UPDATE SET "content" = ? || "content" '
'WHERE "category" = ?',
argThat(equals(['my content', 'important: ', 1, 'second: ', 1])),
));
},
@ -316,8 +317,8 @@ void main() {
);
verify(executor.runInsert(
'INSERT INTO todos (content) VALUES (?) '
'ON CONFLICT(content) DO UPDATE SET content = ?',
'INSERT INTO "todos" ("content") VALUES (?) '
'ON CONFLICT("content") DO UPDATE SET "content" = ?',
argThat(equals(['my content', 'changed'])),
));
});
@ -332,9 +333,9 @@ void main() {
);
verify(executor.runInsert(
'INSERT INTO todos (content) VALUES (?) '
'ON CONFLICT(content) DO UPDATE SET content = ? '
'WHERE content = title',
'INSERT INTO "todos" ("content") VALUES (?) '
'ON CONFLICT("content") DO UPDATE SET "content" = ? '
'WHERE "content" = "title"',
argThat(equals(['my content', 'changed'])),
));
});
@ -346,8 +347,8 @@ void main() {
TodosTableCompanion.insert(content: 'content', id: const Value(3)));
verify(executor.runInsert(
'INSERT INTO todos (id, content) VALUES (?, ?) '
'ON CONFLICT(id) DO UPDATE SET id = ?, content = ?',
'INSERT INTO "todos" ("id", "content") VALUES (?, ?) '
'ON CONFLICT("id") DO UPDATE SET "id" = ?, "content" = ?',
[3, 'content', 3, 'content'],
));
expect(id, 3);
@ -364,9 +365,9 @@ void main() {
);
verify(executor.runInsert(
'INSERT INTO todos (content) VALUES (?) '
'ON CONFLICT(id) DO UPDATE '
'SET content = todos.content || excluded.content',
'INSERT INTO "todos" ("content") VALUES (?) '
'ON CONFLICT("id") DO UPDATE '
'SET "content" = "todos"."content" || "excluded"."content"',
['content'],
));
});
@ -382,10 +383,10 @@ void main() {
);
verify(executor.runInsert(
'INSERT INTO todos (content) VALUES (?) '
'ON CONFLICT(id) DO UPDATE '
'SET content = todos.content || excluded.content '
'WHERE todos.title = excluded.title',
'INSERT INTO "todos" ("content") VALUES (?) '
'ON CONFLICT("id") DO UPDATE '
'SET "content" = "todos"."content" || "excluded"."content" '
'WHERE "todos"."title" = "excluded"."title"',
['content'],
));
});
@ -397,7 +398,7 @@ void main() {
));
verify(executor.runInsert(
'INSERT INTO categories ("desc", priority) VALUES (?, ?)',
'INSERT INTO "categories" ("desc", "priority") VALUES (?, ?)',
['description', 1],
));
});
@ -420,7 +421,7 @@ void main() {
));
verify(executor.runSelect(
'INSERT INTO categories ("desc", priority) VALUES (?, ?) RETURNING *',
'INSERT INTO "categories" ("desc", "priority") VALUES (?, ?) RETURNING *',
['description', 1],
));
});
@ -432,7 +433,7 @@ void main() {
.insert(CategoriesCompanion.insert(description: 'description'));
verify(executor.runInsert(
'INSERT INTO categories ("desc") VALUES (?)', ['description']));
'INSERT INTO "categories" ("desc") VALUES (?)', ['description']));
});
test('insertOne', () async {
@ -441,7 +442,7 @@ void main() {
mode: InsertMode.insertOrReplace);
verify(executor.runInsert(
'INSERT OR REPLACE INTO categories ("desc") VALUES (?)',
'INSERT OR REPLACE INTO "categories" ("desc") VALUES (?)',
['description']));
});
@ -470,7 +471,7 @@ void main() {
);
verify(executor.runSelect(
'INSERT INTO categories ("desc") VALUES (?) RETURNING *',
'INSERT INTO "categories" ("desc") VALUES (?) RETURNING *',
['description'],
));
});

View File

@ -23,12 +23,12 @@ void main() {
]).get();
verify(executor.runSelect(
'SELECT t.id AS "t.id", t.title AS "t.title", '
't.content AS "t.content", t.target_date AS "t.target_date", '
't.category AS "t.category", c.id AS "c.id", c."desc" AS "c.desc", '
'c.priority AS "c.priority", '
'c.description_in_upper_case AS "c.description_in_upper_case" '
'FROM todos t LEFT OUTER JOIN categories c ON c.id = t.category;',
'SELECT "t"."id" AS "t.id", "t"."title" AS "t.title", '
'"t"."content" AS "t.content", "t"."target_date" AS "t.target_date", '
'"t"."category" AS "t.category", "c"."id" AS "c.id", "c"."desc" AS "c.desc", '
'"c"."priority" AS "c.priority", '
'"c"."description_in_upper_case" AS "c.description_in_upper_case" '
'FROM "todos" "t" LEFT OUTER JOIN "categories" "c" ON "c"."id" = "t"."category";',
argThat(isEmpty)));
});
@ -126,7 +126,7 @@ void main() {
[innerJoin(categories, categories.id.equalsExp(todos.category))]).get();
verify(executor.runSelect(
argThat(contains('WHERE t.id < ? ORDER BY t.title ASC')), [3]));
argThat(contains('WHERE "t"."id" < ? ORDER BY "t"."title" ASC')), [3]));
});
test('limit clause is kept', () async {
@ -189,8 +189,8 @@ void main() {
await query.get();
verify(executor
.runSelect(argThat(contains('WHERE t.id < ? AND c.id >= ?')), [5, 10]));
verify(executor.runSelect(
argThat(contains('WHERE "t"."id" < ? AND "c"."id" >= ?')), [5, 10]));
});
test('supports custom columns and results', () async {
@ -214,10 +214,10 @@ void main() {
final result = await query.getSingle();
verify(executor.runSelect(
'SELECT c.id AS "c.id", c."desc" AS "c.desc", '
'c.priority AS "c.priority", c.description_in_upper_case AS '
'"c.description_in_upper_case", LENGTH(c."desc") AS "c4" '
'FROM categories c;',
'SELECT "c"."id" AS "c.id", "c"."desc" AS "c.desc", '
'"c"."priority" AS "c.priority", "c"."description_in_upper_case" AS '
'"c.description_in_upper_case", LENGTH("c"."desc") AS "c4" '
'FROM "categories" "c";',
[],
));
@ -263,11 +263,11 @@ void main() {
final result = await query.getSingle();
verify(executor.runSelect(
'SELECT c.id AS "c.id", c."desc" AS "c.desc", c.priority AS "c.priority"'
', c.description_in_upper_case AS "c.description_in_upper_case", '
'LENGTH(c."desc") AS "c4" '
'FROM categories c '
'INNER JOIN todos t ON c.id = t.category;',
'SELECT "c"."id" AS "c.id", "c"."desc" AS "c.desc", "c"."priority" AS "c.priority"'
', "c"."description_in_upper_case" AS "c.description_in_upper_case", '
'LENGTH("c"."desc") AS "c4" '
'FROM "categories" "c" '
'INNER JOIN "todos" "t" ON "c"."id" = "t"."category";',
[],
));
@ -319,12 +319,12 @@ void main() {
final result = await query.getSingle();
verify(executor.runSelect(
'SELECT c.id AS "c.id", c."desc" AS "c.desc", '
'c.priority AS "c.priority", '
'c.description_in_upper_case AS "c.description_in_upper_case", '
'COUNT(t.id) AS "c4" '
'FROM categories c INNER JOIN todos t ON t.category = c.id '
'GROUP BY c.id HAVING COUNT(t.id) >= ?;',
'SELECT "c"."id" AS "c.id", "c"."desc" AS "c.desc", '
'"c"."priority" AS "c.priority", '
'"c"."description_in_upper_case" AS "c.description_in_upper_case", '
'COUNT("t"."id") AS "c4" '
'FROM "categories" "c" INNER JOIN "todos" "t" ON "t"."category" = "c"."id" '
'GROUP BY "c"."id" HAVING COUNT("t"."id") >= ?;',
[10]));
expect(result.readTableOrNull(todos), isNull);
@ -356,8 +356,8 @@ void main() {
final row = await query.getSingle();
verify(executor.runSelect(
'SELECT AVG(LENGTH(todos.content)) AS "c0", '
'MAX(LENGTH(todos.content)) AS "c1" FROM todos;',
'SELECT AVG(LENGTH("todos"."content")) AS "c0", '
'MAX(LENGTH("todos"."content")) AS "c1" FROM "todos";',
[]));
expect(row.read(avgLength), 3.0);
@ -392,9 +392,9 @@ void main() {
final result = await query.getSingle();
verify(executor.runSelect(
'SELECT categories.id AS "categories.id", COUNT(todos.id) AS "c1" '
'FROM categories INNER JOIN todos ON todos.category = categories.id '
'GROUP BY categories.id;',
'SELECT "categories"."id" AS "categories.id", COUNT("todos"."id") AS "c1" '
'FROM "categories" INNER JOIN "todos" ON "todos"."category" = "categories"."id" '
'GROUP BY "categories"."id";',
[]));
expect(result.read(categories.id), equals(2));
@ -428,9 +428,9 @@ void main() {
final result = await query.getSingle();
verify(executor.runSelect(
'SELECT categories.id AS "categories.id", COUNT(todos.id) AS "c1" '
'FROM categories INNER JOIN todos ON todos.category = categories.id '
'GROUP BY categories.id;',
'SELECT "categories"."id" AS "categories.id", COUNT("todos"."id") AS "c1" '
'FROM "categories" INNER JOIN "todos" ON "todos"."category" = "categories"."id" '
'GROUP BY "categories"."id";',
[]));
expect(result.read(categories.id), equals(2));

View File

@ -19,7 +19,7 @@ void main() {
query.orderBy([(tbl) => OrderingTerm(expression: tbl.name)]);
await query.get();
verify(executor.runSelect(
'SELECT * FROM users ORDER BY name ASC;',
'SELECT * FROM "users" ORDER BY "name" ASC;',
argThat(isEmpty),
));
});
@ -34,7 +34,7 @@ void main() {
]);
await query.get();
verify(executor.runSelect(
'SELECT * FROM users ORDER BY name ASC NULLS LAST;',
'SELECT * FROM "users" ORDER BY "name" ASC NULLS LAST;',
argThat(isEmpty),
));
});
@ -49,7 +49,7 @@ void main() {
]);
await query.get();
verify(executor.runSelect(
'SELECT * FROM users ORDER BY name ASC NULLS FIRST;',
'SELECT * FROM "users" ORDER BY "name" ASC NULLS FIRST;',
argThat(isEmpty),
));
});
@ -71,7 +71,7 @@ void main() {
]);
await query.get();
verify(executor.runSelect(
'SELECT * FROM users ORDER BY name ASC NULLS FIRST, creation_time ASC, profile_picture ASC NULLS LAST;',
'SELECT * FROM "users" ORDER BY "name" ASC NULLS FIRST, "creation_time" ASC, "profile_picture" ASC NULLS LAST;',
argThat(isEmpty),
));
});
@ -79,10 +79,10 @@ void main() {
test('works with helper factories', () {
final table = db.users;
expect(OrderingTerm.asc(table.id), generates('id ASC'));
expect(OrderingTerm.asc(table.id), generates('"id" ASC'));
expect(OrderingTerm.asc(table.id, nulls: NullsOrder.last),
generates('id ASC NULLS LAST'));
generates('"id" ASC NULLS LAST'));
expect(OrderingTerm.desc(table.id, nulls: NullsOrder.first),
generates('id DESC NULLS FIRST'));
generates('"id" DESC NULLS FIRST'));
});
}

View File

@ -20,40 +20,40 @@ void main() {
// should create todos, categories, users and shared_todos table
verify(mockExecutor.runCustom(
'CREATE TABLE IF NOT EXISTS todos '
'(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, title TEXT NULL, '
'content TEXT NOT NULL, target_date INTEGER NULL UNIQUE, '
'category INTEGER NULL REFERENCES categories (id), '
'UNIQUE (title, category), UNIQUE (title, target_date));',
'CREATE TABLE IF NOT EXISTS "todos" '
'("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "title" TEXT NULL, '
'"content" TEXT NOT NULL, "target_date" INTEGER NULL UNIQUE, '
'"category" INTEGER NULL REFERENCES "categories" ("id"), '
'UNIQUE ("title", "category"), UNIQUE ("title", "target_date"));',
[]));
verify(mockExecutor.runCustom(
'CREATE TABLE IF NOT EXISTS categories '
'(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, '
'CREATE TABLE IF NOT EXISTS "categories" '
'("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, '
'"desc" TEXT NOT NULL UNIQUE, '
'priority INTEGER NOT NULL DEFAULT 0, '
'description_in_upper_case TEXT NOT NULL GENERATED ALWAYS AS '
'"priority" INTEGER NOT NULL DEFAULT 0, '
'"description_in_upper_case" TEXT NOT NULL GENERATED ALWAYS AS '
'(UPPER("desc")) VIRTUAL'
');',
[]));
verify(mockExecutor.runCustom(
'CREATE TABLE IF NOT EXISTS users ('
'id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, '
'name TEXT NOT NULL UNIQUE, '
'is_awesome INTEGER NOT NULL DEFAULT 1 CHECK (is_awesome IN (0, 1)), '
'profile_picture BLOB NOT NULL, '
'creation_time INTEGER NOT NULL '
'CREATE TABLE IF NOT EXISTS "users" ('
'"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, '
'"name" TEXT NOT NULL UNIQUE, '
'"is_awesome" INTEGER NOT NULL DEFAULT 1 CHECK ("is_awesome" IN (0, 1)), '
'"profile_picture" BLOB NOT NULL, '
'"creation_time" INTEGER NOT NULL '
"DEFAULT (CAST(strftime('%s', CURRENT_TIMESTAMP) AS INTEGER)) "
'CHECK(creation_time > -631152000)'
'CHECK("creation_time" > -631152000)'
');',
[]));
verify(mockExecutor.runCustom(
'CREATE TABLE IF NOT EXISTS shared_todos ('
'todo INTEGER NOT NULL, '
'user INTEGER NOT NULL, '
'PRIMARY KEY (todo, user), '
'CREATE TABLE IF NOT EXISTS "shared_todos" ('
'"todo" INTEGER NOT NULL, '
'"user" INTEGER NOT NULL, '
'PRIMARY KEY ("todo", "user"), '
'FOREIGN KEY (todo) REFERENCES todos(id), '
'FOREIGN KEY (user) REFERENCES users(id)'
');',
@ -61,33 +61,33 @@ void main() {
verify(mockExecutor.runCustom(
'CREATE TABLE IF NOT EXISTS '
'table_without_p_k ('
'not_really_an_id INTEGER NOT NULL, '
'some_float REAL NOT NULL, '
'web_safe_int INTEGER NULL, '
'custom TEXT NOT NULL'
'"table_without_p_k" ('
'"not_really_an_id" INTEGER NOT NULL, '
'"some_float" REAL NOT NULL, '
'"web_safe_int" INTEGER NULL, '
'"custom" TEXT NOT NULL'
');',
[]));
verify(mockExecutor.runCustom(
'CREATE VIEW IF NOT EXISTS category_todo_count_view '
'(description, item_count) AS SELECT '
't1."desc" || \'!\' AS "description", '
'COUNT(t0.id) AS "item_count" '
'FROM categories t1 '
'INNER JOIN todos t0 '
'ON t0.category = t1.id '
'GROUP BY t1.id',
'CREATE VIEW IF NOT EXISTS "category_todo_count_view" '
'("description", "item_count") AS SELECT '
'"t1"."desc" || \'!\' AS "description", '
'COUNT("t0"."id") AS "item_count" '
'FROM "categories" "t1" '
'INNER JOIN "todos" "t0" '
'ON "t0"."category" = "t1"."id" '
'GROUP BY "t1"."id"',
[]));
verify(mockExecutor.runCustom(
'CREATE VIEW IF NOT EXISTS todo_with_category_view '
'(title, "desc") AS SELECT '
't0.title AS "title", '
't1."desc" AS "desc" '
'FROM todos t0 '
'INNER JOIN categories t1 '
'ON t1.id = t0.category',
'CREATE VIEW IF NOT EXISTS "todo_with_category_view" '
'("title", "desc") AS SELECT '
'"t0"."title" AS "title", '
'"t1"."desc" AS "desc" '
'FROM "todos" "t0" '
'INNER JOIN "categories" "t1" '
'ON "t1"."id" = "t0"."category"',
[]));
});
@ -95,14 +95,14 @@ void main() {
await db.createMigrator().createTable(db.users);
verify(mockExecutor.runCustom(
'CREATE TABLE IF NOT EXISTS users '
'(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, '
'name TEXT NOT NULL UNIQUE, '
'is_awesome INTEGER NOT NULL DEFAULT 1 CHECK (is_awesome IN (0, 1)), '
'profile_picture BLOB NOT NULL, '
'creation_time INTEGER NOT NULL '
'CREATE TABLE IF NOT EXISTS "users" '
'("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, '
'"name" TEXT NOT NULL UNIQUE, '
'"is_awesome" INTEGER NOT NULL DEFAULT 1 CHECK ("is_awesome" IN (0, 1)), '
'"profile_picture" BLOB NOT NULL, '
'"creation_time" INTEGER NOT NULL '
"DEFAULT (CAST(strftime('%s', CURRENT_TIMESTAMP) AS INTEGER)) "
'CHECK(creation_time > -631152000)'
'CHECK("creation_time" > -631152000)'
');',
[]));
});
@ -111,21 +111,21 @@ void main() {
await db.createMigrator().create(db.categoryTodoCountView);
verify(mockExecutor.runCustom(
'CREATE VIEW IF NOT EXISTS category_todo_count_view '
'(description, item_count) AS SELECT '
't1."desc" || \'!\' AS "description", '
'COUNT(t0.id) AS "item_count" '
'FROM categories t1 '
'INNER JOIN todos t0 '
'ON t0.category = t1.id '
'GROUP BY t1.id',
'CREATE VIEW IF NOT EXISTS "category_todo_count_view" '
'("description", "item_count") AS SELECT '
'"t1"."desc" || \'!\' AS "description", '
'COUNT("t0"."id") AS "item_count" '
'FROM "categories" "t1" '
'INNER JOIN "todos" "t0" '
'ON "t0"."category" = "t1"."id" '
'GROUP BY "t1"."id"',
[]));
});
test('drops tables', () async {
await db.createMigrator().deleteTable('users');
verify(mockExecutor.runCustom('DROP TABLE IF EXISTS users;'));
verify(mockExecutor.runCustom('DROP TABLE IF EXISTS "users";'));
});
test('drops indices', () async {
@ -137,15 +137,15 @@ void main() {
test('drops triggers', () async {
await db.createMigrator().drop(Trigger('foo', 'my_trigger'));
verify(mockExecutor.runCustom('DROP TRIGGER IF EXISTS my_trigger;'));
verify(mockExecutor.runCustom('DROP TRIGGER IF EXISTS "my_trigger";'));
});
test('adds columns', () async {
await db.createMigrator().addColumn(db.users, db.users.isAwesome);
verify(mockExecutor.runCustom('ALTER TABLE users ADD COLUMN '
'is_awesome INTEGER NOT NULL DEFAULT 1 '
'CHECK (is_awesome IN (0, 1));'));
verify(mockExecutor.runCustom('ALTER TABLE "users" ADD COLUMN '
'"is_awesome" INTEGER NOT NULL DEFAULT 1 '
'CHECK ("is_awesome" IN (0, 1));'));
});
test('renames columns', () async {
@ -154,7 +154,7 @@ void main() {
.renameColumn(db.users, 'my name', db.users.name);
verify(mockExecutor
.runCustom('ALTER TABLE users RENAME COLUMN "my name" TO name;'));
.runCustom('ALTER TABLE "users" RENAME COLUMN "my name" TO "name";'));
});
});
@ -207,7 +207,7 @@ void main() {
// This should not attempt to generate a parameter (`?`)
// https://github.com/simolus3/drift/discussions/1936
verify(executor.runCustom(argThat(contains('CHECK(foo < 3)')), []));
verify(executor.runCustom(argThat(contains('CHECK("foo" < 3)')), []));
});
}

View File

@ -34,26 +34,26 @@ void main() {
test('for simple statements', () async {
await db.select(db.users, distinct: true).get();
verify(executor.runSelect(
'SELECT DISTINCT * FROM users;', argThat(isEmpty)));
'SELECT DISTINCT * FROM "users";', argThat(isEmpty)));
});
test('with limit statements', () async {
await (db.select(db.users)..limit(10, offset: 0)).get();
verify(executor.runSelect(
'SELECT * FROM users LIMIT 10 OFFSET 0;', argThat(isEmpty)));
'SELECT * FROM "users" LIMIT 10 OFFSET 0;', argThat(isEmpty)));
});
test('with simple limits', () async {
await (db.select(db.users)..limit(10)).get();
verify(executor.runSelect(
'SELECT * FROM users LIMIT 10;', argThat(isEmpty)));
'SELECT * FROM "users" LIMIT 10;', argThat(isEmpty)));
});
test('with like expressions', () async {
await (db.select(db.users)..where((u) => u.name.like('Dash%'))).get();
verify(executor
.runSelect('SELECT * FROM users WHERE name LIKE ?;', ['Dash%']));
.runSelect('SELECT * FROM "users" WHERE "name" LIKE ?;', ['Dash%']));
});
test('with order-by clauses', () async {
@ -65,8 +65,8 @@ void main() {
.get();
verify(executor.runSelect(
'SELECT * FROM users ORDER BY '
'is_awesome DESC, id ASC;',
'SELECT * FROM "users" ORDER BY '
'"is_awesome" DESC, "id" ASC;',
argThat(isEmpty)));
});
@ -75,7 +75,7 @@ void main() {
.get();
verify(executor.runSelect(
'SELECT * FROM users ORDER BY random() ASC;', argThat(isEmpty)));
'SELECT * FROM "users" ORDER BY random() ASC;', argThat(isEmpty)));
});
test('with complex predicates', () async {
@ -85,7 +85,7 @@ void main() {
.get();
verify(executor.runSelect(
'SELECT * FROM users WHERE NOT (name = ?) AND id > ?;',
'SELECT * FROM "users" WHERE NOT ("name" = ?) AND "id" > ?;',
['Dash', 12]));
});
@ -93,7 +93,7 @@ void main() {
await (db.select(db.users)..where((u) => u.isAwesome)).get();
verify(executor.runSelect(
'SELECT * FROM users WHERE is_awesome;', argThat(isEmpty)));
'SELECT * FROM "users" WHERE "is_awesome";', argThat(isEmpty)));
});
test('with aliased tables', () async {
@ -102,13 +102,14 @@ void main() {
..where((u) => u.id.isSmallerThan(const Constant(5))))
.get();
verify(executor.runSelect('SELECT * FROM users u WHERE id < 5;', []));
verify(
executor.runSelect('SELECT * FROM "users" "u" WHERE "id" < 5;', []));
});
});
group('SELECT results are parsed', () {
test('when all fields are non-null', () {
when(executor.runSelect('SELECT * FROM todos;', any))
when(executor.runSelect('SELECT * FROM "todos";', any))
.thenAnswer((_) => Future.value([_dataOfTodoEntry]));
expect(db.select(db.todosTable).get(), completion([_todoEntry]));
@ -130,7 +131,7 @@ void main() {
category: null,
);
when(executor.runSelect('SELECT * FROM todos;', any))
when(executor.runSelect('SELECT * FROM "todos";', any))
.thenAnswer((_) => Future.value(data));
expect(db.select(db.todosTable).get(), completion([resolved]));
@ -139,13 +140,13 @@ void main() {
group('queries for a single row', () {
test('get once', () {
when(executor.runSelect('SELECT * FROM todos;', any))
when(executor.runSelect('SELECT * FROM "todos";', any))
.thenAnswer((_) => Future.value([_dataOfTodoEntry]));
expect(db.select(db.todosTable).getSingle(), completion(_todoEntry));
});
test('get once without rows', () {
when(executor.runSelect('SELECT * FROM todos;', any))
when(executor.runSelect('SELECT * FROM "todos";', any))
.thenAnswer((_) => Future.value([]));
expect(db.select(db.todosTable).getSingle(), throwsA(anything));
@ -160,7 +161,7 @@ void main() {
];
var currentRow = 0;
when(executor.runSelect('SELECT * FROM todos;', any)).thenAnswer((_) {
when(executor.runSelect('SELECT * FROM "todos";', any)).thenAnswer((_) {
return Future.value(resultRows[currentRow++]);
});

View File

@ -28,7 +28,8 @@ void main() {
));
verify(executor.runUpdate(
'UPDATE todos SET title = ?, category = ?;', ['Updated title', 3]));
'UPDATE "todos" SET "title" = ?, "category" = ?;',
['Updated title', 3]));
});
test('with a WHERE clause', () async {
@ -37,7 +38,8 @@ void main() {
.write(const TodosTableCompanion(title: Value('Changed title')));
verify(executor.runUpdate(
'UPDATE todos SET title = ? WHERE id < ?;', ['Changed title', 50]));
'UPDATE "todos" SET "title" = ? WHERE "id" < ?;',
['Changed title', 50]));
});
test('with escaped column names', () async {
@ -46,7 +48,7 @@ void main() {
.write(PureDefaultsCompanion(txt: Value(MyCustomObject('foo'))));
verify(executor
.runUpdate('UPDATE pure_defaults SET "insert" = ?;', ['foo']));
.runUpdate('UPDATE "pure_defaults" SET "insert" = ?;', ['foo']));
});
});
@ -60,8 +62,8 @@ void main() {
));
verify(executor.runUpdate(
'UPDATE todos SET title = ?, content = ?, '
'target_date = ?, category = ? WHERE id = ?;',
'UPDATE "todos" SET "title" = ?, "content" = ?, '
'"target_date" = ?, "category" = ? WHERE "id" = ?;',
['Title', 'Updated content', null, null, 3]));
});
@ -75,9 +77,9 @@ void main() {
);
verify(executor.runUpdate(
'UPDATE users SET name = ?, profile_picture = ?, is_awesome = 1, '
'creation_time = CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)'
' WHERE id = ?;',
'UPDATE "users" SET "name" = ?, "profile_picture" = ?, "is_awesome" = 1, '
'"creation_time" = CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)'
' WHERE "id" = ?;',
['Hummingbird', Uint8List(0), 3]));
});
});
@ -121,22 +123,22 @@ void main() {
));
verify(executor.runUpdate(
'UPDATE todos SET content = content, target_date = target_date + ? '
'WHERE id = ?;',
'UPDATE "todos" SET "content" = "content", "target_date" = "target_date" + ? '
'WHERE "id" = ?;',
argThat(equals([86400, 4])),
));
});
group('custom updates', () {
test('execute the correct sql', () async {
await db.customUpdate('DELETE FROM users');
await db.customUpdate('DELETE FROM "users"');
verify(executor.runUpdate('DELETE FROM users', []));
verify(executor.runUpdate('DELETE FROM "users"', []));
});
test('map the variables correctly', () async {
await db.customUpdate(
'DELETE FROM users WHERE name = ? AND birthdate < ?',
'DELETE FROM "users" WHERE "name" = ? AND "birthdate" < ?',
variables: [
Variable.withString('Name'),
Variable.withDateTime(
@ -144,7 +146,7 @@ void main() {
]);
verify(executor.runUpdate(
'DELETE FROM users WHERE name = ? AND birthdate < ?',
'DELETE FROM "users" WHERE "name" = ? AND "birthdate" < ?',
['Name', 1551297563]));
});
@ -166,7 +168,7 @@ void main() {
test('update()', () async {
await db.users.update().write(const UsersCompanion(id: Value(3)));
verify(executor.runUpdate('UPDATE users SET id = ?;', [3]));
verify(executor.runUpdate('UPDATE "users" SET "id" = ?;', [3]));
});
test('replace', () async {
@ -174,7 +176,7 @@ void main() {
id: Value(3), description: Value('new name')));
verify(executor.runUpdate(
'UPDATE categories SET "desc" = ?, priority = 0 WHERE id = ?;',
'UPDATE "categories" SET "desc" = ?, "priority" = 0 WHERE "id" = ?;',
['new name', 3]));
});
});
@ -195,8 +197,8 @@ void main() {
.update()
.writeReturning(const CategoriesCompanion(description: Value('test')));
verify(executor
.runSelect('UPDATE categories SET "desc" = ? RETURNING *;', ['test']));
verify(executor.runSelect(
'UPDATE "categories" SET "desc" = ? RETURNING *;', ['test']));
verify(streamQueries.handleTableUpdates(
{TableUpdate.onTable(db.categories, kind: UpdateKind.update)}));

View File

@ -15,8 +15,8 @@ void main() {
nonNull.writeColumnDefinition(nonNullQuery);
nullable.writeColumnDefinition(nullableQuery);
expect(nullableQuery.sql, equals('name INTEGER NULL'));
expect(nonNullQuery.sql, equals('name INTEGER NOT NULL'));
expect(nullableQuery.sql, equals('"name" INTEGER NULL'));
expect(nonNullQuery.sql, equals('"name" INTEGER NOT NULL'));
});
group('mapping datetime values', () {

View File

@ -16,8 +16,8 @@ void main() {
final context = GenerationContext.fromDb(TodoDb());
column.writeColumnDefinition(context);
expect(
context.sql, equals('foo INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT'));
expect(context.sql,
equals('"foo" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT'));
});
test('int column writes PRIMARY KEY constraint', () {
@ -27,6 +27,6 @@ void main() {
final context = GenerationContext.fromDb(TodoDb());
column.writeColumnDefinition(context);
expect(context.sql, equals('foo INTEGER NOT NULL PRIMARY KEY'));
expect(context.sql, equals('"foo" INTEGER NOT NULL PRIMARY KEY'));
});
}

View File

@ -495,7 +495,7 @@ class $TodosTableTable extends TodosTable
'category', aliasedName, true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints: 'REFERENCES categories (id)');
defaultConstraints: 'REFERENCES "categories" ("id")');
@override
List<GeneratedColumn> get $columns =>
[id, title, content, targetDate, category];
@ -777,7 +777,7 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
'is_awesome', aliasedName, false,
type: DriftSqlType.bool,
requiredDuringInsert: false,
defaultConstraints: 'CHECK (is_awesome IN (0, 1))',
defaultConstraints: 'CHECK ("is_awesome" IN (0, 1))',
defaultValue: const Constant(true));
final VerificationMeta _profilePictureMeta =
const VerificationMeta('profilePicture');

View File

@ -7,32 +7,32 @@ import '../generated/custom_tables.dart';
import '../test_utils/test_utils.dart';
const _createNoIds =
'CREATE TABLE IF NOT EXISTS no_ids (payload BLOB NOT NULL PRIMARY KEY) '
'CREATE TABLE IF NOT EXISTS "no_ids" ("payload" BLOB NOT NULL PRIMARY KEY) '
'WITHOUT ROWID;';
const _createWithDefaults = 'CREATE TABLE IF NOT EXISTS with_defaults ('
"a TEXT DEFAULT 'something', b INTEGER UNIQUE);";
const _createWithDefaults = 'CREATE TABLE IF NOT EXISTS "with_defaults" ('
"\"a\" TEXT DEFAULT 'something', \"b\" INTEGER UNIQUE);";
const _createWithConstraints = 'CREATE TABLE IF NOT EXISTS with_constraints ('
'a TEXT, b INTEGER NOT NULL, c REAL, '
const _createWithConstraints = 'CREATE TABLE IF NOT EXISTS "with_constraints" ('
'"a" TEXT, "b" INTEGER NOT NULL, "c" REAL, '
'FOREIGN KEY (a, b) REFERENCES with_defaults (a, b)'
');';
const _createConfig = 'CREATE TABLE IF NOT EXISTS config ('
'config_key TEXT not null primary key, '
'config_value TEXT, '
'sync_state INTEGER, '
'sync_state_implicit INTEGER) STRICT;';
const _createConfig = 'CREATE TABLE IF NOT EXISTS "config" ('
'"config_key" TEXT not null primary key, '
'"config_value" TEXT, '
'"sync_state" INTEGER, '
'"sync_state_implicit" INTEGER) STRICT;';
const _createMyTable = 'CREATE TABLE IF NOT EXISTS mytable ('
'someid INTEGER NOT NULL, '
'sometext TEXT, '
'is_inserting INTEGER, '
'somedate TEXT, '
const _createMyTable = 'CREATE TABLE IF NOT EXISTS "mytable" ('
'"someid" INTEGER NOT NULL, '
'"sometext" TEXT, '
'"is_inserting" INTEGER, '
'"somedate" TEXT, '
'PRIMARY KEY (someid DESC)'
');';
const _createEmail = 'CREATE VIRTUAL TABLE IF NOT EXISTS email USING '
const _createEmail = 'CREATE VIRTUAL TABLE IF NOT EXISTS "email" USING '
'fts5(sender, title, body);';
const _createMyTrigger =
@ -98,7 +98,7 @@ void main() {
// regression test for #112: https://github.com/simolus3/drift/issues/112
await db.into(db.mytable).insert(const MytableCompanion());
verify(mock.runInsert('INSERT INTO mytable DEFAULT VALUES', []));
verify(mock.runInsert('INSERT INTO "mytable" DEFAULT VALUES', []));
});
test('runs queries with arrays and Dart templates', () async {
@ -108,7 +108,7 @@ void main() {
verify(mock.runSelect(
'SELECT * FROM config WHERE config_key IN (?1, ?2) '
'ORDER BY config_key ASC',
'ORDER BY "config_key" ASC',
['a', 'b'],
));
});
@ -122,8 +122,8 @@ void main() {
.readDynamic(predicate: (config) => config.configKey.equals('key'))
.getSingle();
verify(
mock.runSelect('SELECT * FROM config WHERE config_key = ?1', ['key']));
verify(mock
.runSelect('SELECT * FROM config WHERE "config_key" = ?1', ['key']));
expect(parsed, const Config(configKey: 'key', configValue: 'value'));
});
@ -136,7 +136,7 @@ void main() {
test('columns use table names in queries with multiple tables', () async {
await db.multiple(predicate: (d, c) => d.a.equals('foo')).get();
verify(mock.runSelect(argThat(contains('d.a = ?1')), any));
verify(mock.runSelect(argThat(contains('"d"."a" = ?1')), any));
});
test('order by-params are ignored by default', () async {
@ -241,7 +241,7 @@ void main() {
..where((tbl) => tbl.syncState.equalsValue(SyncType.synchronized)))
.getSingleOrNull();
verify(mock.runSelect('SELECT * FROM config WHERE sync_state = ?;',
verify(mock.runSelect('SELECT * FROM "config" WHERE "sync_state" = ?;',
[ConfigTable.$converter0.toSql(SyncType.synchronized)]));
});
}

View File

@ -53,7 +53,7 @@ void main() {
.map((row) => row.read<String>('sql'))
.getSingle();
expect(createStmt, contains('category INT'));
expect(createStmt, contains('"category" INT'));
final item = await db.select(db.todosTable).getSingle();
expect(item.category, 12);
@ -102,7 +102,7 @@ void main() {
expect(
createStmt,
allOf(contains('category INT'), isNot(contains('category_old'))),
allOf(contains('"category" INT'), isNot(contains('category_old'))),
);
final item = await db.select(db.todosTable).getSingle();

View File

@ -1,7 +1,6 @@
import 'package:charcode/ascii.dart';
import 'package:collection/collection.dart';
import 'package:drift/drift.dart' show SqlDialect;
import 'package:drift/sqlite_keywords.dart';
import 'package:drift_dev/moor_generator.dart';
import 'package:drift_dev/src/analyzer/options.dart';
import 'package:drift_dev/src/utils/string_escaper.dart';
@ -75,13 +74,6 @@ class SqlWriter extends NodeSqlBuilder {
(f) => f.variable.resolvedIndex == target.resolvedIndex);
}
@override
void identifier(String identifier,
{bool spaceBefore = true, bool spaceAfter = true}) {
final escaped = escapeIfNeeded(identifier, options.effectiveDialect);
symbol(escaped, spaceBefore: spaceBefore, spaceAfter: spaceAfter);
}
void _writeMoorVariable(FoundVariable variable) {
if (variable.isArray) {
_writeRawInSpaces('(\$${expandedName(variable)})');

View File

@ -1,4 +1,3 @@
import 'package:drift/sqlite_keywords.dart';
import 'package:sqlparser/sqlparser.dart';
import '../../model/model.dart';
@ -32,8 +31,8 @@ String defaultConstraints(DriftColumn column) {
for (final feature in column.features) {
if (feature is ResolvedDartForeignKeyReference) {
final tableName = escapeIfNeeded(feature.otherTable.sqlName);
final columnName = escapeIfNeeded(feature.otherColumn.name.name);
final tableName = '"${feature.otherTable.sqlName}"';
final columnName = '"${feature.otherColumn.name.name}"';
var constraint = 'REFERENCES $tableName ($columnName)';
@ -55,7 +54,7 @@ String defaultConstraints(DriftColumn column) {
}
if (column.type == DriftSqlType.bool) {
final name = escapeIfNeeded(column.name.name);
final name = '"${column.name.name}"';
defaultConstraints.add('CHECK ($name IN (0, 1))');
}

View File

@ -402,7 +402,7 @@ class $TodoEntriesTable extends TodoEntries
'category', aliasedName, true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints: 'REFERENCES categories (id)');
defaultConstraints: 'REFERENCES "categories" ("id")');
final VerificationMeta _dueDateMeta = const VerificationMeta('dueDate');
@override
late final GeneratedColumn<DateTime> dueDate = GeneratedColumn<DateTime>(

View File

@ -200,7 +200,7 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
'next_user', aliasedName, true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints: 'REFERENCES users (id)');
defaultConstraints: 'REFERENCES "users" ("id")');
@override
List<GeneratedColumn> get $columns => [id, name, birthday, nextUser];
@override

View File

@ -474,7 +474,7 @@ class $FriendshipsTable extends Friendships
'really_good_friends', aliasedName, false,
type: DriftSqlType.bool,
requiredDuringInsert: false,
defaultConstraints: 'CHECK (really_good_friends IN (0, 1))',
defaultConstraints: 'CHECK ("really_good_friends" IN (0, 1))',
defaultValue: const Constant(false));
@override
List<GeneratedColumn> get $columns =>
@ -562,7 +562,7 @@ abstract class _$Database extends GeneratedDatabase {
Selectable<FriendshipsOfResult> friendshipsOf(int user) {
return customSelect(
'SELECT f.really_good_friends,"user"."id" AS "nested_0.id", "user"."name" AS "nested_0.name", "user"."birth_date" AS "nested_0.birth_date", "user"."profile_picture" AS "nested_0.profile_picture", "user"."preferences" AS "nested_0.preferences" FROM friendships AS f INNER JOIN users AS "user" ON "user".id IN (f.first_user, f.second_user) AND "user".id != @1 WHERE(f.first_user = @1 OR f.second_user = @1)',
'SELECT f.really_good_friends,"user"."id" AS "nested_0.id", "user"."name" AS "nested_0.name", "user"."birth_date" AS "nested_0.birth_date", "user"."profile_picture" AS "nested_0.profile_picture", "user"."preferences" AS "nested_0.preferences" FROM friendships AS f INNER JOIN users AS user ON user.id IN (f.first_user, f.second_user) AND user.id != @1 WHERE(f.first_user = @1 OR f.second_user = @1)',
variables: [
Variable<int>(user)
],