mirror of https://github.com/AMT-Cheif/drift.git
Add `DriftAny` type to wrap `ANY` in strit tables
This commit is contained in:
parent
b520568984
commit
e2265eb597
|
@ -4,6 +4,8 @@
|
|||
values as string.
|
||||
- Add `updates` parameter to `Batch.customStatement` - it can be used to specify
|
||||
which tables are affected by the custom statement.
|
||||
- For `STRICT` tables in drift files declaring a `ANY` column, drift will now
|
||||
generate a mapping to the new `DriftAny` type.
|
||||
- Fix `UNIQUE` keys declared in drift files being written twice.
|
||||
- Fix `customConstraints` not appearing in dumped database schema files.
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
// This field is analyzed by drift_dev to easily obtain common types.
|
||||
export 'dart:typed_data' show Uint8List;
|
||||
|
||||
export 'runtime/types/converters.dart' show TypeConverter, JsonTypeConverter2;
|
||||
export 'runtime/types/mapping.dart' show DriftAny;
|
||||
export 'runtime/query_builder/query_builder.dart' show TableInfo;
|
||||
|
||||
export 'dsl/dsl.dart' show Table, View, DriftDatabase, DriftAccessor;
|
||||
|
|
|
@ -78,6 +78,10 @@ class SqlTypes {
|
|||
return dartValue ? 1 : 0;
|
||||
}
|
||||
|
||||
if (dartValue is DriftAny) {
|
||||
return dartValue.rawSqlValue;
|
||||
}
|
||||
|
||||
return dartValue;
|
||||
}
|
||||
|
||||
|
@ -115,6 +119,8 @@ class SqlTypes {
|
|||
// 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)}'";
|
||||
} else if (dart is DriftAny) {
|
||||
return mapToSqlLiteral(dart.rawSqlValue);
|
||||
}
|
||||
|
||||
throw ArgumentError.value(dart, 'dart',
|
||||
|
@ -190,10 +196,75 @@ class SqlTypes {
|
|||
return sqlValue as T;
|
||||
case DriftSqlType.double:
|
||||
return (sqlValue as num?)?.toDouble() as T;
|
||||
case DriftSqlType.any:
|
||||
return DriftAny(sqlValue) as T;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A drift type around a SQL value with an unknown type.
|
||||
///
|
||||
/// In [STRICT tables], a column can be declared with the type `ANY`. In such
|
||||
/// column, _any_ value can be stored without sqlite3 (or drift) attempting to
|
||||
/// cast it to a specific type. Thus, the [rawSqlValue] is directly passed to
|
||||
/// or from the underlying SQL database package.
|
||||
///
|
||||
/// To write a custom value into the database with [DriftAny], you can construct
|
||||
/// it and pass it into a [Variable] or into a companion of a table having a
|
||||
/// column with an `ANY` type.
|
||||
///
|
||||
/// [STRICT tables]: https://www.sqlite.org/stricttables.html
|
||||
@sealed
|
||||
class DriftAny {
|
||||
/// The direct, unmodified SQL value being wrapped by this [DriftAny]
|
||||
/// instance.
|
||||
///
|
||||
/// Please note that a [rawSqlValue] can't always be mapped to a unique Dart
|
||||
/// interpretation - see [readAs] for a discussion of which additional
|
||||
/// information is necessary to interpret this value.
|
||||
final Object rawSqlValue;
|
||||
|
||||
/// Constructs a [DriftAny] wrapper around the [rawSqlValue] that will be
|
||||
/// written into the database without any modification by drift.
|
||||
const DriftAny(this.rawSqlValue) : assert(rawSqlValue is! DriftAny);
|
||||
|
||||
/// Interprets the [rawSqlValue] as a drift [type] under the configuration
|
||||
/// given by [types].
|
||||
///
|
||||
/// A given [rawSqlValue] may have different Dart representations that would
|
||||
/// be given to you by drift. For instance, the SQL value `1` could have the
|
||||
/// following possible Dart interpretations:
|
||||
///
|
||||
/// - The [bool] constant `true`.
|
||||
/// - The [int] constant `1`
|
||||
/// - The big integer [BigInt.one].
|
||||
/// - All [DateTime] values having `1` as their UNIX timestamp in seconds
|
||||
/// (this depends on the configuration - drift can be configured to store
|
||||
/// date times [as text] too).
|
||||
///
|
||||
/// For this reason, it is not always possible to directly map these raw
|
||||
/// values to Dart without further information. Drift also needs to know the
|
||||
/// expected type and some configuration options for context. For all SQL
|
||||
/// types _except_ `ANY`, drift will do this for you behind the scenes.
|
||||
///
|
||||
/// You can obtain a [types] instance from a database or DAO by using
|
||||
/// [DatabaseConnectionUser.typeMapping].
|
||||
///
|
||||
/// [as text]: https://drift.simonbinder.eu/docs/getting-started/advanced_dart_tables/#datetime-options
|
||||
T readAs<T extends Object>(DriftSqlType<T> type, SqlTypes types) {
|
||||
return types.read<T>(type, rawSqlValue)!;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(DriftAny, rawSqlValue);
|
||||
|
||||
@override
|
||||
bool operator ==(other) {
|
||||
return identical(this, other) ||
|
||||
other is DriftAny && other.rawSqlValue == rawSqlValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// In [DriftSqlType.forNullableType], we need to do an `is` check over
|
||||
/// `DriftSqlType<T>` with a potentially nullable `T`. Since `DriftSqlType` is
|
||||
/// defined with a non-nullable `T`, this is illegal.
|
||||
|
@ -235,7 +306,12 @@ enum DriftSqlType<T extends Object> implements _InternalDriftSqlType<T> {
|
|||
blob<Uint8List>(),
|
||||
|
||||
/// A [double] value, stored as a `REAL` type in sqlite.
|
||||
double<core.double>();
|
||||
double<core.double>(),
|
||||
|
||||
/// The drift type for columns declared as `ANY` in [STRICT tables].
|
||||
///
|
||||
/// [STRICT tables]: https://www.sqlite.org/stricttables.html
|
||||
any<DriftAny>();
|
||||
|
||||
/// Returns a suitable representation of this type in SQL.
|
||||
String sqlTypeName(GenerationContext context) {
|
||||
|
@ -260,6 +336,8 @@ enum DriftSqlType<T extends Object> implements _InternalDriftSqlType<T> {
|
|||
return dialect == SqlDialect.sqlite ? 'BLOB' : 'bytea';
|
||||
case DriftSqlType.double:
|
||||
return dialect == SqlDialect.sqlite ? 'REAL' : 'float8';
|
||||
case DriftSqlType.any:
|
||||
return 'ANY';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../../test_utils/test_utils.dart';
|
||||
|
||||
void main() {
|
||||
test('implements == and hashCode', () {
|
||||
final a1 = DriftAny('a');
|
||||
final a2 = DriftAny('a');
|
||||
final b = DriftAny('b');
|
||||
|
||||
expect(a1, equals(a2));
|
||||
expect(a2, equals(a1));
|
||||
expect(a1.hashCode, a2.hashCode);
|
||||
|
||||
expect(b.hashCode, isNot(a1.hashCode));
|
||||
expect(b, isNot(a1));
|
||||
});
|
||||
|
||||
test('can be read', () {
|
||||
final value = DriftAny(1);
|
||||
final types = SqlTypes(false);
|
||||
|
||||
expect(value.readAs(DriftSqlType.any, types), value);
|
||||
expect(value.readAs(DriftSqlType.string, types), '1');
|
||||
expect(value.readAs(DriftSqlType.int, types), 1);
|
||||
expect(value.readAs(DriftSqlType.bool, types), true);
|
||||
expect(value.readAs(DriftSqlType.bigInt, types), BigInt.one);
|
||||
expect(value.readAs(DriftSqlType.double, types), 1.0);
|
||||
});
|
||||
|
||||
test('can be written', () {
|
||||
void bogusValue() {}
|
||||
|
||||
expect(Variable(DriftAny(bogusValue)), generates('?', [bogusValue]));
|
||||
});
|
||||
}
|
|
@ -11,4 +11,31 @@ void main() {
|
|||
reason: '$type should map null response to null value');
|
||||
}
|
||||
});
|
||||
|
||||
test('keeps `DriftAny` values unchanged', () {
|
||||
final values = [
|
||||
1,
|
||||
'two',
|
||||
#whatever,
|
||||
1.54,
|
||||
String,
|
||||
DateTime.now(),
|
||||
DateTime.now().toUtc(),
|
||||
() {},
|
||||
];
|
||||
|
||||
const mapping = SqlTypes(false);
|
||||
|
||||
for (final value in values) {
|
||||
expect(mapping.mapToSqlVariable(DriftAny(value)), value);
|
||||
expect(mapping.read(DriftSqlType.any, value), DriftAny(value));
|
||||
}
|
||||
});
|
||||
|
||||
test('maps `DriftAny` to literal', () {
|
||||
const mapping = SqlTypes(false);
|
||||
|
||||
expect(mapping.mapToSqlLiteral(DriftAny(1)), '1');
|
||||
expect(mapping.mapToSqlLiteral(DriftAny('two')), "'two'");
|
||||
});
|
||||
}
|
||||
|
|
|
@ -495,7 +495,7 @@ class WithConstraints extends Table
|
|||
|
||||
class Config extends DataClass implements Insertable<Config> {
|
||||
final String configKey;
|
||||
final String? configValue;
|
||||
final DriftAny? configValue;
|
||||
final SyncType? syncState;
|
||||
final SyncType? syncStateImplicit;
|
||||
const Config(
|
||||
|
@ -508,7 +508,7 @@ class Config extends DataClass implements Insertable<Config> {
|
|||
final map = <String, Expression>{};
|
||||
map['config_key'] = Variable<String>(configKey);
|
||||
if (!nullToAbsent || configValue != null) {
|
||||
map['config_value'] = Variable<String>(configValue);
|
||||
map['config_value'] = Variable<DriftAny>(configValue);
|
||||
}
|
||||
if (!nullToAbsent || syncState != null) {
|
||||
final converter = ConfigTable.$convertersyncStaten;
|
||||
|
@ -542,7 +542,7 @@ class Config extends DataClass implements Insertable<Config> {
|
|||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return Config(
|
||||
configKey: serializer.fromJson<String>(json['config_key']),
|
||||
configValue: serializer.fromJson<String?>(json['config_value']),
|
||||
configValue: serializer.fromJson<DriftAny?>(json['config_value']),
|
||||
syncState: serializer.fromJson<SyncType?>(json['sync_state']),
|
||||
syncStateImplicit: ConfigTable.$convertersyncStateImplicitn
|
||||
.fromJson(serializer.fromJson<int?>(json['sync_state_implicit'])),
|
||||
|
@ -557,7 +557,7 @@ class Config extends DataClass implements Insertable<Config> {
|
|||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'config_key': serializer.toJson<String>(configKey),
|
||||
'config_value': serializer.toJson<String?>(configValue),
|
||||
'config_value': serializer.toJson<DriftAny?>(configValue),
|
||||
'sync_state': serializer.toJson<SyncType?>(syncState),
|
||||
'sync_state_implicit': serializer.toJson<int?>(
|
||||
ConfigTable.$convertersyncStateImplicitn.toJson(syncStateImplicit)),
|
||||
|
@ -566,7 +566,7 @@ class Config extends DataClass implements Insertable<Config> {
|
|||
|
||||
Config copyWith(
|
||||
{String? configKey,
|
||||
Value<String?> configValue = const Value.absent(),
|
||||
Value<DriftAny?> configValue = const Value.absent(),
|
||||
Value<SyncType?> syncState = const Value.absent(),
|
||||
Value<SyncType?> syncStateImplicit = const Value.absent()}) =>
|
||||
Config(
|
||||
|
@ -603,7 +603,7 @@ class Config extends DataClass implements Insertable<Config> {
|
|||
|
||||
class ConfigCompanion extends UpdateCompanion<Config> {
|
||||
final Value<String> configKey;
|
||||
final Value<String?> configValue;
|
||||
final Value<DriftAny?> configValue;
|
||||
final Value<SyncType?> syncState;
|
||||
final Value<SyncType?> syncStateImplicit;
|
||||
const ConfigCompanion({
|
||||
|
@ -620,7 +620,7 @@ class ConfigCompanion extends UpdateCompanion<Config> {
|
|||
}) : configKey = Value(configKey);
|
||||
static Insertable<Config> custom({
|
||||
Expression<String>? configKey,
|
||||
Expression<String>? configValue,
|
||||
Expression<DriftAny>? configValue,
|
||||
Expression<int>? syncState,
|
||||
Expression<int>? syncStateImplicit,
|
||||
}) {
|
||||
|
@ -634,7 +634,7 @@ class ConfigCompanion extends UpdateCompanion<Config> {
|
|||
|
||||
ConfigCompanion copyWith(
|
||||
{Value<String>? configKey,
|
||||
Value<String?>? configValue,
|
||||
Value<DriftAny?>? configValue,
|
||||
Value<SyncType?>? syncState,
|
||||
Value<SyncType?>? syncStateImplicit}) {
|
||||
return ConfigCompanion(
|
||||
|
@ -652,7 +652,7 @@ class ConfigCompanion extends UpdateCompanion<Config> {
|
|||
map['config_key'] = Variable<String>(configKey.value);
|
||||
}
|
||||
if (configValue.present) {
|
||||
map['config_value'] = Variable<String>(configValue.value);
|
||||
map['config_value'] = Variable<DriftAny>(configValue.value);
|
||||
}
|
||||
if (syncState.present) {
|
||||
final converter = ConfigTable.$convertersyncStaten;
|
||||
|
@ -692,9 +692,9 @@ class ConfigTable extends Table with TableInfo<ConfigTable, Config> {
|
|||
$customConstraints: 'NOT NULL PRIMARY KEY');
|
||||
static const VerificationMeta _configValueMeta =
|
||||
const VerificationMeta('configValue');
|
||||
late final GeneratedColumn<String> configValue = GeneratedColumn<String>(
|
||||
late final GeneratedColumn<DriftAny> configValue = GeneratedColumn<DriftAny>(
|
||||
'config_value', aliasedName, true,
|
||||
type: DriftSqlType.string,
|
||||
type: DriftSqlType.any,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints: '');
|
||||
static const VerificationMeta _syncStateMeta =
|
||||
|
@ -752,7 +752,7 @@ class ConfigTable extends Table with TableInfo<ConfigTable, Config> {
|
|||
configKey: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}config_key'])!,
|
||||
configValue: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}config_value']),
|
||||
.read(DriftSqlType.any, data['${effectivePrefix}config_value']),
|
||||
syncState: ConfigTable.$convertersyncStaten.fromSql(attachedDatabase
|
||||
.typeMapping
|
||||
.read(DriftSqlType.int, data['${effectivePrefix}sync_state'])),
|
||||
|
@ -1454,7 +1454,7 @@ class WeirdTable extends Table with TableInfo<WeirdTable, WeirdData> {
|
|||
|
||||
class MyViewData extends DataClass {
|
||||
final String configKey;
|
||||
final String? configValue;
|
||||
final DriftAny? configValue;
|
||||
final SyncType? syncState;
|
||||
final SyncType? syncStateImplicit;
|
||||
const MyViewData(
|
||||
|
@ -1467,7 +1467,7 @@ class MyViewData extends DataClass {
|
|||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return MyViewData(
|
||||
configKey: serializer.fromJson<String>(json['config_key']),
|
||||
configValue: serializer.fromJson<String?>(json['config_value']),
|
||||
configValue: serializer.fromJson<DriftAny?>(json['config_value']),
|
||||
syncState: serializer.fromJson<SyncType?>(json['sync_state']),
|
||||
syncStateImplicit: ConfigTable.$convertersyncStateImplicitn
|
||||
.fromJson(serializer.fromJson<int?>(json['sync_state_implicit'])),
|
||||
|
@ -1483,7 +1483,7 @@ class MyViewData extends DataClass {
|
|||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'config_key': serializer.toJson<String>(configKey),
|
||||
'config_value': serializer.toJson<String?>(configValue),
|
||||
'config_value': serializer.toJson<DriftAny?>(configValue),
|
||||
'sync_state': serializer.toJson<SyncType?>(syncState),
|
||||
'sync_state_implicit': serializer.toJson<int?>(
|
||||
ConfigTable.$convertersyncStateImplicitn.toJson(syncStateImplicit)),
|
||||
|
@ -1492,7 +1492,7 @@ class MyViewData extends DataClass {
|
|||
|
||||
MyViewData copyWith(
|
||||
{String? configKey,
|
||||
Value<String?> configValue = const Value.absent(),
|
||||
Value<DriftAny?> configValue = const Value.absent(),
|
||||
Value<SyncType?> syncState = const Value.absent(),
|
||||
Value<SyncType?> syncStateImplicit = const Value.absent()}) =>
|
||||
MyViewData(
|
||||
|
@ -1551,7 +1551,7 @@ class MyView extends ViewInfo<MyView, MyViewData> implements HasResultSet {
|
|||
configKey: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}config_key'])!,
|
||||
configValue: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}config_value']),
|
||||
.read(DriftSqlType.any, data['${effectivePrefix}config_value']),
|
||||
syncState: ConfigTable.$convertersyncStaten.fromSql(attachedDatabase
|
||||
.typeMapping
|
||||
.read(DriftSqlType.int, data['${effectivePrefix}sync_state'])),
|
||||
|
@ -1564,9 +1564,9 @@ class MyView extends ViewInfo<MyView, MyViewData> implements HasResultSet {
|
|||
late final GeneratedColumn<String> configKey = GeneratedColumn<String>(
|
||||
'config_key', aliasedName, false,
|
||||
type: DriftSqlType.string);
|
||||
late final GeneratedColumn<String> configValue = GeneratedColumn<String>(
|
||||
late final GeneratedColumn<DriftAny> configValue = GeneratedColumn<DriftAny>(
|
||||
'config_value', aliasedName, true,
|
||||
type: DriftSqlType.string);
|
||||
type: DriftSqlType.any);
|
||||
late final GeneratedColumnWithTypeConverter<SyncType?, int> syncState =
|
||||
GeneratedColumn<int>('sync_state', aliasedName, true,
|
||||
type: DriftSqlType.int)
|
||||
|
@ -1603,10 +1603,10 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
|
|||
'CREATE TRIGGER my_trigger AFTER INSERT ON config BEGIN INSERT INTO with_defaults VALUES (new.config_key, LENGTH(new.config_value));END',
|
||||
'my_trigger');
|
||||
late final MyView myView = MyView(this);
|
||||
Future<int> writeConfig({required String key, String? value}) {
|
||||
Future<int> writeConfig({required String key, DriftAny? value}) {
|
||||
return customInsert(
|
||||
'REPLACE INTO config (config_key, config_value) VALUES (?1, ?2)',
|
||||
variables: [Variable<String>(key), Variable<String>(value)],
|
||||
variables: [Variable<String>(key), Variable<DriftAny>(value)],
|
||||
updates: {config},
|
||||
);
|
||||
}
|
||||
|
@ -1773,7 +1773,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
|
|||
row: row,
|
||||
rowid: row.read<int>('rowid'),
|
||||
configKey: row.read<String>('config_key'),
|
||||
configValue: row.readNullable<String>('config_value'),
|
||||
configValue: row.readNullable<DriftAny>('config_value'),
|
||||
syncState: NullAwareTypeConverter.wrapFromSql(
|
||||
ConfigTable.$convertersyncState,
|
||||
row.readNullable<int>('sync_state')),
|
||||
|
@ -1954,7 +1954,7 @@ typedef Multiple$predicate = Expression<bool> Function(
|
|||
class ReadRowIdResult extends CustomResultSet {
|
||||
final int rowid;
|
||||
final String configKey;
|
||||
final String? configValue;
|
||||
final DriftAny? configValue;
|
||||
final SyncType? syncState;
|
||||
final SyncType? syncStateImplicit;
|
||||
ReadRowIdResult({
|
||||
|
|
|
@ -20,7 +20,7 @@ CREATE TABLE with_constraints (
|
|||
|
||||
create table config (
|
||||
config_key TEXT not null primary key,
|
||||
config_value TEXT,
|
||||
config_value ANY,
|
||||
sync_state INTEGER MAPPED BY `const SyncTypeConverter()`,
|
||||
sync_state_implicit ENUM(SyncType)
|
||||
) STRICT AS "Config";
|
||||
|
|
|
@ -93,7 +93,7 @@ void main() {
|
|||
final stream = db.readView().watch();
|
||||
const entry = Config(
|
||||
configKey: 'another_key',
|
||||
configValue: 'value',
|
||||
configValue: DriftAny('value'),
|
||||
syncState: SyncType.synchronized,
|
||||
syncStateImplicit: SyncType.synchronized,
|
||||
);
|
||||
|
@ -155,7 +155,7 @@ void main() {
|
|||
final result = await db.addConfig(
|
||||
value: ConfigCompanion.insert(
|
||||
configKey: 'key2',
|
||||
configValue: const Value('val'),
|
||||
configValue: const Value(DriftAny('val')),
|
||||
syncState: const Value(SyncType.locallyCreated),
|
||||
syncStateImplicit: const Value(SyncType.locallyCreated),
|
||||
));
|
||||
|
@ -165,7 +165,7 @@ void main() {
|
|||
result.single,
|
||||
const Config(
|
||||
configKey: 'key2',
|
||||
configValue: 'val',
|
||||
configValue: DriftAny('val'),
|
||||
syncState: SyncType.locallyCreated,
|
||||
syncStateImplicit: SyncType.locallyCreated,
|
||||
),
|
||||
|
|
|
@ -20,7 +20,7 @@ const _createWithConstraints = 'CREATE TABLE IF NOT EXISTS "with_constraints" ('
|
|||
|
||||
const _createConfig = 'CREATE TABLE IF NOT EXISTS "config" ('
|
||||
'"config_key" TEXT NOT NULL PRIMARY KEY, '
|
||||
'"config_value" TEXT, '
|
||||
'"config_value" ANY, '
|
||||
'"sync_state" INTEGER, '
|
||||
'"sync_state_implicit" INTEGER) STRICT;';
|
||||
|
||||
|
@ -125,7 +125,8 @@ void main() {
|
|||
|
||||
verify(mock
|
||||
.runSelect('SELECT * FROM config WHERE "config_key" = ?1', ['key']));
|
||||
expect(parsed, const Config(configKey: 'key', configValue: 'value'));
|
||||
expect(
|
||||
parsed, const Config(configKey: 'key', configValue: DriftAny('value')));
|
||||
});
|
||||
|
||||
test('applies default parameter expressions when not set', () async {
|
||||
|
@ -219,7 +220,7 @@ void main() {
|
|||
entry,
|
||||
const Config(
|
||||
configKey: 'key',
|
||||
configValue: 'value',
|
||||
configValue: DriftAny('value'),
|
||||
syncState: SyncType.locallyUpdated,
|
||||
syncStateImplicit: SyncType.locallyUpdated,
|
||||
),
|
||||
|
|
|
@ -280,6 +280,8 @@ class KnownSqliteFunction {
|
|||
return 'TEXT';
|
||||
case BasicType.blob:
|
||||
return 'BLOB';
|
||||
case BasicType.any:
|
||||
return 'ANY';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@ class KnownDriftTypes {
|
|||
final InterfaceType driftAccessor;
|
||||
final InterfaceElement typeConverter;
|
||||
final InterfaceElement jsonTypeConverter;
|
||||
final InterfaceType driftAny;
|
||||
final InterfaceType uint8List;
|
||||
|
||||
KnownDriftTypes._(
|
||||
this.helperLibrary,
|
||||
|
@ -32,6 +34,8 @@ class KnownDriftTypes {
|
|||
this.jsonTypeConverter,
|
||||
this.driftDatabase,
|
||||
this.driftAccessor,
|
||||
this.driftAny,
|
||||
this.uint8List,
|
||||
);
|
||||
|
||||
/// Constructs the set of known drift types from a helper library, which is
|
||||
|
@ -52,6 +56,10 @@ class KnownDriftTypes {
|
|||
exportNamespace.get('JsonTypeConverter2') as InterfaceElement,
|
||||
dbElement.defaultInstantiation,
|
||||
daoElement.defaultInstantiation,
|
||||
(exportNamespace.get('DriftAny') as InterfaceElement)
|
||||
.defaultInstantiation,
|
||||
(exportNamespace.get('Uint8List') as InterfaceElement)
|
||||
.defaultInstantiation,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ class DartTableResolver extends LocalElementResolver<DiscoveredDartTable> {
|
|||
final primaryKey = await _readPrimaryKey(element, columns);
|
||||
final uniqueKeys = await _readUniqueKeys(element, columns);
|
||||
|
||||
final dataClassInfo = _readDataClassInformation(columns, element);
|
||||
final dataClassInfo = await _readDataClassInformation(columns, element);
|
||||
|
||||
final references = <DriftElement>{};
|
||||
|
||||
|
@ -107,8 +107,8 @@ class DartTableResolver extends LocalElementResolver<DiscoveredDartTable> {
|
|||
return table;
|
||||
}
|
||||
|
||||
DataClassInformation _readDataClassInformation(
|
||||
List<DriftColumn> columns, ClassElement element) {
|
||||
Future<DataClassInformation> _readDataClassInformation(
|
||||
List<DriftColumn> columns, ClassElement element) async {
|
||||
DartObject? dataClassName;
|
||||
DartObject? useRowClass;
|
||||
|
||||
|
@ -162,10 +162,11 @@ class DartTableResolver extends LocalElementResolver<DiscoveredDartTable> {
|
|||
}
|
||||
}
|
||||
|
||||
final helper = await resolver.driver.loadKnownTypes();
|
||||
final verified = existingClass == null
|
||||
? null
|
||||
: validateExistingClass(columns, existingClass,
|
||||
constructorInExistingClass!, generateInsertable!, this);
|
||||
constructorInExistingClass!, generateInsertable!, this, helper);
|
||||
return DataClassInformation(name, customParentClass, verified);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ class DartViewResolver extends LocalElementResolver<DiscoveredDartView> {
|
|||
final staticReferences = await _parseStaticReferences();
|
||||
final structure = await _parseSelectStructure(staticReferences);
|
||||
final columns = await _parseColumns(structure, staticReferences);
|
||||
final dataClassInfo = _readDataClassInformation(columns);
|
||||
final dataClassInfo = await _readDataClassInformation(columns);
|
||||
|
||||
return DriftView(
|
||||
discovered.ownId,
|
||||
|
@ -303,7 +303,8 @@ class DartViewResolver extends LocalElementResolver<DiscoveredDartView> {
|
|||
}[name];
|
||||
}
|
||||
|
||||
DataClassInformation _readDataClassInformation(List<DriftColumn> columns) {
|
||||
Future<DataClassInformation> _readDataClassInformation(
|
||||
List<DriftColumn> columns) async {
|
||||
DartObject? useRowClass;
|
||||
DartObject? driftView;
|
||||
AnnotatedDartCode? customParentClass;
|
||||
|
@ -359,10 +360,11 @@ class DartViewResolver extends LocalElementResolver<DiscoveredDartView> {
|
|||
}
|
||||
}
|
||||
|
||||
final knownTypes = await resolver.driver.loadKnownTypes();
|
||||
final verified = existingClass == null
|
||||
? null
|
||||
: validateExistingClass(columns, existingClass,
|
||||
constructorInExistingClass!, generateInsertable!, this);
|
||||
constructorInExistingClass!, generateInsertable!, this, knownTypes);
|
||||
return DataClassInformation(name, customParentClass, verified);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,6 +99,8 @@ class TypeMapping {
|
|||
return ResolvedType(type: BasicType.blob, hint: overrideHint);
|
||||
case DriftSqlType.double:
|
||||
return ResolvedType(type: BasicType.real, hint: overrideHint);
|
||||
case DriftSqlType.any:
|
||||
return ResolvedType(type: BasicType.any, hint: overrideHint);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,6 +134,8 @@ class TypeMapping {
|
|||
return DriftSqlType.string;
|
||||
case BasicType.blob:
|
||||
return DriftSqlType.blob;
|
||||
case BasicType.any:
|
||||
return DriftSqlType.any;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -304,8 +304,9 @@ class DriftTableResolver extends LocalElementResolver<DiscoveredDriftTable> {
|
|||
'you missing an import?',
|
||||
));
|
||||
} else {
|
||||
existingRowClass =
|
||||
validateExistingClass(columns, clazz, '', false, this);
|
||||
final knownTypes = await resolver.driver.loadKnownTypes();
|
||||
existingRowClass = validateExistingClass(
|
||||
columns, clazz, '', false, this, knownTypes);
|
||||
dataClassName = existingRowClass?.targetClass.toString();
|
||||
}
|
||||
} else if (overriddenNames.contains('/')) {
|
||||
|
|
|
@ -65,8 +65,9 @@ class DriftViewResolver extends DriftElementResolver<DiscoveredDriftView> {
|
|||
'you missing an import?',
|
||||
));
|
||||
} else {
|
||||
existingRowClass =
|
||||
validateExistingClass(columns, clazz, '', false, this);
|
||||
final knownTypes = await resolver.driver.loadKnownTypes();
|
||||
existingRowClass = validateExistingClass(
|
||||
columns, clazz, '', false, this, knownTypes);
|
||||
final newName = existingRowClass?.targetClass.toString();
|
||||
if (newName != null) {
|
||||
rowClassName = newName;
|
||||
|
|
|
@ -27,6 +27,7 @@ ExistingRowClass? validateExistingClass(
|
|||
String constructor,
|
||||
bool generateInsertable,
|
||||
LocalElementResolver step,
|
||||
KnownDriftTypes knownTypes,
|
||||
) {
|
||||
final desiredClass = dartClass.classElement;
|
||||
final library = desiredClass.library;
|
||||
|
@ -111,7 +112,7 @@ ExistingRowClass? validateExistingClass(
|
|||
namedColumns[parameter] = column;
|
||||
}
|
||||
|
||||
_checkParameterType(parameter, column, step);
|
||||
_checkParameterType(parameter, column, step, knownTypes);
|
||||
} else if (!parameter.isOptional) {
|
||||
step.reportError(DriftAnalysisError.forDartElement(
|
||||
parameter,
|
||||
|
@ -214,7 +215,7 @@ AppliedTypeConverter? readTypeConverter(
|
|||
}
|
||||
|
||||
_checkType(columnType, columnIsNullable, null, sqlType, library.typeProvider,
|
||||
library.typeSystem, reportError);
|
||||
library.typeSystem, helper, reportError);
|
||||
|
||||
return AppliedTypeConverter(
|
||||
expression: AnnotatedDartCode.ast(dartExpression),
|
||||
|
@ -274,8 +275,12 @@ AppliedTypeConverter readEnumConverter(
|
|||
);
|
||||
}
|
||||
|
||||
void _checkParameterType(ParameterElement element, DriftColumn column,
|
||||
LocalElementResolver resolver) {
|
||||
void _checkParameterType(
|
||||
ParameterElement element,
|
||||
DriftColumn column,
|
||||
LocalElementResolver resolver,
|
||||
KnownDriftTypes helper,
|
||||
) {
|
||||
final type = element.type;
|
||||
final library = element.library!;
|
||||
final typesystem = library.typeSystem;
|
||||
|
@ -301,6 +306,7 @@ void _checkParameterType(ParameterElement element, DriftColumn column,
|
|||
element.type,
|
||||
library.typeProvider,
|
||||
library.typeSystem,
|
||||
helper,
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
@ -312,6 +318,7 @@ void _checkType(
|
|||
DartType typeToCheck,
|
||||
TypeProvider typeProvider,
|
||||
TypeSystem typeSystem,
|
||||
KnownDriftTypes knownTypes,
|
||||
void Function(String) error,
|
||||
) {
|
||||
DartType expectedDartType;
|
||||
|
@ -321,27 +328,17 @@ void _checkType(
|
|||
typeToCheck = typeSystem.promoteToNonNull(typeToCheck);
|
||||
}
|
||||
} else {
|
||||
expectedDartType = typeProvider.typeFor(columnType);
|
||||
expectedDartType = typeProvider.typeFor(columnType, knownTypes);
|
||||
}
|
||||
|
||||
// BLOB columns should be stored in an Uint8List (or a supertype of that).
|
||||
// We don't get a Uint8List from the type provider unfortunately, but as it
|
||||
// cannot be extended we can just check for that manually.
|
||||
final isAllowedUint8List = typeConverter == null &&
|
||||
columnType == DriftSqlType.blob &&
|
||||
typeToCheck is InterfaceType &&
|
||||
typeToCheck.element.name == 'Uint8List' &&
|
||||
typeToCheck.element.library.name == 'dart.typed_data';
|
||||
|
||||
if (!typeSystem.isAssignableTo(expectedDartType, typeToCheck) &&
|
||||
!isAllowedUint8List) {
|
||||
if (!typeSystem.isAssignableTo(expectedDartType, typeToCheck)) {
|
||||
error('Parameter must accept '
|
||||
'${expectedDartType.getDisplayString(withNullability: true)}');
|
||||
}
|
||||
}
|
||||
|
||||
extension on TypeProvider {
|
||||
DartType typeFor(DriftSqlType type) {
|
||||
DartType typeFor(DriftSqlType type, KnownDriftTypes knownTypes) {
|
||||
switch (type) {
|
||||
case DriftSqlType.int:
|
||||
return intType;
|
||||
|
@ -356,9 +353,11 @@ extension on TypeProvider {
|
|||
return intElement.library.getClass('DateTime')!.instantiate(
|
||||
typeArguments: const [], nullabilitySuffix: NullabilitySuffix.none);
|
||||
case DriftSqlType.blob:
|
||||
return listType(intType);
|
||||
return knownTypes.uint8List;
|
||||
case DriftSqlType.double:
|
||||
return doubleType;
|
||||
case DriftSqlType.any:
|
||||
return knownTypes.driftAny;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,47 +39,6 @@ extension OperationOnTypes on HasType {
|
|||
|
||||
return nullable;
|
||||
}
|
||||
|
||||
/// The moor Dart type that matches the type of this column.
|
||||
///
|
||||
/// This is the same as [dartTypeCode] but without custom types.
|
||||
String variableTypeCode({bool? nullable}) {
|
||||
if (isArray) {
|
||||
return 'List<${innerColumnType(nullable: nullable ?? this.nullable)}>';
|
||||
} else {
|
||||
return innerColumnType(nullable: nullable ?? this.nullable);
|
||||
}
|
||||
}
|
||||
|
||||
String innerColumnType({bool nullable = false}) {
|
||||
String code;
|
||||
|
||||
switch (sqlType) {
|
||||
case DriftSqlType.int:
|
||||
code = 'int';
|
||||
break;
|
||||
case DriftSqlType.bigInt:
|
||||
code = 'BigInt';
|
||||
break;
|
||||
case DriftSqlType.string:
|
||||
code = 'String';
|
||||
break;
|
||||
case DriftSqlType.bool:
|
||||
code = 'bool';
|
||||
break;
|
||||
case DriftSqlType.dateTime:
|
||||
code = 'DateTime';
|
||||
break;
|
||||
case DriftSqlType.blob:
|
||||
code = 'Uint8List';
|
||||
break;
|
||||
case DriftSqlType.double:
|
||||
code = 'double';
|
||||
break;
|
||||
}
|
||||
|
||||
return nullable ? '$code?' : code;
|
||||
}
|
||||
}
|
||||
|
||||
Map<DriftSqlType, DartTopLevelSymbol> dartTypeNames = Map.unmodifiable({
|
||||
|
@ -90,6 +49,7 @@ Map<DriftSqlType, DartTopLevelSymbol> dartTypeNames = Map.unmodifiable({
|
|||
DriftSqlType.dateTime: DartTopLevelSymbol('DateTime', Uri.parse('dart:core')),
|
||||
DriftSqlType.blob: DartTopLevelSymbol('Uint8List', Uri.parse('dart:convert')),
|
||||
DriftSqlType.double: DartTopLevelSymbol('double', Uri.parse('dart:core')),
|
||||
DriftSqlType.any: DartTopLevelSymbol('DriftAny', AnnotatedDartCode.drift),
|
||||
});
|
||||
|
||||
/// Maps from a column type to code that can be used to create a variable of the
|
||||
|
|
|
@ -450,18 +450,25 @@ extension _SerializeSqlType on DriftSqlType {
|
|||
static DriftSqlType deserialize(String description) {
|
||||
switch (description) {
|
||||
case 'ColumnType.boolean':
|
||||
case 'bool':
|
||||
return DriftSqlType.bool;
|
||||
case 'ColumnType.text':
|
||||
case 'string':
|
||||
return DriftSqlType.string;
|
||||
case 'ColumnType.bigInt':
|
||||
case 'bigInt':
|
||||
return DriftSqlType.bigInt;
|
||||
case 'ColumnType.integer':
|
||||
case 'int':
|
||||
return DriftSqlType.int;
|
||||
case 'ColumnType.datetime':
|
||||
case 'datetime':
|
||||
return DriftSqlType.dateTime;
|
||||
case 'ColumnType.blob':
|
||||
case 'blob':
|
||||
return DriftSqlType.blob;
|
||||
case 'ColumnType.real':
|
||||
case 'real':
|
||||
return DriftSqlType.double;
|
||||
default:
|
||||
throw ArgumentError.value(
|
||||
|
@ -470,21 +477,6 @@ extension _SerializeSqlType on DriftSqlType {
|
|||
}
|
||||
|
||||
String toSerializedString() {
|
||||
switch (this) {
|
||||
case DriftSqlType.bool:
|
||||
return 'ColumnType.boolean';
|
||||
case DriftSqlType.string:
|
||||
return 'ColumnType.text';
|
||||
case DriftSqlType.bigInt:
|
||||
return 'ColumnType.bigInt';
|
||||
case DriftSqlType.int:
|
||||
return 'ColumnType.integer';
|
||||
case DriftSqlType.dateTime:
|
||||
return 'ColumnType.datetime';
|
||||
case DriftSqlType.blob:
|
||||
return 'ColumnType.blob';
|
||||
case DriftSqlType.double:
|
||||
return 'ColumnType.real';
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -665,7 +665,8 @@ class _ExpandedVariableWriter {
|
|||
// write all the variables sequentially.
|
||||
String constructVar(String dartExpr) {
|
||||
// No longer an array here, we apply a for loop if necessary
|
||||
final type = element.innerColumnType(nullable: false);
|
||||
final type =
|
||||
_emitter.dartCode(_emitter.innerColumnType(element, nullable: false));
|
||||
|
||||
final varType = _emitter.drift('Variable');
|
||||
final buffer = StringBuffer('$varType<$type>(');
|
||||
|
|
|
@ -263,7 +263,8 @@ class DataClassWriter {
|
|||
}
|
||||
if (needsScope) _buffer.write('{');
|
||||
|
||||
final typeName = column.variableTypeCode(nullable: false);
|
||||
final typeName =
|
||||
_emitter.dartCode(_emitter.variableTypeCode(column, nullable: false));
|
||||
final mapSetter = 'map[${asDartLiteral(column.nameInSql)}] = '
|
||||
'$variable<$typeName>';
|
||||
|
||||
|
|
|
@ -102,8 +102,9 @@ abstract class TableOrViewWriter {
|
|||
emitter.dartCode(column.clientDefaultCode!);
|
||||
}
|
||||
|
||||
final innerType = column.innerColumnType();
|
||||
var type = '${emitter.drift('GeneratedColumn')}<$innerType>';
|
||||
final innerType = emitter.innerColumnType(column);
|
||||
var type =
|
||||
'${emitter.drift('GeneratedColumn')}<${emitter.dartCode(innerType)}>';
|
||||
expressionBuffer
|
||||
..write(type)
|
||||
..write(
|
||||
|
@ -135,7 +136,7 @@ abstract class TableOrViewWriter {
|
|||
.readConverter(converter, forNullable: column.nullable));
|
||||
|
||||
type = '${emitter.drift('GeneratedColumnWithTypeConverter')}'
|
||||
'<$mappedType, $innerType>';
|
||||
'<$mappedType, ${emitter.dartCode(innerType)}>';
|
||||
expressionBuffer
|
||||
..write('.withConverter<')
|
||||
..write(mappedType)
|
||||
|
|
|
@ -132,7 +132,7 @@ class UpdateCompanionWriter {
|
|||
|
||||
final expression = _emitter.drift('Expression');
|
||||
for (final column in columns) {
|
||||
final typeName = column.innerColumnType();
|
||||
final typeName = _emitter.dartCode(_emitter.innerColumnType(column));
|
||||
_buffer.write('$expression<$typeName>? ${column.nameInDart}, \n');
|
||||
}
|
||||
|
||||
|
@ -188,7 +188,8 @@ class UpdateCompanionWriter {
|
|||
final getterName = thisIfNeeded(column.nameInDart, locals);
|
||||
|
||||
_buffer.write('if ($getterName.present) {');
|
||||
final typeName = column.variableTypeCode(nullable: false);
|
||||
final typeName =
|
||||
_emitter.dartCode(_emitter.variableTypeCode(column, nullable: false));
|
||||
final mapSetter = 'map[${asDartLiteral(column.nameInSql)}] = '
|
||||
'${_emitter.drift('Variable')}<$typeName>';
|
||||
|
||||
|
|
|
@ -177,6 +177,35 @@ abstract class _NodeOrWriter {
|
|||
}
|
||||
}
|
||||
|
||||
/// The Dart type that matches the type of this column, ignoring type
|
||||
/// converters.
|
||||
///
|
||||
/// This is the same as [dartType] but without custom types.
|
||||
AnnotatedDartCode variableTypeCode(HasType type, {bool? nullable}) {
|
||||
if (type.isArray) {
|
||||
final inner = innerColumnType(type, nullable: nullable ?? type.nullable);
|
||||
return AnnotatedDartCode([
|
||||
DartTopLevelSymbol.list,
|
||||
'<',
|
||||
...inner.elements,
|
||||
'>',
|
||||
]);
|
||||
} else {
|
||||
return innerColumnType(type, nullable: nullable ?? type.nullable);
|
||||
}
|
||||
}
|
||||
|
||||
/// The raw Dart type for this column, taking its nullability only from the
|
||||
/// [nullable] parameter.
|
||||
///
|
||||
/// This type does not respect type converters or arrays.
|
||||
AnnotatedDartCode innerColumnType(HasType type, {bool nullable = false}) {
|
||||
return AnnotatedDartCode([
|
||||
dartTypeNames[type.sqlType],
|
||||
if (nullable) '?',
|
||||
]);
|
||||
}
|
||||
|
||||
String refUri(Uri definition, String element) {
|
||||
final prefix =
|
||||
writer.generationOptions.imports.prefixFor(definition, element);
|
||||
|
|
|
@ -405,6 +405,32 @@ class Companies extends Table {
|
|||
.having((e) => e.isAsyncFactory, 'isAsyncFactory', isTrue));
|
||||
});
|
||||
|
||||
test('handles `ANY` columns', () async {
|
||||
final backend = TestBackend.inTest({
|
||||
'a|lib/a.drift': '''
|
||||
import 'row.dart';
|
||||
|
||||
CREATE TABLE foo (
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
x ANY
|
||||
) STRICT WITH FooData;
|
||||
''',
|
||||
'a|lib/row.dart': '''
|
||||
import 'package:drift/drift.dart';
|
||||
|
||||
class FooData {
|
||||
FooData({required int id, required DriftAny? x});
|
||||
}
|
||||
''',
|
||||
});
|
||||
|
||||
final file = await backend.analyze('package:a/a.drift');
|
||||
backend.expectNoErrors();
|
||||
|
||||
final table = file.analyzedElements.single as DriftTable;
|
||||
expect(table.existingRowClass, isA<ExistingRowClass>());
|
||||
});
|
||||
|
||||
group('custom data class parent', () {
|
||||
test('check valid', () async {
|
||||
final file =
|
||||
|
|
|
@ -193,4 +193,52 @@ secondQuery AS MyResultClass: SELECT 'bar' AS r1, 2 AS r2;
|
|||
})),
|
||||
}, result.dartOutputs, result);
|
||||
});
|
||||
|
||||
test('generates imports for query variables with modular generation',
|
||||
() async {
|
||||
final result = await emulateDriftBuild(
|
||||
inputs: {
|
||||
'a|lib/main.drift': '''
|
||||
CREATE TABLE my_table (
|
||||
a INTEGER PRIMARY KEY,
|
||||
b TEXT,
|
||||
c BLOB,
|
||||
d ANY
|
||||
) STRICT;
|
||||
|
||||
q: INSERT INTO my_table (b, c, d) VALUES (?, ?, ?);
|
||||
''',
|
||||
},
|
||||
modularBuild: true,
|
||||
logger: loggerThat(neverEmits(anything)),
|
||||
);
|
||||
|
||||
checkOutputs({
|
||||
'a|lib/main.drift.dart': decodedMatches(
|
||||
allOf(
|
||||
contains(
|
||||
'import \'package:drift/drift.dart\' as i0;\n'
|
||||
'import \'package:a/main.drift.dart\' as i1;\n'
|
||||
'import \'dart:convert\' as i2;\n'
|
||||
'import \'package:drift/internal/modular.dart\' as i3;\n',
|
||||
),
|
||||
contains(
|
||||
'class MyTableData extends i0.DataClass\n'
|
||||
' implements i0.Insertable<i1.MyTableData> {\n'
|
||||
' final int a;\n'
|
||||
' final String? b;\n'
|
||||
' final i2.Uint8List? c;\n'
|
||||
' final i0.DriftAny? d;\n',
|
||||
),
|
||||
contains(
|
||||
' variables: [\n'
|
||||
' i0.Variable<String>(var1),\n'
|
||||
' i0.Variable<i2.Uint8List>(var2),\n'
|
||||
' i0.Variable<i0.DriftAny>(var3)\n'
|
||||
' ],\n',
|
||||
),
|
||||
),
|
||||
),
|
||||
}, result.dartOutputs, result);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ void main() {
|
|||
{
|
||||
"name": "id",
|
||||
"getter_name": "id",
|
||||
"moor_type": "ColumnType.integer",
|
||||
"moor_type": "int",
|
||||
"nullable": false,
|
||||
"customConstraints": "PRIMARY KEY",
|
||||
"default_dart": null,
|
||||
|
@ -63,7 +63,7 @@ void main() {
|
|||
{
|
||||
"name": "name",
|
||||
"getter_name": "name",
|
||||
"moor_type": "ColumnType.text",
|
||||
"moor_type": "string",
|
||||
"nullable": true,
|
||||
"customConstraints": "",
|
||||
"default_dart": null,
|
||||
|
@ -92,7 +92,7 @@ void main() {
|
|||
{
|
||||
"name": "name",
|
||||
"getter_name": "name",
|
||||
"moor_type": "ColumnType.text",
|
||||
"moor_type": "string",
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
|
|
|
@ -90,7 +90,11 @@ class Database {}
|
|||
});
|
||||
|
||||
test('can generate code from schema json', () {
|
||||
final serializedSchema = json.decode(expected) as Map<String, dynamic>;
|
||||
final serializedSchema = json.decode(
|
||||
// Column types used to be serialized under a different format, test
|
||||
// reading that as well.
|
||||
expected.replaceAll('"int"', '"ColumnType.integer"'))
|
||||
as Map<String, dynamic>;
|
||||
final reader = SchemaReader.readJson(serializedSchema);
|
||||
|
||||
final writer = Writer(
|
||||
|
@ -142,7 +146,7 @@ const expected = r'''
|
|||
{
|
||||
"name": "id",
|
||||
"getter_name": "id",
|
||||
"moor_type": "ColumnType.integer",
|
||||
"moor_type": "int",
|
||||
"nullable": false,
|
||||
"customConstraints": "NOT NULL PRIMARY KEY AUTOINCREMENT",
|
||||
"default_dart": null,
|
||||
|
@ -154,7 +158,7 @@ const expected = r'''
|
|||
{
|
||||
"name": "name",
|
||||
"getter_name": "name",
|
||||
"moor_type": "ColumnType.text",
|
||||
"moor_type": "string",
|
||||
"nullable": false,
|
||||
"customConstraints": "NOT NULL",
|
||||
"default_dart": null,
|
||||
|
@ -185,7 +189,7 @@ const expected = r'''
|
|||
{
|
||||
"name": "sender",
|
||||
"getter_name": "sender",
|
||||
"moor_type": "ColumnType.text",
|
||||
"moor_type": "string",
|
||||
"nullable": false,
|
||||
"customConstraints": "",
|
||||
"default_dart": null,
|
||||
|
@ -195,7 +199,7 @@ const expected = r'''
|
|||
{
|
||||
"name": "title",
|
||||
"getter_name": "title",
|
||||
"moor_type": "ColumnType.text",
|
||||
"moor_type": "string",
|
||||
"nullable": false,
|
||||
"customConstraints": "",
|
||||
"default_dart": null,
|
||||
|
@ -205,7 +209,7 @@ const expected = r'''
|
|||
{
|
||||
"name": "body",
|
||||
"getter_name": "body",
|
||||
"moor_type": "ColumnType.text",
|
||||
"moor_type": "string",
|
||||
"nullable": false,
|
||||
"customConstraints": "",
|
||||
"default_dart": null,
|
||||
|
@ -230,7 +234,7 @@ const expected = r'''
|
|||
{
|
||||
"name": "id",
|
||||
"getter_name": "id",
|
||||
"moor_type": "ColumnType.integer",
|
||||
"moor_type": "int",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"defaultConstraints": "PRIMARY KEY AUTOINCREMENT",
|
||||
|
@ -243,7 +247,7 @@ const expected = r'''
|
|||
{
|
||||
"name": "name",
|
||||
"getter_name": "name",
|
||||
"moor_type": "ColumnType.text",
|
||||
"moor_type": "string",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
|
@ -253,7 +257,7 @@ const expected = r'''
|
|||
{
|
||||
"name": "setting",
|
||||
"getter_name": "settings",
|
||||
"moor_type": "ColumnType.text",
|
||||
"moor_type": "string",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
|
@ -290,7 +294,7 @@ const expected = r'''
|
|||
{
|
||||
"name": "group",
|
||||
"getter_name": "group",
|
||||
"moor_type": "ColumnType.integer",
|
||||
"moor_type": "int",
|
||||
"nullable": false,
|
||||
"customConstraints": "NOT NULL REFERENCES \"groups\"(id)",
|
||||
"default_dart": null,
|
||||
|
@ -302,7 +306,7 @@ const expected = r'''
|
|||
{
|
||||
"name": "user",
|
||||
"getter_name": "user",
|
||||
"moor_type": "ColumnType.integer",
|
||||
"moor_type": "int",
|
||||
"nullable": false,
|
||||
"customConstraints": "NOT NULL REFERENCES users(id)",
|
||||
"default_dart": null,
|
||||
|
@ -314,7 +318,7 @@ const expected = r'''
|
|||
{
|
||||
"name": "is_admin",
|
||||
"getter_name": "isAdmin",
|
||||
"moor_type": "ColumnType.boolean",
|
||||
"moor_type": "bool",
|
||||
"nullable": false,
|
||||
"customConstraints": "NOT NULL DEFAULT FALSE",
|
||||
"default_dart": "const CustomExpression('FALSE')",
|
||||
|
@ -377,7 +381,7 @@ const expected = r'''
|
|||
{
|
||||
"name": "id",
|
||||
"getter_name": "id",
|
||||
"moor_type": "ColumnType.integer",
|
||||
"moor_type": "int",
|
||||
"nullable": false,
|
||||
"customConstraints": null,
|
||||
"default_dart": null,
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:build_test/build_test.dart';
|
|||
import 'package:drift_dev/integrations/build.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:test/test.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
final _resolvers = AnalyzerResolvers();
|
||||
|
@ -13,6 +14,13 @@ BuilderOptions builderOptionsFromYaml(String yaml) {
|
|||
return BuilderOptions((map as YamlMap).cast());
|
||||
}
|
||||
|
||||
Logger loggerThat(dynamic expectedLogs) {
|
||||
final logger = Logger.detached('drift_dev_test');
|
||||
|
||||
expect(logger.onRecord, expectedLogs);
|
||||
return logger;
|
||||
}
|
||||
|
||||
Future<RecordingAssetWriter> emulateDriftBuild({
|
||||
required Map<String, String> inputs,
|
||||
BuilderOptions options = const BuilderOptions({}),
|
||||
|
|
|
@ -34,8 +34,7 @@ abstract class $Database extends i0.GeneratedDatabase {
|
|||
i3.postsDelete,
|
||||
likes,
|
||||
follows,
|
||||
popularUsers,
|
||||
i1.usersName
|
||||
popularUsers
|
||||
];
|
||||
@override
|
||||
i0.StreamQueryUpdateRules get streamUpdateRules =>
|
||||
|
|
|
@ -210,8 +210,6 @@ class Posts extends i0.Table with i0.TableInfo<Posts, i1.Post> {
|
|||
return Posts(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> get customConstraints => const [];
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
@ -389,8 +387,6 @@ class Likes extends i0.Table with i0.TableInfo<Likes, i1.Like> {
|
|||
return Likes(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> get customConstraints => const [];
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
|
|
@ -184,8 +184,6 @@ class SearchInPosts extends i0.Table
|
|||
return SearchInPosts(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> get customConstraints => const [];
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
@override
|
||||
|
|
|
@ -261,8 +261,6 @@ class Users extends i0.Table with i0.TableInfo<Users, i1.User> {
|
|||
$converterpreferencesn =
|
||||
i0.JsonTypeConverter2.asNullable($converterpreferences);
|
||||
@override
|
||||
List<String> get customConstraints => const [];
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
||||
|
|
|
@ -77,8 +77,11 @@ class SchemaFromCreateTable {
|
|||
name: stmt.tableName,
|
||||
resolvedColumns: [
|
||||
for (var def in stmt.columns)
|
||||
_readColumn(def,
|
||||
primaryKeyColumnsInStrictTable: stmt.isStrict ? primaryKey : null)
|
||||
_readColumn(
|
||||
def,
|
||||
isStrict: stmt.isStrict,
|
||||
primaryKeyColumnsInStrictTable: stmt.isStrict ? primaryKey : null,
|
||||
)
|
||||
],
|
||||
withoutRowId: stmt.withoutRowId,
|
||||
isStrict: stmt.isStrict,
|
||||
|
@ -114,8 +117,9 @@ class SchemaFromCreateTable {
|
|||
}
|
||||
|
||||
TableColumn _readColumn(ColumnDefinition definition,
|
||||
{Set<String>? primaryKeyColumnsInStrictTable}) {
|
||||
final type = resolveColumnType(definition.typeName);
|
||||
{required bool isStrict,
|
||||
required Set<String>? primaryKeyColumnsInStrictTable}) {
|
||||
final type = resolveColumnType(definition.typeName, isStrict: isStrict);
|
||||
|
||||
// Column is nullable if it doesn't contain a `NotNull` constraint and it's
|
||||
// not part of a PK in a strict table.
|
||||
|
@ -137,7 +141,7 @@ class SchemaFromCreateTable {
|
|||
/// [IsDateTime] hints if the type name contains `BOOL` or `DATE`,
|
||||
/// respectively.
|
||||
/// https://www.sqlite.org/datatype3.html#determination_of_column_affinity
|
||||
ResolvedType resolveColumnType(String? typeName) {
|
||||
ResolvedType resolveColumnType(String? typeName, {bool isStrict = false}) {
|
||||
if (typeName == null) {
|
||||
return const ResolvedType(type: BasicType.blob);
|
||||
}
|
||||
|
@ -156,6 +160,10 @@ class SchemaFromCreateTable {
|
|||
return const ResolvedType(type: BasicType.blob);
|
||||
}
|
||||
|
||||
if (isStrict && upper == 'ANY') {
|
||||
return const ResolvedType(type: BasicType.any);
|
||||
}
|
||||
|
||||
if (driftExtensions) {
|
||||
if (upper.contains('BOOL')) {
|
||||
return const ResolvedType.bool();
|
||||
|
|
|
@ -10,6 +10,12 @@ enum BasicType {
|
|||
real,
|
||||
text,
|
||||
blob,
|
||||
|
||||
/// A column that has explicitly been defined as `ANY` in a strict table.
|
||||
///
|
||||
/// This is semantically different from a column with an unknown type, which
|
||||
/// is why we don't currently use [any] as a fallback during type inference.
|
||||
any,
|
||||
}
|
||||
|
||||
class ResolvedType {
|
||||
|
|
|
@ -36,12 +36,24 @@ const _affinityTests = {
|
|||
};
|
||||
|
||||
void main() {
|
||||
test('affinity from typename', () {
|
||||
group('column type from SQL', () {
|
||||
const resolver = SchemaFromCreateTable();
|
||||
|
||||
_affinityTests.forEach((key, value) {
|
||||
expect(resolver.columnAffinity(key), equals(value),
|
||||
reason: '$key should have $value affinity');
|
||||
test('affinity', () {
|
||||
_affinityTests.forEach((key, value) {
|
||||
expect(resolver.columnAffinity(key), equals(value),
|
||||
reason: '$key should have $value affinity');
|
||||
});
|
||||
});
|
||||
|
||||
test('any in a non-strict table', () {
|
||||
expect(resolver.resolveColumnType('ANY', isStrict: false).type,
|
||||
BasicType.real);
|
||||
});
|
||||
|
||||
test('any in a strict table', () {
|
||||
expect(resolver.resolveColumnType('ANY', isStrict: true).type,
|
||||
BasicType.any);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -191,6 +203,28 @@ void main() {
|
|||
});
|
||||
});
|
||||
|
||||
test('resolves types in strict tables', () {
|
||||
final engine = SqlEngine(EngineOptions(version: SqliteVersion.v3_37));
|
||||
|
||||
final stmt = engine.parse('''
|
||||
CREATE TABLE foo (
|
||||
a INTEGER PRIMARY KEY,
|
||||
b TEXT,
|
||||
c ANY
|
||||
) STRICT;
|
||||
''').rootNode;
|
||||
|
||||
final table =
|
||||
const SchemaFromCreateTable().read(stmt as CreateTableStatement);
|
||||
|
||||
expect(table.resolvedColumns.map((c) => c.name), ['a', 'b', 'c']);
|
||||
expect(table.resolvedColumns.map((c) => c.type), const [
|
||||
ResolvedType(type: BasicType.int),
|
||||
ResolvedType(type: BasicType.text, nullable: true),
|
||||
ResolvedType(type: BasicType.any, nullable: true),
|
||||
]);
|
||||
});
|
||||
|
||||
group('sets withoutRowid and isStrict', () {
|
||||
final engine = SqlEngine(EngineOptions(version: SqliteVersion.v3_37));
|
||||
|
||||
|
|
Loading…
Reference in New Issue