Merge branch 'simolus3:develop' into develop

This commit is contained in:
Moshe Dicker 2024-04-11 15:42:05 -04:00 committed by GitHub
commit 3bd837517c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 94 additions and 24 deletions

View File

@ -4,6 +4,7 @@
generated companion class.
- Add the `TypeConverter.extensionType` factory to create type converters for
extension types.
- Fix invalid SQL syntax being generated for `BLOB` literals on postgres.
## 2.16.0

View File

@ -115,9 +115,17 @@ final class SqlTypes {
return (dart.millisecondsSinceEpoch ~/ 1000).toString();
}
} else if (dart is Uint8List) {
// BLOB literals are string literals containing hexadecimal data and
// preceded by a single "x" or "X" character. Example: X'53514C697465'
return "x'${hex.encode(dart)}'";
final String hexString = hex.encode(dart);
if (dialect == SqlDialect.postgres) {
// Postgres BYTEA hex format
// https://www.postgresql.org/docs/current/datatype-binary.html#DATATYPE-BINARY-BYTEA-HEX-FORMAT
return "'\\x$hexString'::bytea";
} else {
// BLOB literals are string literals containing hexadecimal data and
// preceded by a single "x" or "X" character. Example: X'53514C697465'
return "x'$hexString'";
}
} else if (dart is DriftAny) {
return mapToSqlLiteral(dart.rawSqlValue);
}

View File

@ -2,6 +2,9 @@
- Fix drift using the wrong import alias in generated part files.
- Add the `use_sql_column_name_as_json_key` builder option.
- Add a `setup` parameter to `SchemaVerifier`. It is called when the verifier
creates database connections (similar to the callback on `NativeDatabase`)
and can be used to register custom functions.
## 2.16.0

View File

