mirror of https://github.com/AMT-Cheif/drift.git
Support custom types in postgres backend
This commit is contained in:
parent
62bda167bd
commit
207847b02c
|
@ -191,32 +191,35 @@ class UpdateCompanionWriter {
|
|||
for (final column in columns) {
|
||||
final getterName = thisIfNeeded(column.nameInDart, locals);
|
||||
|
||||
_buffer.write('if ($getterName.present) {');
|
||||
_buffer.writeln('if ($getterName.present) {');
|
||||
final typeName =
|
||||
_emitter.dartCode(_emitter.variableTypeCode(column, nullable: false));
|
||||
final mapSetter = 'map[${asDartLiteral(column.nameInSql)}] = '
|
||||
'${_emitter.drift('Variable')}<$typeName>';
|
||||
var value = '$getterName.value';
|
||||
|
||||
final converter = column.typeConverter;
|
||||
if (converter != null) {
|
||||
// apply type converter before writing the variable
|
||||
final fieldName = _emitter.dartCode(
|
||||
_emitter.readConverter(converter, forNullable: column.nullable));
|
||||
_buffer
|
||||
..write('final converter = $fieldName;\n')
|
||||
..write(mapSetter)
|
||||
..write('(converter.toSql($getterName.value)')
|
||||
..write(');');
|
||||
} else {
|
||||
// no type converter. Write variable directly
|
||||
_buffer
|
||||
..write(mapSetter)
|
||||
..write('(')
|
||||
..write('$getterName.value')
|
||||
..write(');');
|
||||
_buffer.writeln('final converter = $fieldName;\n');
|
||||
value = 'converter.toSql($value)';
|
||||
}
|
||||
|
||||
_buffer.write('}');
|
||||
_buffer
|
||||
..write(mapSetter)
|
||||
..write('($value');
|
||||
|
||||
if (column.sqlType.isCustom) {
|
||||
// Also specify the custom type since it can't be inferred from the
|
||||
// value passed to the variable.
|
||||
_buffer
|
||||
..write(', ')
|
||||
..write(_emitter.dartCode(column.sqlType.custom!.expression));
|
||||
}
|
||||
|
||||
_buffer.writeln(');}');
|
||||
}
|
||||
|
||||
_buffer.write('return map; \n}\n');
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:drift_postgres/postgres.dart';
|
||||
import 'package:postgres/postgres_v3_experimental.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
part 'main.g.dart';
|
||||
|
||||
class Users extends Table {
|
||||
UuidColumn get id => customType(PgTypes.uuid).withDefault(genRandomUuid())();
|
||||
TextColumn get name => text()();
|
||||
}
|
||||
|
||||
@DriftDatabase(tables: [Users])
|
||||
class DriftPostgresDatabase extends _$DriftPostgresDatabase {
|
||||
DriftPostgresDatabase(super.e);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
}
|
||||
|
||||
void main() async {
|
||||
final database = DriftPostgresDatabase(PgDatabase(
|
||||
endpoint: PgEndpoint(
|
||||
host: 'localhost',
|
||||
database: 'postgres',
|
||||
username: 'postgres',
|
||||
password: 'postgres',
|
||||
),
|
||||
logStatements: true,
|
||||
));
|
||||
|
||||
final user = await database.users.insertReturning(
|
||||
UsersCompanion.insert(name: 'Simon', id: Value(Uuid().v4obj())));
|
||||
print(user);
|
||||
|
||||
await database.close();
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'main.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
class $UsersTable extends Users with TableInfo<$UsersTable, User> {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
$UsersTable(this.attachedDatabase, [this._alias]);
|
||||
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
@override
|
||||
late final GeneratedColumn<UuidValue> id = GeneratedColumn<UuidValue>(
|
||||
'id', aliasedName, false,
|
||||
type: PgTypes.uuid,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: genRandomUuid());
|
||||
static const VerificationMeta _nameMeta = const VerificationMeta('name');
|
||||
@override
|
||||
late final GeneratedColumn<String> name = GeneratedColumn<String>(
|
||||
'name', aliasedName, false,
|
||||
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [id, name];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'users';
|
||||
@override
|
||||
VerificationContext validateIntegrity(Insertable<User> instance,
|
||||
{bool isInserting = false}) {
|
||||
final context = VerificationContext();
|
||||
final data = instance.toColumns(true);
|
||||
if (data.containsKey('id')) {
|
||||
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
|
||||
}
|
||||
if (data.containsKey('name')) {
|
||||
context.handle(
|
||||
_nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_nameMeta);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => const {};
|
||||
@override
|
||||
User map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return User(
|
||||
id: attachedDatabase.typeMapping
|
||||
.read(PgTypes.uuid, data['${effectivePrefix}id'])!,
|
||||
name: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}name'])!,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
$UsersTable createAlias(String alias) {
|
||||
return $UsersTable(attachedDatabase, alias);
|
||||
}
|
||||
}
|
||||
|
||||
class User extends DataClass implements Insertable<User> {
|
||||
final UuidValue id;
|
||||
final String name;
|
||||
const User({required this.id, required this.name});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
map['id'] = Variable<UuidValue>(id);
|
||||
map['name'] = Variable<String>(name);
|
||||
return map;
|
||||
}
|
||||
|
||||
UsersCompanion toCompanion(bool nullToAbsent) {
|
||||
return UsersCompanion(
|
||||
id: Value(id),
|
||||
name: Value(name),
|
||||
);
|
||||
}
|
||||
|
||||
factory User.fromJson(Map<String, dynamic> json,
|
||||
{ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return User(
|
||||
id: serializer.fromJson<UuidValue>(json['id']),
|
||||
name: serializer.fromJson<String>(json['name']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'id': serializer.toJson<UuidValue>(id),
|
||||
'name': serializer.toJson<String>(name),
|
||||
};
|
||||
}
|
||||
|
||||
User copyWith({UuidValue? id, String? name}) => User(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
);
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('User(')
|
||||
..write('id: $id, ')
|
||||
..write('name: $name')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(id, name);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is User && other.id == this.id && other.name == this.name);
|
||||
}
|
||||
|
||||
class UsersCompanion extends UpdateCompanion<User> {
|
||||
final Value<UuidValue> id;
|
||||
final Value<String> name;
|
||||
final Value<int> rowid;
|
||||
const UsersCompanion({
|
||||
this.id = const Value.absent(),
|
||||
this.name = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
});
|
||||
UsersCompanion.insert({
|
||||
this.id = const Value.absent(),
|
||||
required String name,
|
||||
this.rowid = const Value.absent(),
|
||||
}) : name = Value(name);
|
||||
static Insertable<User> custom({
|
||||
Expression<UuidValue>? id,
|
||||
Expression<String>? name,
|
||||
Expression<int>? rowid,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (id != null) 'id': id,
|
||||
if (name != null) 'name': name,
|
||||
if (rowid != null) 'rowid': rowid,
|
||||
});
|
||||
}
|
||||
|
||||
UsersCompanion copyWith(
|
||||
{Value<UuidValue>? id, Value<String>? name, Value<int>? rowid}) {
|
||||
return UsersCompanion(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
rowid: rowid ?? this.rowid,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
if (id.present) {
|
||||
map['id'] = Variable<UuidValue>(id.value, PgTypes.uuid);
|
||||
}
|
||||
if (name.present) {
|
||||
map['name'] = Variable<String>(name.value);
|
||||
}
|
||||
if (rowid.present) {
|
||||
map['rowid'] = Variable<int>(rowid.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('UsersCompanion(')
|
||||
..write('id: $id, ')
|
||||
..write('name: $name, ')
|
||||
..write('rowid: $rowid')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$DriftPostgresDatabase extends GeneratedDatabase {
|
||||
_$DriftPostgresDatabase(QueryExecutor e) : super(e);
|
||||
late final $UsersTable users = $UsersTable(this);
|
||||
@override
|
||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||
@override
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities => [users];
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
import 'package:drift/backends.dart';
|
||||
import 'package:drift/src/runtime/query_builder/query_builder.dart';
|
||||
import 'package:drift_postgres/postgres.dart';
|
||||
import 'package:postgres/postgres_v3_experimental.dart';
|
||||
|
||||
void main() async {
|
||||
final postgres = PgDatabase(
|
||||
endpoint: PgEndpoint(
|
||||
host: 'localhost',
|
||||
database: 'postgres',
|
||||
username: 'postgres',
|
||||
password: 'postgres',
|
||||
),
|
||||
logStatements: true,
|
||||
);
|
||||
|
||||
await postgres.ensureOpen(_NullUser());
|
||||
|
||||
final rows = await postgres.runSelect(r'SELECT $1', [true]);
|
||||
final row = rows.single;
|
||||
print(row);
|
||||
print(row.values.map((e) => e.runtimeType).toList());
|
||||
}
|
||||
|
||||
class _NullUser extends QueryExecutorUser {
|
||||
@override
|
||||
Future<void> beforeOpen(
|
||||
QueryExecutor executor, OpeningDetails details) async {}
|
||||
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
}
|
|
@ -2,6 +2,23 @@
|
|||
@experimental
|
||||
library drift.postgres;
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import 'src/types.dart';
|
||||
|
||||
export 'src/pg_database.dart';
|
||||
|
||||
typedef UuidColumn = Column<UuidValue>;
|
||||
|
||||
final class PgTypes {
|
||||
PgTypes._();
|
||||
|
||||
static const CustomSqlType<UuidValue> uuid = UuidType();
|
||||
}
|
||||
|
||||
/// Calls the `gen_random_uuid` function in postgres.
|
||||
Expression<UuidValue> genRandomUuid() {
|
||||
return FunctionCallExpression('gen_random_uuid', []);
|
||||
}
|
||||
|
|
|
@ -152,25 +152,16 @@ class _BoundArguments {
|
|||
}
|
||||
|
||||
for (final value in args) {
|
||||
if (value == null) {
|
||||
add(PgTypedParameter(PgDataType.text, null));
|
||||
} else if (value is int) {
|
||||
add(PgTypedParameter(PgDataType.bigInteger, value));
|
||||
} else if (value is BigInt) {
|
||||
// Drift only uses BigInts to represent 64-bit values on the web, so we
|
||||
// can use toInt() here.
|
||||
add(PgTypedParameter(PgDataType.bigInteger, value));
|
||||
} else if (value is bool) {
|
||||
add(PgTypedParameter(PgDataType.boolean, value));
|
||||
} else if (value is double) {
|
||||
add(PgTypedParameter(PgDataType.double, value));
|
||||
} else if (value is String) {
|
||||
add(PgTypedParameter(PgDataType.text, value));
|
||||
} else if (value is List<int>) {
|
||||
add(PgTypedParameter(PgDataType.byteArray, value));
|
||||
} else {
|
||||
throw ArgumentError.value(value, 'value', 'Unsupported type');
|
||||
}
|
||||
add(switch (value) {
|
||||
PgTypedParameter() => value,
|
||||
null => PgTypedParameter(PgDataType.text, null),
|
||||
int() || BigInt() => PgTypedParameter(PgDataType.bigInteger, value),
|
||||
String() => PgTypedParameter(PgDataType.text, value),
|
||||
bool() => PgTypedParameter(PgDataType.boolean, value),
|
||||
double() => PgTypedParameter(PgDataType.double, value),
|
||||
List<int>() => PgTypedParameter(PgDataType.byteArray, value),
|
||||
_ => throw ArgumentError.value(value, 'value', 'Unsupported type'),
|
||||
});
|
||||
}
|
||||
|
||||
return _BoundArguments(types, parameters);
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:postgres/postgres_v3_experimental.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class UuidType implements CustomSqlType<UuidValue> {
|
||||
const UuidType();
|
||||
|
||||
@override
|
||||
String mapToSqlLiteral(UuidValue dartValue) {
|
||||
// UUIDs can't contain escape characters, so we don't check these values.
|
||||
return "'${dartValue.uuid}'";
|
||||
}
|
||||
|
||||
@override
|
||||
Object mapToSqlParameter(UuidValue dartValue) {
|
||||
return PgTypedParameter(PgDataType.uuid, dartValue.uuid);
|
||||
}
|
||||
|
||||
@override
|
||||
UuidValue read(Object fromSql) {
|
||||
return UuidValue(fromSql as String);
|
||||
}
|
||||
|
||||
@override
|
||||
String sqlTypeName(GenerationContext context) {
|
||||
return 'uuid';
|
||||
}
|
||||
}
|
|
@ -3,19 +3,22 @@ description: Postgres support for drift
|
|||
version: 1.0.0
|
||||
|
||||
environment:
|
||||
sdk: '>=2.12.0-0 <4.0.0'
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
collection: ^1.16.0
|
||||
drift: ^2.0.0
|
||||
postgres:
|
||||
meta: ^1.8.0
|
||||
uuid: ^4.1.0
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^2.0.0
|
||||
test: ^1.18.0
|
||||
drift_dev:
|
||||
drift_testcases:
|
||||
path: ../integration_tests/drift_testcases
|
||||
build_runner: ^2.4.6
|
||||
|
||||
dependency_overrides:
|
||||
drift:
|
||||
|
|
Loading…
Reference in New Issue