mirror of https://github.com/AMT-Cheif/drift.git
Merge branch 'use-sqlite3-package' into develop
This commit is contained in:
commit
ba708ee9c5
|
@ -1,7 +1,7 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:moor/ffi.dart';
|
||||||
import 'package:moor/moor.dart';
|
import 'package:moor/moor.dart';
|
||||||
import 'package:moor_ffi/moor_ffi.dart';
|
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,26 @@ class KeyValue extends DataClass implements Insertable<KeyValue> {
|
||||||
stringType.mapFromDatabaseResponse(data['${effectivePrefix}value']),
|
stringType.mapFromDatabaseResponse(data['${effectivePrefix}value']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@override
|
||||||
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, Expression>{};
|
||||||
|
if (!nullToAbsent || key != null) {
|
||||||
|
map['key'] = Variable<String>(key);
|
||||||
|
}
|
||||||
|
if (!nullToAbsent || value != null) {
|
||||||
|
map['value'] = Variable<String>(value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyValuesCompanion toCompanion(bool nullToAbsent) {
|
||||||
|
return KeyValuesCompanion(
|
||||||
|
key: key == null && nullToAbsent ? const Value.absent() : Value(key),
|
||||||
|
value:
|
||||||
|
value == null && nullToAbsent ? const Value.absent() : Value(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
factory KeyValue.fromJson(Map<String, dynamic> json,
|
factory KeyValue.fromJson(Map<String, dynamic> json,
|
||||||
{ValueSerializer serializer}) {
|
{ValueSerializer serializer}) {
|
||||||
serializer ??= moorRuntimeOptions.defaultSerializer;
|
serializer ??= moorRuntimeOptions.defaultSerializer;
|
||||||
|
@ -38,15 +58,6 @@ class KeyValue extends DataClass implements Insertable<KeyValue> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
KeyValuesCompanion createCompanion(bool nullToAbsent) {
|
|
||||||
return KeyValuesCompanion(
|
|
||||||
key: key == null && nullToAbsent ? const Value.absent() : Value(key),
|
|
||||||
value:
|
|
||||||
value == null && nullToAbsent ? const Value.absent() : Value(value),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyValue copyWith({String key, String value}) => KeyValue(
|
KeyValue copyWith({String key, String value}) => KeyValue(
|
||||||
key: key ?? this.key,
|
key: key ?? this.key,
|
||||||
value: value ?? this.value,
|
value: value ?? this.value,
|
||||||
|
@ -80,12 +91,43 @@ class KeyValuesCompanion extends UpdateCompanion<KeyValue> {
|
||||||
@required String value,
|
@required String value,
|
||||||
}) : key = Value(key),
|
}) : key = Value(key),
|
||||||
value = Value(value);
|
value = Value(value);
|
||||||
|
static Insertable<KeyValue> custom({
|
||||||
|
Expression<String> key,
|
||||||
|
Expression<String> value,
|
||||||
|
}) {
|
||||||
|
return RawValuesInsertable({
|
||||||
|
if (key != null) 'key': key,
|
||||||
|
if (value != null) 'value': value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
KeyValuesCompanion copyWith({Value<String> key, Value<String> value}) {
|
KeyValuesCompanion copyWith({Value<String> key, Value<String> value}) {
|
||||||
return KeyValuesCompanion(
|
return KeyValuesCompanion(
|
||||||
key: key ?? this.key,
|
key: key ?? this.key,
|
||||||
value: value ?? this.value,
|
value: value ?? this.value,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, Expression>{};
|
||||||
|
if (key.present) {
|
||||||
|
map['key'] = Variable<String>(key.value);
|
||||||
|
}
|
||||||
|
if (value.present) {
|
||||||
|
map['value'] = Variable<String>(value.value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('KeyValuesCompanion(')
|
||||||
|
..write('key: $key, ')
|
||||||
|
..write('value: $value')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class $KeyValuesTable extends KeyValues
|
class $KeyValuesTable extends KeyValues
|
||||||
|
@ -126,17 +168,19 @@ class $KeyValuesTable extends KeyValues
|
||||||
@override
|
@override
|
||||||
final String actualTableName = 'key_values';
|
final String actualTableName = 'key_values';
|
||||||
@override
|
@override
|
||||||
VerificationContext validateIntegrity(KeyValuesCompanion d,
|
VerificationContext validateIntegrity(Insertable<KeyValue> instance,
|
||||||
{bool isInserting = false}) {
|
{bool isInserting = false}) {
|
||||||
final context = VerificationContext();
|
final context = VerificationContext();
|
||||||
if (d.key.present) {
|
final data = instance.toColumns(true);
|
||||||
context.handle(_keyMeta, key.isAcceptableValue(d.key.value, _keyMeta));
|
if (data.containsKey('key')) {
|
||||||
|
context.handle(
|
||||||
|
_keyMeta, key.isAcceptableOrUnknown(data['key'], _keyMeta));
|
||||||
} else if (isInserting) {
|
} else if (isInserting) {
|
||||||
context.missing(_keyMeta);
|
context.missing(_keyMeta);
|
||||||
}
|
}
|
||||||
if (d.value.present) {
|
if (data.containsKey('value')) {
|
||||||
context.handle(
|
context.handle(
|
||||||
_valueMeta, value.isAcceptableValue(d.value.value, _valueMeta));
|
_valueMeta, value.isAcceptableOrUnknown(data['value'], _valueMeta));
|
||||||
} else if (isInserting) {
|
} else if (isInserting) {
|
||||||
context.missing(_valueMeta);
|
context.missing(_valueMeta);
|
||||||
}
|
}
|
||||||
|
@ -151,18 +195,6 @@ class $KeyValuesTable extends KeyValues
|
||||||
return KeyValue.fromData(data, _db, prefix: effectivePrefix);
|
return KeyValue.fromData(data, _db, prefix: effectivePrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, Variable> entityToSql(KeyValuesCompanion d) {
|
|
||||||
final map = <String, Variable>{};
|
|
||||||
if (d.key.present) {
|
|
||||||
map['key'] = Variable<String>(d.key.value);
|
|
||||||
}
|
|
||||||
if (d.value.present) {
|
|
||||||
map['value'] = Variable<String>(d.value.value);
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
$KeyValuesTable createAlias(String alias) {
|
$KeyValuesTable createAlias(String alias) {
|
||||||
return $KeyValuesTable(_db, alias);
|
return $KeyValuesTable(_db, alias);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:benchmarks/benchmarks.dart';
|
import 'package:benchmarks/benchmarks.dart';
|
||||||
import 'package:moor_ffi/database.dart';
|
import 'package:sqlite3/sqlite3.dart';
|
||||||
|
|
||||||
class SelectStringBenchmark extends BenchmarkBase {
|
class SelectStringBenchmark extends BenchmarkBase {
|
||||||
SelectStringBenchmark(ScoreEmitter emitter)
|
SelectStringBenchmark(ScoreEmitter emitter)
|
||||||
|
@ -10,7 +10,7 @@ class SelectStringBenchmark extends BenchmarkBase {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void setup() {
|
void setup() {
|
||||||
database = Database.memory();
|
database = sqlite3.openInMemory();
|
||||||
statement = database.prepare('SELECT ?;');
|
statement = database.prepare('SELECT ?;');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ class SelectStringBenchmark extends BenchmarkBase {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void teardown() {
|
void teardown() {
|
||||||
statement.close();
|
statement.dispose();
|
||||||
database.close();
|
database.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ description: Runs simple and complex benchmarks to measure performance of moor a
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
moor:
|
moor:
|
||||||
moor_ffi:
|
sqlite3: ^0.1.3
|
||||||
benchmark_harness: ^1.0.5
|
benchmark_harness: ^1.0.5
|
||||||
intl: ^0.16.0
|
intl: ^0.16.0
|
||||||
uuid: ^2.0.0
|
uuid: ^2.0.0
|
||||||
|
@ -16,8 +16,6 @@ dev_dependencies:
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
moor:
|
moor:
|
||||||
path: ../../moor
|
path: ../../moor
|
||||||
moor_ffi:
|
|
||||||
path: ../../moor_ffi
|
|
||||||
moor_generator:
|
moor_generator:
|
||||||
path: ../../moor_generator
|
path: ../../moor_generator
|
||||||
sqlparser:
|
sqlparser:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:moor_ffi/moor_ffi.dart';
|
import 'package:moor/ffi.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'package:tests/database/database.dart';
|
import 'package:tests/database/database.dart';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:moor_ffi/moor_ffi.dart';
|
import 'package:moor/ffi.dart';
|
||||||
import 'package:tests/tests.dart';
|
import 'package:tests/tests.dart';
|
||||||
|
|
||||||
import 'package:path/path.dart' show join;
|
import 'package:path/path.dart' show join;
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
/// Moor implementation using `package:sqlite3/`.
|
||||||
|
library moor.ffi;
|
||||||
|
|
||||||
|
export 'src/ffi/vm_database.dart';
|
|
@ -0,0 +1,197 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:sqlite3/sqlite3.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: avoid_returning_null, only_throw_errors
|
||||||
|
|
||||||
|
/// Extension to register moor-specific sql functions.
|
||||||
|
extension EnableMoorFunctions on Database {
|
||||||
|
/// Enables moor-specific sql functions on this database.
|
||||||
|
void useMoorVersions() {
|
||||||
|
createFunction(
|
||||||
|
functionName: 'power',
|
||||||
|
deterministic: true,
|
||||||
|
argumentCount: const AllowedArgumentCount(2),
|
||||||
|
function: _pow,
|
||||||
|
);
|
||||||
|
createFunction(
|
||||||
|
functionName: 'pow',
|
||||||
|
deterministic: true,
|
||||||
|
argumentCount: const AllowedArgumentCount(2),
|
||||||
|
function: _pow,
|
||||||
|
);
|
||||||
|
|
||||||
|
createFunction(
|
||||||
|
functionName: 'sqrt',
|
||||||
|
deterministic: true,
|
||||||
|
argumentCount: const AllowedArgumentCount(1),
|
||||||
|
function: _unaryNumFunction(sqrt),
|
||||||
|
);
|
||||||
|
createFunction(
|
||||||
|
functionName: 'sin',
|
||||||
|
deterministic: true,
|
||||||
|
argumentCount: const AllowedArgumentCount(1),
|
||||||
|
function: _unaryNumFunction(sin),
|
||||||
|
);
|
||||||
|
createFunction(
|
||||||
|
functionName: 'cos',
|
||||||
|
deterministic: true,
|
||||||
|
argumentCount: const AllowedArgumentCount(1),
|
||||||
|
function: _unaryNumFunction(cos),
|
||||||
|
);
|
||||||
|
createFunction(
|
||||||
|
functionName: 'tan',
|
||||||
|
deterministic: true,
|
||||||
|
argumentCount: const AllowedArgumentCount(1),
|
||||||
|
function: _unaryNumFunction(tan),
|
||||||
|
);
|
||||||
|
createFunction(
|
||||||
|
functionName: 'asin',
|
||||||
|
deterministic: true,
|
||||||
|
argumentCount: const AllowedArgumentCount(1),
|
||||||
|
function: _unaryNumFunction(asin),
|
||||||
|
);
|
||||||
|
createFunction(
|
||||||
|
functionName: 'acos',
|
||||||
|
deterministic: true,
|
||||||
|
argumentCount: const AllowedArgumentCount(1),
|
||||||
|
function: _unaryNumFunction(acos),
|
||||||
|
);
|
||||||
|
createFunction(
|
||||||
|
functionName: 'atan',
|
||||||
|
deterministic: true,
|
||||||
|
argumentCount: const AllowedArgumentCount(1),
|
||||||
|
function: _unaryNumFunction(atan),
|
||||||
|
);
|
||||||
|
|
||||||
|
createFunction(
|
||||||
|
functionName: 'regexp',
|
||||||
|
deterministic: true,
|
||||||
|
argumentCount: const AllowedArgumentCount(2),
|
||||||
|
function: _regexpImpl,
|
||||||
|
);
|
||||||
|
// Third argument can be used to set flags (like multiline, case
|
||||||
|
// sensitivity, etc.)
|
||||||
|
createFunction(
|
||||||
|
functionName: 'regexp_moor_ffi',
|
||||||
|
deterministic: true,
|
||||||
|
argumentCount: const AllowedArgumentCount(3),
|
||||||
|
function: _regexpImpl,
|
||||||
|
);
|
||||||
|
|
||||||
|
createFunction(
|
||||||
|
functionName: 'moor_contains',
|
||||||
|
deterministic: true,
|
||||||
|
argumentCount: const AllowedArgumentCount(2),
|
||||||
|
function: _containsImpl,
|
||||||
|
);
|
||||||
|
createFunction(
|
||||||
|
functionName: 'moor_contains',
|
||||||
|
deterministic: true,
|
||||||
|
argumentCount: const AllowedArgumentCount(3),
|
||||||
|
function: _containsImpl,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
num _pow(List<dynamic> args) {
|
||||||
|
final first = args[0];
|
||||||
|
final second = args[1];
|
||||||
|
|
||||||
|
if (first == null || second == null || first is! num || second is! num) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pow(first as num, second as num);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Base implementation for a sqlite function that takes one numerical argument
|
||||||
|
/// and returns one numerical argument.
|
||||||
|
///
|
||||||
|
/// When not called with a number, returns will null. Otherwise, returns with
|
||||||
|
/// [calculation].
|
||||||
|
num Function(List<dynamic>) _unaryNumFunction(num Function(num) calculation) {
|
||||||
|
return (List<dynamic> args) {
|
||||||
|
// sqlite will ensure that this is only called with one argument
|
||||||
|
final value = args[0];
|
||||||
|
if (value is num) {
|
||||||
|
return calculation(value);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _regexpImpl(List<dynamic> args) {
|
||||||
|
var multiLine = false;
|
||||||
|
var caseSensitive = true;
|
||||||
|
var unicode = false;
|
||||||
|
var dotAll = false;
|
||||||
|
|
||||||
|
final argCount = args.length;
|
||||||
|
if (argCount < 2 || argCount > 3) {
|
||||||
|
throw 'Expected two or three arguments to regexp';
|
||||||
|
}
|
||||||
|
|
||||||
|
final firstParam = args[0];
|
||||||
|
final secondParam = args[1];
|
||||||
|
|
||||||
|
if (firstParam == null || secondParam == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (firstParam is! String || secondParam is! String) {
|
||||||
|
throw 'Expected two strings as parameters to regexp';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argCount == 3) {
|
||||||
|
// In the variant with three arguments, the last (int) arg can be used to
|
||||||
|
// enable regex flags. See the regexp() extension in moor for details.
|
||||||
|
final value = args[2];
|
||||||
|
if (value is int) {
|
||||||
|
multiLine = (value & 1) == 1;
|
||||||
|
caseSensitive = (value & 2) != 2;
|
||||||
|
unicode = (value & 4) == 4;
|
||||||
|
dotAll = (value & 8) == 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RegExp regex;
|
||||||
|
try {
|
||||||
|
regex = RegExp(
|
||||||
|
firstParam as String,
|
||||||
|
multiLine: multiLine,
|
||||||
|
caseSensitive: caseSensitive,
|
||||||
|
unicode: unicode,
|
||||||
|
dotAll: dotAll,
|
||||||
|
);
|
||||||
|
} on FormatException {
|
||||||
|
throw 'Invalid regex';
|
||||||
|
}
|
||||||
|
|
||||||
|
return regex.hasMatch(secondParam as String);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _containsImpl(List<dynamic> args) {
|
||||||
|
final argCount = args.length;
|
||||||
|
if (argCount < 2 || argCount > 3) {
|
||||||
|
throw 'Expected 2 or 3 arguments to moor_contains';
|
||||||
|
}
|
||||||
|
|
||||||
|
final first = args[0];
|
||||||
|
final second = args[1];
|
||||||
|
|
||||||
|
if (first is! String || second is! String) {
|
||||||
|
throw 'First two args to contains must be strings';
|
||||||
|
}
|
||||||
|
|
||||||
|
final caseSensitive = argCount == 3 && args[2] == 1;
|
||||||
|
|
||||||
|
final firstAsString = first as String;
|
||||||
|
final secondAsString = second as String;
|
||||||
|
|
||||||
|
final result = caseSensitive
|
||||||
|
? firstAsString.contains(secondAsString)
|
||||||
|
: firstAsString.toLowerCase().contains(secondAsString.toLowerCase());
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:moor/backends.dart';
|
||||||
|
import 'package:sqlite3/sqlite3.dart';
|
||||||
|
|
||||||
|
import 'moor_ffi_functions.dart';
|
||||||
|
|
||||||
|
/// A moor database that runs on the Dart VM.
|
||||||
|
class VmDatabase extends DelegatedDatabase {
|
||||||
|
VmDatabase._(DatabaseDelegate delegate, bool logStatements)
|
||||||
|
: super(delegate, isSequential: true, logStatements: logStatements);
|
||||||
|
|
||||||
|
/// Creates a database that will store its result in the [file], creating it
|
||||||
|
/// if it doesn't exist.
|
||||||
|
factory VmDatabase(File file, {bool logStatements = false}) {
|
||||||
|
return VmDatabase._(_VmDelegate(file), logStatements);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an in-memory database won't persist its changes on disk.
|
||||||
|
factory VmDatabase.memory({bool logStatements = false}) {
|
||||||
|
return VmDatabase._(_VmDelegate(null), logStatements);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VmDelegate extends DatabaseDelegate {
|
||||||
|
Database _db;
|
||||||
|
|
||||||
|
final File file;
|
||||||
|
|
||||||
|
_VmDelegate(this.file);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final TransactionDelegate transactionDelegate = const NoTransactionDelegate();
|
||||||
|
|
||||||
|
@override
|
||||||
|
DbVersionDelegate versionDelegate;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> get isOpen => Future.value(_db != null);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> open(QueryExecutorUser user) async {
|
||||||
|
if (file != null) {
|
||||||
|
_db = sqlite3.open(file.path);
|
||||||
|
} else {
|
||||||
|
_db = sqlite3.openInMemory();
|
||||||
|
}
|
||||||
|
_db.useMoorVersions();
|
||||||
|
versionDelegate = _VmVersionDelegate(_db);
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> runBatched(BatchedStatements statements) async {
|
||||||
|
final prepared = [
|
||||||
|
for (final stmt in statements.statements) _db.prepare(stmt),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (final application in statements.arguments) {
|
||||||
|
final stmt = prepared[application.statementIndex];
|
||||||
|
|
||||||
|
stmt.execute(application.arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final stmt in prepared) {
|
||||||
|
stmt.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _runWithArgs(String statement, List<dynamic> args) async {
|
||||||
|
if (args.isEmpty) {
|
||||||
|
_db.execute(statement);
|
||||||
|
} else {
|
||||||
|
final stmt = _db.prepare(statement);
|
||||||
|
stmt.execute(args);
|
||||||
|
stmt.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> runCustom(String statement, List args) async {
|
||||||
|
await _runWithArgs(statement, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> runInsert(String statement, List args) async {
|
||||||
|
await _runWithArgs(statement, args);
|
||||||
|
return _db.lastInsertRowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> runUpdate(String statement, List args) async {
|
||||||
|
await _runWithArgs(statement, args);
|
||||||
|
return _db.getUpdatedRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<QueryResult> runSelect(String statement, List args) async {
|
||||||
|
final stmt = _db.prepare(statement);
|
||||||
|
final result = stmt.select(args);
|
||||||
|
stmt.dispose();
|
||||||
|
|
||||||
|
return Future.value(QueryResult.fromRows(result.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async {
|
||||||
|
_db.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VmVersionDelegate extends DynamicVersionDelegate {
|
||||||
|
final Database database;
|
||||||
|
|
||||||
|
_VmVersionDelegate(this.database);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> get schemaVersion => Future.value(database.userVersion);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setSchemaVersion(int version) {
|
||||||
|
database.userVersion = version;
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,12 +14,11 @@ dependencies:
|
||||||
collection: ^1.0.0
|
collection: ^1.0.0
|
||||||
synchronized: ^2.1.0
|
synchronized: ^2.1.0
|
||||||
pedantic: ^1.0.0
|
pedantic: ^1.0.0
|
||||||
|
sqlite3: ^0.1.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
moor_generator: ^3.2.0
|
moor_generator: ^3.2.0
|
||||||
uuid: ^2.0.0
|
uuid: ^2.0.0
|
||||||
moor_ffi: # Used to run integration tests
|
|
||||||
path: ../moor_ffi
|
|
||||||
path: ^1.6.4
|
path: ^1.6.4
|
||||||
build_runner: '>=1.3.0 <2.0.0'
|
build_runner: '>=1.3.0 <2.0.0'
|
||||||
test: ^1.9.0
|
test: ^1.9.0
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
@TestOn('vm')
|
@TestOn('vm')
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:moor/ffi.dart';
|
||||||
import 'package:moor/isolate.dart';
|
import 'package:moor/isolate.dart';
|
||||||
import 'package:moor/moor.dart';
|
import 'package:moor/moor.dart';
|
||||||
import 'package:moor_ffi/moor_ffi.dart';
|
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import 'package:path/path.dart' show join;
|
import 'package:path/path.dart' show join;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:moor/moor.dart';
|
|
||||||
@TestOn('vm')
|
@TestOn('vm')
|
||||||
|
import 'package:moor/ffi.dart';
|
||||||
|
import 'package:moor/moor.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'package:moor_ffi/moor_ffi.dart';
|
|
||||||
|
|
||||||
import '../data/tables/todos.dart';
|
import '../data/tables/todos.dart';
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
@Tags(['integration'])
|
@Tags(['integration'])
|
||||||
@TestOn('vm')
|
@TestOn('vm')
|
||||||
import 'package:moor_ffi/moor_ffi.dart';
|
import 'package:moor/ffi.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import '../data/tables/custom_tables.dart';
|
import '../data/tables/custom_tables.dart';
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
@TestOn('vm')
|
@TestOn('vm')
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:moor/ffi.dart';
|
||||||
import 'package:moor/moor.dart';
|
import 'package:moor/moor.dart';
|
||||||
import 'package:moor/extensions/json1.dart';
|
import 'package:moor/extensions/json1.dart';
|
||||||
import 'package:moor_ffi/moor_ffi.dart';
|
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import '../data/tables/todos.dart';
|
import '../data/tables/todos.dart';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
@TestOn('vm')
|
@TestOn('vm')
|
||||||
import 'package:moor/moor.dart';
|
import 'package:moor/moor.dart';
|
||||||
import 'package:moor/extensions/moor_ffi.dart';
|
import 'package:moor/extensions/moor_ffi.dart';
|
||||||
import 'package:moor_ffi/moor_ffi.dart';
|
import 'package:moor/ffi.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import '../data/tables/todos.dart';
|
import '../data/tables/todos.dart';
|
||||||
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:moor/src/ffi/moor_ffi_functions.dart';
|
||||||
|
import 'package:sqlite3/sqlite3.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
Database db;
|
||||||
|
|
||||||
|
setUp(() => db = sqlite3.openInMemory()..useMoorVersions());
|
||||||
|
tearDown(() => db.dispose());
|
||||||
|
|
||||||
|
dynamic selectSingle(String expression) {
|
||||||
|
final stmt = db.prepare('SELECT $expression AS r;');
|
||||||
|
final rows = stmt.select();
|
||||||
|
stmt.dispose();
|
||||||
|
|
||||||
|
return rows.single['r'];
|
||||||
|
}
|
||||||
|
|
||||||
|
group('pow', () {
|
||||||
|
dynamic _resultOfPow(String a, String b) {
|
||||||
|
return selectSingle('pow($a, $b)');
|
||||||
|
}
|
||||||
|
|
||||||
|
test('returns null when any argument is null', () {
|
||||||
|
expect(_resultOfPow('null', 'null'), isNull);
|
||||||
|
expect(_resultOfPow('3', 'null'), isNull);
|
||||||
|
expect(_resultOfPow('null', '3'), isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns correct results', () {
|
||||||
|
expect(_resultOfPow('10', '0'), 1);
|
||||||
|
expect(_resultOfPow('0', '10'), 0);
|
||||||
|
expect(_resultOfPow('0', '0'), 1);
|
||||||
|
expect(_resultOfPow('2', '5'), 32);
|
||||||
|
expect(_resultOfPow('3.5', '2'), 12.25);
|
||||||
|
expect(_resultOfPow('10', '-1'), 0.1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
for (final scenario in _testCases) {
|
||||||
|
final function = scenario.sqlFunction;
|
||||||
|
|
||||||
|
test(function, () {
|
||||||
|
final stmt = db.prepare('SELECT $function(?) AS r');
|
||||||
|
|
||||||
|
for (final input in scenario.inputs) {
|
||||||
|
final sqlResult = stmt.select([input]).single['r'];
|
||||||
|
final dartResult = scenario.dartEquivalent(input);
|
||||||
|
|
||||||
|
// NaN in sqlite is null, account for that
|
||||||
|
if (dartResult.isNaN) {
|
||||||
|
expect(
|
||||||
|
sqlResult,
|
||||||
|
null,
|
||||||
|
reason: '$function($input) = $dartResult',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
expect(
|
||||||
|
sqlResult,
|
||||||
|
equals(dartResult),
|
||||||
|
reason: '$function($input) = $dartResult',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final resultWithNull = stmt.select([null]);
|
||||||
|
expect(resultWithNull.single['r'], isNull);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group('regexp', () {
|
||||||
|
test('cannot be called with more or fewer than 2 parameters', () {
|
||||||
|
expect(() => db.execute("SELECT regexp('foo')"),
|
||||||
|
throwsA(isA<SqliteException>()));
|
||||||
|
|
||||||
|
expect(() => db.execute("SELECT regexp('foo', 'bar', 'baz')"),
|
||||||
|
throwsA(isA<SqliteException>()));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('results in error when not passing a string', () {
|
||||||
|
final complainsAboutTypes = throwsA(isA<SqliteException>().having(
|
||||||
|
(e) => e.message,
|
||||||
|
'message',
|
||||||
|
contains('Expected two strings as parameters to regexp'),
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(() => db.execute("SELECT 'foo' REGEXP 3"), complainsAboutTypes);
|
||||||
|
expect(() => db.execute("SELECT 3 REGEXP 'foo'"), complainsAboutTypes);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails on invalid regex', () {
|
||||||
|
expect(
|
||||||
|
() => db.execute("SELECT 'foo' REGEXP '('"),
|
||||||
|
throwsA(isA<SqliteException>()
|
||||||
|
.having((e) => e.message, 'message', contains('Invalid regex'))),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns true on a match', () {
|
||||||
|
final stmt = db.prepare("SELECT 'foo' REGEXP 'fo+' AS r");
|
||||||
|
final result = stmt.select();
|
||||||
|
expect(result.single['r'], 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns false when the regex doesn't match", () {
|
||||||
|
final stmt = db.prepare("SELECT 'bar' REGEXP 'fo+' AS r");
|
||||||
|
final result = stmt.select();
|
||||||
|
expect(result.single['r'], 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports flags', () {
|
||||||
|
final stmt =
|
||||||
|
db.prepare(r"SELECT regexp_moor_ffi('^bar', 'foo\nbar', 8) AS r;");
|
||||||
|
final result = stmt.select();
|
||||||
|
expect(result.single['r'], 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns null when either argument is null', () {
|
||||||
|
final stmt = db.prepare('SELECT ? REGEXP ?');
|
||||||
|
|
||||||
|
expect(stmt.select(['foo', null]).single.columnAt(0), isNull);
|
||||||
|
expect(stmt.select([null, 'foo']).single.columnAt(0), isNull);
|
||||||
|
|
||||||
|
stmt.dispose();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('moor_contains', () {
|
||||||
|
test('checks for type errors', () {
|
||||||
|
expect(() => db.execute('SELECT moor_contains(12, 1);'),
|
||||||
|
throwsA(isA<SqliteException>()));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('case insensitive without parameter', () {
|
||||||
|
expect(selectSingle("moor_contains('foo', 'O')"), 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('case insensitive with parameter', () {
|
||||||
|
expect(selectSingle("moor_contains('foo', 'O', 0)"), 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('case sensitive', () {
|
||||||
|
expect(selectSingle("moor_contains('Hello', 'hell', 1)"), 0);
|
||||||
|
expect(selectSingle("moor_contains('hi', 'i', 1)"), 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// utils to verify the sql functions behave exactly like the ones from the VM
|
||||||
|
|
||||||
|
class _UnaryFunctionTestCase {
|
||||||
|
final String sqlFunction;
|
||||||
|
final num Function(num) dartEquivalent;
|
||||||
|
final List<num> inputs;
|
||||||
|
|
||||||
|
const _UnaryFunctionTestCase(
|
||||||
|
this.sqlFunction, this.dartEquivalent, this.inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
const _unaryInputs = [
|
||||||
|
pi,
|
||||||
|
0,
|
||||||
|
pi / 2,
|
||||||
|
e,
|
||||||
|
123,
|
||||||
|
];
|
||||||
|
|
||||||
|
const _testCases = <_UnaryFunctionTestCase>[
|
||||||
|
_UnaryFunctionTestCase('sin', sin, _unaryInputs),
|
||||||
|
_UnaryFunctionTestCase('cos', cos, _unaryInputs),
|
||||||
|
_UnaryFunctionTestCase('tan', tan, _unaryInputs),
|
||||||
|
_UnaryFunctionTestCase('sqrt', sqrt, _unaryInputs),
|
||||||
|
_UnaryFunctionTestCase('asin', asin, _unaryInputs),
|
||||||
|
_UnaryFunctionTestCase('acos', acos, _unaryInputs),
|
||||||
|
_UnaryFunctionTestCase('atan', atan, _unaryInputs),
|
||||||
|
];
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:moor/moor.dart';
|
import 'package:moor/moor.dart';
|
||||||
import 'package:moor_ffi/moor_ffi.dart';
|
import 'package:moor/ffi.dart';
|
||||||
@TestOn('vm')
|
@TestOn('vm')
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'dart:isolate';
|
||||||
|
|
||||||
import 'package:moor/isolate.dart';
|
import 'package:moor/isolate.dart';
|
||||||
import 'package:moor/moor.dart';
|
import 'package:moor/moor.dart';
|
||||||
import 'package:moor_ffi/moor_ffi.dart';
|
import 'package:moor/ffi.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import 'data/tables/todos.dart';
|
import 'data/tables/todos.dart';
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/// Exports the low-level [Database] class to run operations on a sqlite
|
/// Exports the low-level [Database] class to run operations on a sqlite
|
||||||
/// database via `dart:ffi`.
|
/// database via `dart:ffi`.
|
||||||
|
@Deprecated('Consider migrating to package:sqlite3/sqlite3.dart')
|
||||||
library database;
|
library database;
|
||||||
|
|
||||||
import 'package:moor_ffi/src/bindings/types.dart';
|
import 'package:moor_ffi/src/bindings/types.dart';
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
@Deprecated('Use package:moor/ffi.dart instead')
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:moor/backends.dart';
|
import 'package:moor/backends.dart';
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/// Utils to open a [DynamicLibrary] on platforms that aren't supported by
|
/// Utils to open a [DynamicLibrary] on platforms that aren't supported by
|
||||||
/// `moor_ffi` by default.
|
/// `moor_ffi` by default.
|
||||||
|
@Deprecated('Consider migrating to package:sqlite3/open.dart')
|
||||||
library open_helper;
|
library open_helper;
|
||||||
|
|
||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
|
|
|
@ -18,4 +18,4 @@ dev_dependencies:
|
||||||
test: ^1.9.4
|
test: ^1.9.4
|
||||||
path: ^1.6.0
|
path: ^1.6.0
|
||||||
ffi: ^0.1.3
|
ffi: ^0.1.3
|
||||||
moor_ffi: ^0.6.0
|
sqlite3: ^0.1.3
|
|
@ -2,7 +2,7 @@ import 'dart:convert';
|
||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
|
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'package:moor_ffi/open_helper.dart';
|
import 'package:sqlite3/open.dart';
|
||||||
import 'package:ffi/ffi.dart';
|
import 'package:ffi/ffi.dart';
|
||||||
|
|
||||||
import 'package:sqlparser/src/reader/tokenizer/token.dart';
|
import 'package:sqlparser/src/reader/tokenizer/token.dart';
|
||||||
|
|
Loading…
Reference in New Issue