@ -11,8 +11,18 @@ export 'package:drift_dev/src/services/schema/verifier_common.dart'
show SchemaMismatch;
abstract class SchemaVerifier {
factory SchemaVerifier(SchemaInstantiationHelper helper) =
VerifierImplementation;
/// Creates a schema verifier for the drift-generated [helper].
///
/// See [tests] for more information.
/// The optional [setup] parameter is used internally by the verifier for
/// every database connection it opens. This can be used to, for instance,
/// register custom functions expected by your database.
///
/// [tests]: https://drift.simonbinder.eu/docs/migrations/tests/
factory SchemaVerifier(
SchemaInstantiationHelper helper, {
void Function(Database raw)? setup,
}) = VerifierImplementation;
/// Creates a [DatabaseConnection] that contains empty tables created for the
/// known schema [version].

View File

@ -13,8 +13,9 @@ Expando<List<Input>> expectedSchema = Expando();
class VerifierImplementation implements SchemaVerifier {
final SchemaInstantiationHelper helper;
final Random _random = Random();
final void Function(Database)? setup;
VerifierImplementation(this.helper);
VerifierImplementation(this.helper, {this.setup});
@override
Future<void> migrateAndValidate(GeneratedDatabase db, int expectedVersion,
@ -57,14 +58,20 @@ class VerifierImplementation implements SchemaVerifier {
return buffer.toString();
}
Database _setupDatabase(String uri) {
final database = sqlite3.open(uri, uri: true);
setup?.call(database);
return database;
}
@override
Future<InitializedSchema> schemaAt(int version) async {
// Use distinct executors for setup and use, allowing us to close the helper
// db here and avoid creating it twice.
// https://www.sqlite.org/inmemorydb.html#sharedmemdb
final uri = 'file:mem${_randomString()}?mode=memory&cache=shared';
final dbForSetup = sqlite3.open(uri, uri: true);
final dbForUse = sqlite3.open(uri, uri: true);
final dbForSetup = _setupDatabase(uri);
final dbForUse = _setupDatabase(uri);
final executor = NativeDatabase.opened(dbForSetup);
final db = helper.databaseForVersion(executor, version);
@ -74,7 +81,7 @@ class VerifierImplementation implements SchemaVerifier {
await db.close();
return InitializedSchema(dbForUse, () {
final db = sqlite3.open(uri, uri: true);
final db = _setupDatabase(uri);
return DatabaseConnection(NativeDatabase.opened(db));
});
}

View File

@ -5,7 +5,11 @@ import 'package:drift_dev/src/services/schema/verifier_impl.dart';
import 'package:test/test.dart';
void main() {
final verifier = SchemaVerifier(_TestHelper());
final verifier = SchemaVerifier(
_TestHelper(),
setup: (rawDb) => rawDb.createFunction(
functionName: 'test_function', function: (args) => 1),
);
group('startAt', () {
test('starts at the requested version', () async {
@ -15,6 +19,12 @@ void main() {
expect(details.hadUpgrade, isFalse, reason: 'no upgrade expected');
}));
});
test('registers custom functions', () async {
final db = (await verifier.startAt(17)).executor;
await db.ensureOpen(_DelegatedUser(17, (_, details) async {}));
await db.runSelect('select test_function()', []);
});
});
group('migrateAndValidate', () {

View File

@ -26,19 +26,19 @@ void main() {
return row.read(expression)!;
}
void testWith<T extends Object>(CustomSqlType<T>? type, T value) {
test('with variable', () async {
final variable = Variable(value, type);
expect(await eval(variable), value);
});
test('with constant', () async {
final constant = Constant(value, type);
expect(await eval(constant), value);
});
}
group('custom types pass through', () {
void testWith<T extends Object>(CustomSqlType<T> type, T value) {
test('with variable', () async {
final variable = Variable(value, type);
expect(await eval(variable), value);
});
test('with constant', () async {
final constant = Constant(value, type);
expect(await eval(constant), value);
});
}
group('uuid', () => testWith(PgTypes.uuid, Uuid().v4obj()));
group(
'interval',
@ -60,6 +60,8 @@ void main() {
);
});
group('bytea', () => testWith(null, Uint8List.fromList([1, 2, 3, 4, 5])));
test('compare datetimes', () async {
final time = DateTime.now();
final before = Variable(

View File

@ -1,5 +1,6 @@
## 3.35.0-dev
- Fix parsing binary literals.
- Drift extensions: Allow custom class names for `CREATE VIEW` statements.
## 0.34.1

View File

@ -290,7 +290,7 @@ class Scanner {
}
final value = source
.substring(_startOffset + 1, _currentOffset - 1)
.substring(_startOffset + (binary ? 2 : 1), _currentOffset - 1)
.replaceAll("''", "'");
tokens.add(StringLiteralToken(value, _currentSpan, binary: binary));
}

View File

@ -642,7 +642,7 @@ class EqualityEnforcingVisitor implements AstVisitor<void, void> {
@override
void visitStringLiteral(StringLiteral e, void arg) {
final current = _currentAs<StringLiteral>(e);
_assert(current.value == e.value, e);
_assert(current.value == e.value && current.isBinary == e.isBinary, e);
_checkChildren(e);
}

View File

@ -1114,6 +1114,9 @@ class NodeSqlBuilder extends AstVisitor<void, void> {
@override
void visitStringLiteral(StringLiteral e, void arg) {
if (e.isBinary) {
symbol('X', spaceBefore: true);
}
_stringLiteral(e.value);
}

View File

@ -86,6 +86,26 @@ void main() {
);
});
test('binary string literal', () {
final scanner = Scanner("X'1234' x'5678'");
scanner.scanTokens();
expect(scanner.tokens, hasLength(3));
expect(
scanner.tokens[0],
const TypeMatcher<StringLiteralToken>()
.having((token) => token.binary, 'binary', isTrue)
.having((token) => token.value, 'value', '1234'),
);
expect(
scanner.tokens[1],
const TypeMatcher<StringLiteralToken>()
.having((token) => token.binary, 'binary', isTrue)
.having((token) => token.value, 'value', '5678'),
);
expect(scanner.tokens[2].type, TokenType.eof);
});
group('parses numeric literals', () {
void checkLiteral(String lexeme, NumericToken other, num value) {
final scanner = Scanner(lexeme)..scanTokens();

View File

@ -568,6 +568,11 @@ CREATE UNIQUE INDEX my_idx ON t1 (c1, c2, c3) WHERE c1 < c3;
testFormat('SELECT a -> b');
testFormat('SELECT a ->> b');
});
test('blob literal', () {
testFormat(
"select typeof(X'0100000300000000000000000000803F000000000000003F0000803F');");
});
});
test('identifiers', () {