Delete deprecated moor_ffi package

This commit is contained in:
Simon Binder 2021-01-03 17:05:15 +01:00
parent 603c9a20cc
commit ae7aa51ee3
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
48 changed files with 0 additions and 3066 deletions

13
moor_ffi/.gitignore vendored
View File

@ -1,13 +0,0 @@
.DS_Store
.dart_tool/
.packages
.pub/
build/
pubspec.lock
# todo determine whether metadata should be added to gitignore (the file says it shouldn't, but does this break)?
.metadata
coverage/**

View File

@ -1,58 +0,0 @@
## 0.8.0
This package is now deprecated, but will continue to work for Flutter users.
Moor users should use the new `package:moor/ffi.dart` library.
To migrate,
- replace imports
- of `package:moor_ffi/moor_ffi.dart` with `package:moor/ffi.dart`
- of `package:moor_ffi/open_helper.dart` with `package:sqlite3/open.dart`
- when using Flutter, add a dependency on `sqlite3_flutter_libs`
Users of this package that don't use moor should use the new [sqlite3](https://pub.dev/packages/sqlite3)
package instead.
## 0.7.0
- Throw an error when using an unsupported datatype as argument
- Return null from `REGEXP` when either argument is null (used to report an error)
## 0.6.0
- Added `moor_contains` sql function to support case-sensitive contains
- Workaround for `dlopen` issues on some Android devices.
## 0.5.0
- Provide mathematical functions in sql (`pow`, `power`, `sin`, `cos`, `tan`, `asin`, `atan`, `acos`, `sqrt`)
- On Android, use sqlite 3.31.1
- added an `extendedResultCode` to `SqliteException`
## 0.4.0
- Use precompiled libraries for faster build times
## 0.3.2
- Fix a bug where empty blobs would read as `null` instead of an empty list
## 0.3.1
- Implement `overrideForAll` and `overrideFor` - thanks, [@negator](https://github.com/negator)
## 0.3.0
- Better setup for compiling sqlite3 on Android
- Compilation options to increase runtime performance, enable `fts5` and `json1`
- We no longer download sqlite sources on the first run, they now ship with the plugin
## 0.2.0
- Remove the `background` flag from the moor apis provided by this package. Use the moor isolate api
instead.
- Remove builtin support for background execution from the low-level `Database` api
- Support Dart 2.6, drop support for older versions
## 0.0.1
- Initial release. Contains standalone bindings and a moor implementation.

View File

@ -1,24 +0,0 @@
MIT License
Copyright (c) 2019 Simon Binder
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
This project also bundles sqlite, which is in the Public Domain.
See https://www.sqlite.org/copyright.html

View File

@ -1,111 +0,0 @@
This package has been deprecated! Consider migrating to new sqlite packages depending on your setup:
- You use moor to build Flutter apps: Remove `moor_ffi` from your dependencies and add `sqlite3_flutter_libs`
instead. Also, replace the following imports:
- change `package:moor_ffi/moor_ffi.dart` to `package:moor/ffi.dart`
- change `package:moor_ffi/open_helper.dart` to `package:sqlite3/open.dart`
- You use moor, but without Flutter: Just drop the `moor_ffi` dependency and use the new
`package:moor/ffi.dart` library. You need to make sure that a dynamic sqlite3 library is
available at runtime.
- You don't use moor, but you do use Flutter: Depend on `sqlite3` and `sqlite3_flutter_libs`. The
`sqlite3` package contains the new Dart apis to use sqlite3.
- You don't use the main moor package or Flutter: Just depend on the `sqlite3` package and make sure that a
dynamic sqlite3 library is available at runtime
# moor_ffi
Dart bindings to sqlite by using `dart:ffi`. This library contains utils to make
integration with [moor](https://pub.dev/packages/moor) easier, but it can also be used
as a standalone package. It also doesn't depend on Flutter, so it can be used on Dart VM
applications as well.
## Supported platforms
You can make this library work on any platform that lets you obtain a `DynamicLibrary`
in which sqlite's symbols are available (see below).
Out of the box, this library supports all platforms where `sqlite3` is installed:
- iOS: Yes
- macOS: Yes
- Linux: Available on most distros
- Windows: Additional setup is required
- Android: Yes when used with Flutter, this library includes the necessary native libraries on Android
### On other platforms
Using this library on platforms that are not supported out of the box is fairly
straightforward. For instance, if you release your own `sqlite3.so` next to your application,
you could use
```dart
import 'dart:ffi';
import 'dart:io';
import 'package:moor_ffi/database.dart';
import 'package:moor_ffi/open_helper.dart';
void main() {
open.overrideFor(OperatingSystem.linux, _openOnLinux);
final db = Database.memory();
db.close();
}
DynamicLibrary _openOnLinux() {
final script = File(Platform.script.toFilePath());
final libraryNextToScript = File('${script.path}/sqlite3.so');
return DynamicLibrary.open(libraryNextToScript.path);
}
```
Just be sure to first override the behavior and then open the database. Further,
if you want to use the isolate api, you can only use a static method or a top-level
function to open the library. For Windows, a similar setup with a `sqlite3.dll` library
should work.
### Supported datatypes
This library supports `null`, `int`, `double`, `String` and `Uint8List` to bind args.
Returned columns from select statements will have the same types.
## Using without moor
```dart
import 'package:moor_ffi/database.dart';
void main() {
final database = Database.memory();
// run some database operations. See the example for details
database.close();
}
```
Be sure to __always__ call `Database.close` to avoid memory leaks!
## Using with moor
If you're migrating an existing project using `moor_flutter`, see the
[documentation](https://moor.simonbinder.eu/docs/other-engines/vm/#migrating-from-moor-flutter-to-moor-ffi).
Add both `moor` and `moor_ffi` to your pubspec:
```yaml
dependencies:
moor: ^2.0.0
moor_ffi: ^0.2.0
dev_dependencies:
moor_generator: ^2.0.0
```
You can then use a `VmDatabase` as an executor:
```dart
@UseMoor(...)
class MyDatabase extends _$MyDatabase {
MyDatabase(): super(VmDatabase(File('app.db')));
}
```
If you need to find an appropriate directory for the database file, you can use the `LazyDatabase` wrapper
from moor. It can be used to create the inner `VmDatabase` asynchronously:
```dart
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
// use this instead of VmDatabase(...)
LazyDatabase(() async {
final dbFolder = await getApplicationDocumentsDirectory();
final file = File(p.join(dbFolder.path, 'app.db'));
return VmDatabase(file);
});
```

View File

@ -1 +0,0 @@
../analysis_options.yaml

View File

@ -1,11 +0,0 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.externalNativeBuild/
cpp/sqlite*

View File

@ -1,42 +0,0 @@
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
}
}
group 'eu.simonbinder.moor_ffi'
version '1.0'
rootProject.allprojects {
repositories {
google()
jcenter()
maven {
url 'https://dl.bintray.com/sbinder/sqlite3-native-library/'
}
}
}
apply plugin: 'com.android.library'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 16
}
lintOptions {
disable 'InvalidPackage'
}
}
dependencies {
implementation 'eu.simonbinder:sqlite3-native-library:3.32.3'
}

View File

@ -1,2 +0,0 @@
org.gradle.jvmargs=-Xmx1536M

View File

@ -1,5 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip

View File

@ -1 +0,0 @@
rootProject.name = 'moor_ffi'

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.simonbinder.moor_ffi"
android:versionCode="1"
android:versionName="1.0" >
</manifest>

View File

@ -1,17 +0,0 @@
package eu.simonbinder.moor_ffi;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
public class MoorFfiPlugin implements FlutterPlugin {
@Override
public void onAttachedToEngine(FlutterPluginBinding binding) {
// Do nothing, we only need the native libraries.
}
@Override
public void onDetachedFromEngine(FlutterPluginBinding binding) {
// Again, nothing to do here.
}
}

View File

@ -1,27 +0,0 @@
import 'package:moor_ffi/database.dart';
const _createTable = r'''
CREATE TABLE frameworks (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name VARCHAR NOT NULL
);
''';
void main() {
final db = Database.memory();
db.execute(_createTable);
final insertStmt = db.prepare('INSERT INTO frameworks(name) VALUES (?)');
insertStmt.execute(['Flutter']);
insertStmt.execute(['AngularDart']);
insertStmt.close();
final selectStmt = db.prepare('SELECT * FROM frameworks ORDER BY name');
final result = selectStmt.select();
for (final row in result) {
print('${row['id']}: ${row['name']}');
}
selectStmt.close();
db.close();
}

View File

@ -1,37 +0,0 @@
.idea/
.vagrant/
.sconsign.dblite
.svn/
.DS_Store
*.swp
profile
DerivedData/
build/
GeneratedPluginRegistrant.h
GeneratedPluginRegistrant.m
.generated/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
xcuserdata
*.moved-aside
*.pyc
*sync/
Icon?
.tags*
/Flutter/Generated.xcconfig
/Flutter/flutter_export_environment.sh

View File

@ -1,4 +0,0 @@
#import <Flutter/Flutter.h>
@interface MoorFfiPlugin : NSObject<FlutterPlugin>
@end

View File

@ -1,8 +0,0 @@
#import "MoorFfiPlugin.h"
#import <moor_ffi/moor_ffi-Swift.h>
@implementation MoorFfiPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
[SwiftMoorFfiPlugin registerWithRegistrar:registrar];
}
@end

View File

@ -1,14 +0,0 @@
import Flutter
import UIKit
public class SwiftMoorFfiPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "moor_ffi", binaryMessenger: registrar.messenger())
let instance = SwiftMoorFfiPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
result("iOS " + UIDevice.current.systemVersion)
}
}

View File

@ -1,23 +0,0 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
#
Pod::Spec.new do |s|
s.name = 'moor_ffi'
s.version = '0.0.1'
s.summary = 'A new flutter plugin project.'
s.description = <<-DESC
A new flutter plugin project.
DESC
s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' }
#s.source_files = 'Classes/**/*'
#s.public_header_files = 'Classes/**/*.h'
s.dependency 'Flutter'
# when we run on Dart 2.6, we should use this library and also use DynamicLibrary.executable()
# s.dependency 'sqlite3'
s.ios.deployment_target = '8.0'
end

View File

@ -1,11 +0,0 @@
/// Exports the low-level [Database] class to run operations on a sqlite
/// database via `dart:ffi`.
@Deprecated('Consider migrating to package:sqlite3/sqlite3.dart')
library database;
import 'package:moor_ffi/src/bindings/types.dart';
export 'src/api/result.dart';
export 'src/bindings/types.dart' hide Database, Statement;
export 'src/impl/database.dart'
show SqliteException, Database, PreparedStatement;

View File

@ -1,8 +0,0 @@
@Deprecated('Use package:moor/ffi.dart instead')
import 'dart:io';
import 'package:moor/backends.dart';
import 'package:moor/moor.dart';
import 'package:moor_ffi/database.dart';
part 'src/vm_database.dart';

View File

@ -1,8 +0,0 @@
/// Utils to open a [DynamicLibrary] on platforms that aren't supported by
/// `moor_ffi` by default.
@Deprecated('Consider migrating to package:sqlite3/open.dart')
library open_helper;
import 'dart:ffi';
export 'src/load_library.dart';

View File

@ -1,64 +0,0 @@
import 'dart:collection';
import 'package:collection/collection.dart';
/// Stores the result of a select statement.
class Result extends Iterable<Row> {
final List<String> columnNames;
// a result set can have multiple columns with the same name, but that's rare
// and users usually use a name as index. So we cache that for O(1) lookups
Map<String, int> _calculatedIndexes;
final List<List<dynamic>> rows;
Result(this.columnNames, this.rows) {
_calculatedIndexes = {
for (var column in columnNames) column: columnNames.lastIndexOf(column),
};
}
@override
Iterator<Row> get iterator => _ResultIterator(this);
}
/// Stores a single row in the result of a select statement.
class Row extends MapMixin<String, dynamic>
with UnmodifiableMapMixin<String, dynamic> {
final Result _result;
final int _rowIndex;
Row._(this._result, this._rowIndex);
/// Returns the value stored in the [i]-th column in this row (zero-indexed).
dynamic columnAt(int i) {
return _result.rows[_rowIndex][i];
}
@override
dynamic operator [](Object key) {
if (key is! String) return null;
final index = _result._calculatedIndexes[key];
if (index == null) return null;
return columnAt(index);
}
@override
Iterable<String> get keys => _result.columnNames;
}
class _ResultIterator extends Iterator<Row> {
final Result result;
int index = -1;
_ResultIterator(this.result);
@override
Row get current => Row._(result, index);
@override
bool moveNext() {
index++;
return index < result.rows.length;
}
}

View File

@ -1,266 +0,0 @@
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:ffi';
import 'package:moor_ffi/open_helper.dart';
import '../ffi/blob.dart';
import 'signatures.dart';
import 'types.dart';
// ignore_for_file: comment_references, non_constant_identifier_names
class _SQLiteBindings {
DynamicLibrary sqlite;
int Function(Pointer<CBlob> filename, Pointer<Pointer<Database>> databaseOut,
int flags, Pointer<CBlob> vfs) sqlite3_open_v2;
int Function(Pointer<Database> database) sqlite3_close_v2;
void Function(Pointer<Void> ptr) sqlite3_free;
int Function(
Pointer<Database> database,
Pointer<CBlob> query,
int nbytes,
Pointer<Pointer<Statement>> statementOut,
Pointer<Pointer<CBlob>> tail) sqlite3_prepare_v2;
int Function(
Pointer<Database> database,
Pointer<CBlob> query,
Pointer<Void> callback,
Pointer<Void> cbFirstArg,
Pointer<Pointer<CBlob>> errorMsgOut,
) sqlite3_exec;
int Function(Pointer<Statement> statement) sqlite3_step;
int Function(Pointer<Statement> statement) sqlite3_reset;
int Function(Pointer<Statement> statement) sqlite3_finalize;
int Function(Pointer<Statement> statement) sqlite3_column_count;
Pointer<CBlob> Function(Pointer<Statement> statement, int columnIndex)
sqlite3_column_name;
int Function(Pointer<Statement> statement, int columnIndex)
sqlite3_column_type;
double Function(Pointer<Statement> statement, int columnIndex)
sqlite3_column_double;
int Function(Pointer<Statement> statement, int columnIndex)
sqlite3_column_int64;
Pointer<CBlob> Function(Pointer<Statement> statement, int columnIndex)
sqlite3_column_text;
Pointer<CBlob> Function(Pointer<Statement> statement, int columnIndex)
sqlite3_column_blob;
/// Returns the amount of bytes to read when using [sqlite3_column_blob].
int Function(Pointer<Statement> statement, int columnIndex)
sqlite3_column_bytes;
int Function(Pointer<Database> db) sqlite3_changes;
int Function(Pointer<Database> db) sqlite3_last_insert_rowid;
int Function(Pointer<Database> db) sqlite3_extended_errcode;
Pointer<CBlob> Function(int code) sqlite3_errstr;
Pointer<CBlob> Function(Pointer<Database> database) sqlite3_errmsg;
int Function(Pointer<Database> database, int onOff)
sqlite3_extended_result_codes;
int Function(Pointer<Statement> statement, int columnIndex, double value)
sqlite3_bind_double;
int Function(Pointer<Statement> statement, int columnIndex, int value)
sqlite3_bind_int64;
int Function(
Pointer<Statement> statement,
int columnIndex,
Pointer<CBlob> value,
int minusOne,
Pointer<Void> disposeCb) sqlite3_bind_text;
int Function(
Pointer<Statement> statement,
int columnIndex,
Pointer<CBlob> value,
int length,
Pointer<Void> disposeCb) sqlite3_bind_blob;
int Function(Pointer<Statement> statement, int columnIndex) sqlite3_bind_null;
sqlite3_bind_parameter_count_dart sqlite3_bind_parameter_count;
int Function(
Pointer<Database> db,
Pointer<Uint8> zFunctionName,
int argCount,
int eTextRep,
Pointer<Void> arg,
Pointer<NativeFunction<sqlite3_function_handler>> handler,
Pointer<NativeFunction<sqlite3_function_handler>> step,
Pointer<NativeFunction<sqlite3_function_finalizer>> finalizer,
Pointer<NativeFunction<sqlite3_finalizer>> destroyArg,
) sqlite3_create_function_v2;
Pointer<CBlob> Function(Pointer<SqliteValue> value) sqlite3_value_blob;
Pointer<CBlob> Function(Pointer<SqliteValue> value) sqlite3_value_text;
double Function(Pointer<SqliteValue> value) sqlite3_value_double;
int Function(Pointer<SqliteValue> value) sqlite3_value_int64;
int Function(Pointer<SqliteValue> value) sqlite3_value_bytes;
int Function(Pointer<SqliteValue> value) sqlite3_value_type;
void Function(Pointer<FunctionContext> ctx) sqlite3_result_null;
void Function(Pointer<FunctionContext> ctx, int value) sqlite3_result_int64;
void Function(Pointer<FunctionContext> ctx, double value)
sqlite3_result_double;
void Function(Pointer<FunctionContext> ctx, Pointer<CBlob> msg, int length)
sqlite3_result_error;
_SQLiteBindings() {
sqlite = open.openSqlite();
sqlite3_bind_double = sqlite
.lookup<NativeFunction<sqlite3_bind_double_native>>(
'sqlite3_bind_double')
.asFunction();
sqlite3_bind_int64 = sqlite
.lookup<NativeFunction<sqlite3_bind_int64_native>>('sqlite3_bind_int64')
.asFunction();
sqlite3_bind_text = sqlite
.lookup<NativeFunction<sqlite3_bind_text_native>>('sqlite3_bind_text')
.asFunction();
sqlite3_bind_blob = sqlite
.lookup<NativeFunction<sqlite3_bind_blob_native>>('sqlite3_bind_blob')
.asFunction();
sqlite3_bind_null = sqlite
.lookup<NativeFunction<sqlite3_bind_null_native>>('sqlite3_bind_null')
.asFunction();
sqlite3_bind_parameter_count = sqlite.lookupFunction<
sqlite3_bind_parameter_count_native,
sqlite3_bind_parameter_count_dart>('sqlite3_bind_parameter_count');
sqlite3_open_v2 = sqlite
.lookup<NativeFunction<sqlite3_open_v2_native_t>>('sqlite3_open_v2')
.asFunction();
sqlite3_close_v2 = sqlite
.lookup<NativeFunction<sqlite3_close_v2_native_t>>('sqlite3_close_v2')
.asFunction();
sqlite3_free = sqlite
.lookup<NativeFunction<sqlite3_free_native>>('sqlite3_free')
.asFunction();
sqlite3_prepare_v2 = sqlite
.lookup<NativeFunction<sqlite3_prepare_v2_native_t>>(
'sqlite3_prepare_v2')
.asFunction();
sqlite3_exec = sqlite
.lookup<NativeFunction<sqlite3_exec_native>>('sqlite3_exec')
.asFunction();
sqlite3_step = sqlite
.lookup<NativeFunction<sqlite3_step_native_t>>('sqlite3_step')
.asFunction();
sqlite3_reset = sqlite
.lookup<NativeFunction<sqlite3_reset_native_t>>('sqlite3_reset')
.asFunction();
sqlite3_changes = sqlite
.lookup<NativeFunction<sqlite3_changes_native>>('sqlite3_changes')
.asFunction();
sqlite3_last_insert_rowid = sqlite
.lookup<NativeFunction<sqlite3_last_insert_rowid_native>>(
'sqlite3_last_insert_rowid')
.asFunction();
sqlite3_finalize = sqlite
.lookup<NativeFunction<sqlite3_finalize_native_t>>('sqlite3_finalize')
.asFunction();
sqlite3_extended_errcode = sqlite
.lookup<NativeFunction<sqlite3_extended_errcode_native_t>>(
'sqlite3_extended_errcode')
.asFunction();
sqlite3_errstr = sqlite
.lookup<NativeFunction<sqlite3_errstr_native_t>>('sqlite3_errstr')
.asFunction();
sqlite3_errmsg = sqlite
.lookup<NativeFunction<sqlite3_errmsg_native_t>>('sqlite3_errmsg')
.asFunction();
sqlite3_extended_result_codes = sqlite
.lookup<NativeFunction<sqlite3_extended_result_codes_t>>(
'sqlite3_extended_result_codes')
.asFunction();
sqlite3_column_count = sqlite
.lookup<NativeFunction<sqlite3_column_count_native_t>>(
'sqlite3_column_count')
.asFunction();
sqlite3_column_name = sqlite
.lookup<NativeFunction<sqlite3_column_name_native_t>>(
'sqlite3_column_name')
.asFunction();
sqlite3_column_type = sqlite
.lookup<NativeFunction<sqlite3_column_type_native_t>>(
'sqlite3_column_type')
.asFunction();
sqlite3_column_double = sqlite
.lookup<NativeFunction<sqlite3_column_double_native_t>>(
'sqlite3_column_double')
.asFunction();
sqlite3_column_int64 = sqlite
.lookup<NativeFunction<sqlite3_column_int64_native_t>>(
'sqlite3_column_int64')
.asFunction();
sqlite3_column_text = sqlite
.lookup<NativeFunction<sqlite3_column_text_native_t>>(
'sqlite3_column_text')
.asFunction();
sqlite3_column_blob = sqlite
.lookup<NativeFunction<sqlite3_column_blob_native_t>>(
'sqlite3_column_blob')
.asFunction();
sqlite3_column_bytes = sqlite
.lookup<NativeFunction<sqlite3_column_bytes_native_t>>(
'sqlite3_column_bytes')
.asFunction();
sqlite3_create_function_v2 = sqlite
.lookup<NativeFunction<sqlite3_create_function_v2_native>>(
'sqlite3_create_function_v2')
.asFunction();
sqlite3_value_blob = sqlite
.lookup<NativeFunction<sqlite3_value_blob_native>>('sqlite3_value_blob')
.asFunction();
sqlite3_value_text = sqlite
.lookup<NativeFunction<sqlite3_value_text_native>>('sqlite3_value_text')
.asFunction();
sqlite3_value_double = sqlite
.lookup<NativeFunction<sqlite3_value_double_native>>(
'sqlite3_value_double')
.asFunction();
sqlite3_value_int64 = sqlite
.lookup<NativeFunction<sqlite3_value_int64_native>>(
'sqlite3_value_int64')
.asFunction();
sqlite3_value_bytes = sqlite
.lookup<NativeFunction<sqlite3_value_bytes_native>>(
'sqlite3_value_bytes')
.asFunction();
sqlite3_value_type = sqlite
.lookup<NativeFunction<sqlite3_value_type_native>>('sqlite3_value_type')
.asFunction();
sqlite3_result_null = sqlite
.lookup<NativeFunction<sqlite3_result_null_native>>(
'sqlite3_result_null')
.asFunction();
sqlite3_result_int64 = sqlite
.lookup<NativeFunction<sqlite3_result_int64_native>>(
'sqlite3_result_int64')
.asFunction();
sqlite3_result_double = sqlite
.lookup<NativeFunction<sqlite3_result_double_native>>(
'sqlite3_result_double')
.asFunction();
sqlite3_result_error = sqlite
.lookup<NativeFunction<sqlite3_result_error_native>>(
'sqlite3_result_error')
.asFunction();
}
}
_SQLiteBindings _cachedBindings;
_SQLiteBindings get bindings => _cachedBindings ??= _SQLiteBindings();

View File

@ -1,193 +0,0 @@
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// ignore_for_file: constant_identifier_names
/// Result Codes
///
/// Many SQLite functions return an integer result code from the set shown
/// here in order to indicates success or failure.
///
/// New error codes may be added in future versions of SQLite.
///
/// See also: SQLITE_IOERR_READ | extended result codes,
/// sqlite3_vtab_on_conflict() SQLITE_ROLLBACK | result codes.
class Errors {
/// Successful result
static const int SQLITE_OK = 0;
/// Generic error
static const int SQLITE_ERROR = 1;
/// Internal logic error in SQLite
static const int SQLITE_INTERNAL = 2;
/// Access permission denied
static const int SQLITE_PERM = 3;
/// Callback routine requested an abort
static const int SQLITE_ABORT = 4;
/// The database file is locked
static const int SQLITE_BUSY = 5;
/// A table in the database is locked
static const int SQLITE_LOCKED = 6;
/// A malloc() failed
static const int SQLITE_NOMEM = 7;
/// Attempt to write a readonly database
static const int SQLITE_READONLY = 8;
/// Operation terminated by sqlite3_interrupt()
static const int SQLITE_INTERRUPT = 9;
/// Some kind of disk I/O error occurred
static const int SQLITE_IOERR = 10;
/// The database disk image is malformed
static const int SQLITE_CORRUPT = 11;
/// Unknown opcode in sqlite3_file_control()
static const int SQLITE_NOTFOUND = 12;
/// Insertion failed because database is full
static const int SQLITE_FULL = 13;
/// Unable to open the database file
static const int SQLITE_CANTOPEN = 14;
/// Database lock protocol error
static const int SQLITE_PROTOCOL = 15;
/// Internal use only
static const int SQLITE_EMPTY = 16;
/// The database schema changed
static const int SQLITE_SCHEMA = 17;
/// String or BLOB exceeds size limit
static const int SQLITE_TOOBIG = 18;
/// Abort due to constraint violation
static const int SQLITE_CONSTRAINT = 19;
/// Data type mismatch
static const int SQLITE_MISMATCH = 20;
/// Library used incorrectly
static const int SQLITE_MISUSE = 21;
/// Uses OS features not supported on host
static const int SQLITE_NOLFS = 22;
/// Authorization denied
static const int SQLITE_AUTH = 23;
/// Not used
static const int SQLITE_FORMAT = 24;
/// 2nd parameter to sqlite3_bind out of range
static const int SQLITE_RANGE = 25;
/// File opened that is not a database file
static const int SQLITE_NOTADB = 26;
/// Notifications from sqlite3_log()
static const int SQLITE_NOTICE = 27;
/// Warnings from sqlite3_log()
static const int SQLITE_WARNING = 28;
/// sqlite3_step() has another row ready
static const int SQLITE_ROW = 100;
/// sqlite3_step() has finished executing
static const int SQLITE_DONE = 101;
}
/// Flags For File Open Operations
///
/// These bit values are intended for use in the
/// 3rd parameter to the [sqlite3_open_v2()] interface and
/// in the 4th parameter to the `sqlite3_vfs.xOpen` method.
class Flags {
/// Ok for sqlite3_open_v2()
static const int SQLITE_OPEN_READONLY = 0x00000001;
/// Ok for sqlite3_open_v2()
static const int SQLITE_OPEN_READWRITE = 0x00000002;
/// Ok for sqlite3_open_v2()
static const int SQLITE_OPEN_CREATE = 0x00000004;
/// VFS only
static const int SQLITE_OPEN_DELETEONCLOSE = 0x00000008;
/// VFS only
static const int SQLITE_OPEN_EXCLUSIVE = 0x00000010;
/// VFS only
static const int SQLITE_OPEN_AUTOPROXY = 0x00000020;
/// Ok for sqlite3_open_v2()
static const int SQLITE_OPEN_URI = 0x00000040;
/// Ok for sqlite3_open_v2()
static const int SQLITE_OPEN_MEMORY = 0x00000080;
/// VFS only
static const int SQLITE_OPEN_MAIN_DB = 0x00000100;
/// VFS only
static const int SQLITE_OPEN_TEMP_DB = 0x00000200;
/// VFS only
static const int SQLITE_OPEN_TRANSIENT_DB = 0x00000400;
/// VFS only
static const int SQLITE_OPEN_MAIN_JOURNAL = 0x00000800;
/// VFS only
static const int SQLITE_OPEN_TEMP_JOURNAL = 0x00001000;
/// VFS only
static const int SQLITE_OPEN_SUBJOURNAL = 0x00002000;
/// VFS only
static const int SQLITE_OPEN_MASTER_JOURNAL = 0x00004000;
/// Ok for sqlite3_open_v2()
static const int SQLITE_OPEN_NOMUTEX = 0x00008000;
/// Ok for sqlite3_open_v2()
static const int SQLITE_OPEN_FULLMUTEX = 0x00010000;
/// Ok for sqlite3_open_v2()
static const int SQLITE_OPEN_SHAREDCACHE = 0x00020000;
/// Ok for sqlite3_open_v2()
static const int SQLITE_OPEN_PRIVATECACHE = 0x00040000;
/// VFS only
static const int SQLITE_OPEN_WAL = 0x00080000;
}
class TextEncodings {
static const int SQLITE_UTF8 = 1;
}
class FunctionFlags {
static const int SQLITE_DETERMINISTIC = 0x000000800;
static const int SQLITE_DIRECTONLY = 0x000080000;
}
class Types {
static const int SQLITE_INTEGER = 1;
static const int SQLITE_FLOAT = 2;
static const int SQLITE_TEXT = 3;
static const int SQLITE_BLOB = 4;
static const int SQLITE_NULL = 5;
}

View File

@ -1,140 +0,0 @@
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:ffi';
import '../ffi/blob.dart';
import 'types.dart';
typedef sqlite3_open_v2_native_t = Int32 Function(Pointer<CBlob> filename,
Pointer<Pointer<Database>> ppDb, Int32 flags, Pointer<CBlob> vfs);
typedef sqlite3_close_v2_native_t = Int32 Function(Pointer<Database> database);
typedef sqlite3_free_native = Void Function(Pointer<Void> pointer);
typedef sqlite3_prepare_v2_native_t = Int32 Function(
Pointer<Database> database,
Pointer<CBlob> query,
Int32 nbytes,
Pointer<Pointer<Statement>> statementOut,
Pointer<Pointer<CBlob>> tail);
typedef sqlite3_exec_native = Int32 Function(
Pointer<Database> database,
Pointer<CBlob> query,
Pointer<Void> callback,
Pointer<Void> firstCbArg,
Pointer<Pointer<CBlob>> errorOut);
typedef sqlite3_step_native_t = Int32 Function(Pointer<Statement> statement);
typedef sqlite3_reset_native_t = Int32 Function(Pointer<Statement> statement);
typedef sqlite3_finalize_native_t = Int32 Function(
Pointer<Statement> statement);
typedef sqlite3_extended_errcode_native_t = Int32 Function(
Pointer<Database> database);
typedef sqlite3_errstr_native_t = Pointer<CBlob> Function(Int32 error);
typedef sqlite3_errmsg_native_t = Pointer<CBlob> Function(
Pointer<Database> database);
typedef sqlite3_extended_result_codes_t = Int32 Function(
Pointer<Database> database, Int32 onOff);
typedef sqlite3_column_count_native_t = Int32 Function(
Pointer<Statement> statement);
typedef sqlite3_column_name_native_t = Pointer<CBlob> Function(
Pointer<Statement> statement, Int32 columnIndex);
typedef sqlite3_column_type_native_t = Int32 Function(
Pointer<Statement> statement, Int32 columnIndex);
typedef sqlite3_column_double_native_t = Double Function(
Pointer<Statement> statement, Int32 columnIndex);
typedef sqlite3_column_int64_native_t = Int64 Function(
Pointer<Statement> statement, Int32 columnIndex);
typedef sqlite3_column_text_native_t = Pointer<CBlob> Function(
Pointer<Statement> statement, Int32 columnIndex);
typedef sqlite3_column_blob_native_t = Pointer<CBlob> Function(
Pointer<Statement> statement, Int32 columnIndex);
typedef sqlite3_column_bytes_native_t = Int32 Function(
Pointer<Statement> statement, Int32 columnIndex);
typedef sqlite3_changes_native = Int32 Function(Pointer<Database> database);
typedef sqlite3_last_insert_rowid_native = Int64 Function(
Pointer<Database> database);
typedef sqlite3_bind_double_native = Int32 Function(
Pointer<Statement> statement, Int32 columnIndex, Double value);
typedef sqlite3_bind_int64_native = Int32 Function(
Pointer<Statement> statement, Int32 columnIndex, Int64 value);
typedef sqlite3_bind_text_native = Int32 Function(
Pointer<Statement> statement,
Int32 columnIndex,
Pointer<CBlob> value,
Int32 length,
Pointer<Void> callback);
typedef sqlite3_bind_blob_native = Int32 Function(
Pointer<Statement> statement,
Int32 columnIndex,
Pointer<CBlob> value,
Int32 length,
Pointer<Void> callback);
typedef sqlite3_bind_null_native = Int32 Function(
Pointer<Statement> statement, Int32 columnIndex);
typedef sqlite3_bind_parameter_count_dart = int Function(
Pointer<Statement> stmt);
typedef sqlite3_bind_parameter_count_native = Int32 Function(
Pointer<Statement> statement);
typedef sqlite3_function_handler = Void Function(
Pointer<FunctionContext> context,
Int32 argCount,
Pointer<Pointer<SqliteValue>> args);
typedef sqlite3_function_finalizer = Void Function(
Pointer<FunctionContext> context);
typedef sqlite3_finalizer = Void Function(Pointer<Void> ptr);
typedef sqlite3_create_function_v2_native = Int32 Function(
Pointer<Database> db,
Pointer<Uint8> zFunctionName,
Int32 nArg,
Int32 eTextRep,
Pointer<Void> pApp,
Pointer<NativeFunction<sqlite3_function_handler>> xFunc,
Pointer<NativeFunction<sqlite3_function_handler>> xStep,
Pointer<NativeFunction<sqlite3_function_finalizer>> xDestroy,
Pointer<NativeFunction<sqlite3_finalizer>> finalizePApp,
);
typedef sqlite3_value_blob_native = Pointer<CBlob> Function(
Pointer<SqliteValue> value);
typedef sqlite3_value_double_native = Double Function(
Pointer<SqliteValue> value);
typedef sqlite3_value_int64_native = Int64 Function(Pointer<SqliteValue> value);
typedef sqlite3_value_text_native = Pointer<CBlob> Function(
Pointer<SqliteValue> value);
typedef sqlite3_value_bytes_native = Int32 Function(Pointer<SqliteValue> value);
typedef sqlite3_value_type_native = Int32 Function(Pointer<SqliteValue> value);
typedef sqlite3_result_null_native = Void Function(
Pointer<FunctionContext> context);
typedef sqlite3_result_double_native = Void Function(
Pointer<FunctionContext> context, Double value);
typedef sqlite3_result_int64_native = Void Function(
Pointer<FunctionContext> context, Int64 value);
typedef sqlite3_result_error_native = Void Function(
Pointer<FunctionContext> context, Pointer<CBlob> char, Int32 len);

View File

@ -1,142 +0,0 @@
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:convert';
import 'dart:ffi';
import 'package:moor/moor.dart';
import '../ffi/blob.dart';
import '../ffi/utils.dart';
import 'bindings.dart';
import 'constants.dart';
// ignore_for_file: comment_references
/// Database Connection Handle
///
/// Each open SQLite database is represented by a pointer to an instance of
/// the opaque structure named "sqlite3". It is useful to think of an sqlite3
/// pointer as an object. The [sqlite3_open()], [sqlite3_open16()], and
/// [sqlite3_open_v2()] interfaces are its constructors, and [sqlite3_close()]
/// is its destructor. There are many other interfaces (such as
/// [sqlite3_prepare_v2()], [sqlite3_create_function()], and
/// [sqlite3_busy_timeout()] to name but three) that are methods on an
class Database extends Struct {}
/// SQL Statement Object
///
/// An instance of this object represents a single SQL statement.
/// This object is variously known as a "prepared statement" or a
/// "compiled SQL statement" or simply as a "statement".
///
/// The life of a statement object goes something like this:
///
/// <ol>
/// <li> Create the object using [sqlite3_prepare_v2()] or a related
/// function.
/// <li> Bind values to [host parameters] using the sqlite3_bind_*()
/// interfaces.
/// <li> Run the SQL by calling [sqlite3_step()] one or more times.
/// <li> Reset the statement using [sqlite3_reset()] then go back
/// to step 2. Do this zero or more times.
/// <li> Destroy the object using [sqlite3_finalize()].
/// </ol>
///
/// Refer to documentation on individual methods above for additional
/// information.
class Statement extends Struct {}
/// The context in which an SQL function executes is stored in this object.
/// A pointer to this object is always the first paramater to
/// application-defined SQL functions.
///
/// See also:
/// - https://www.sqlite.org/c3ref/context.html
class FunctionContext extends Struct {}
/// A value object in sqlite, which can represent all values that can be stored
/// in a database table.
class SqliteValue extends Struct {}
/// Extension to extract value from a [SqliteValue].
extension SqliteValuePointer on Pointer<SqliteValue> {
/// Extracts the raw value from the object.
///
/// Depending on the type of this value as set in sqlite, [value] returns
/// - a [String]
/// - a [Uint8List]
/// - a [int]
/// - a [double]
/// - `null`
///
/// For texts and bytes, the value will be copied.
dynamic get value {
final api = bindings;
final type = api.sqlite3_value_type(this);
switch (type) {
case Types.SQLITE_INTEGER:
return api.sqlite3_value_int64(this);
case Types.SQLITE_FLOAT:
return api.sqlite3_value_double(this);
case Types.SQLITE_TEXT:
final length = api.sqlite3_value_bytes(this);
return api.sqlite3_value_text(this).readAsStringWithLength(length);
case Types.SQLITE_BLOB:
final length = api.sqlite3_value_bytes(this);
if (length == 0) {
// sqlite3_value_bytes returns a null pointer for non-null blobs with
// a length of 0. Note that we can distinguish this from a proper null
// by checking the type (which isn't SQLITE_NULL)
return Uint8List(0);
}
return api.sqlite3_value_blob(this).readBytes(length);
case Types.SQLITE_NULL:
default:
return null;
}
}
}
extension SqliteFunctionContextPointer on Pointer<FunctionContext> {
void resultNull() {
bindings.sqlite3_result_null(this);
}
void resultInt(int value) {
bindings.sqlite3_result_int64(this, value);
}
void resultDouble(double value) {
bindings.sqlite3_result_double(this, value);
}
void resultNum(num value) {
if (value is int) {
resultInt(value);
return;
} else if (value is double) {
resultDouble(value);
return;
}
throw AssertionError();
}
void resultBool(bool value) {
resultInt(value ? 1 : 0);
}
void resultError(String message) {
final encoded = Uint8List.fromList(utf8.encode(message));
final ptr = CBlob.allocate(encoded);
bindings.sqlite3_result_error(this, ptr, encoded.length);
// Note that sqlite3_result_error makes a private copy of error message
// before returning. Hence, we can deallocate the message here.
ptr.free();
}
}

View File

@ -1,66 +0,0 @@
import 'dart:convert';
import 'dart:ffi';
import 'dart:typed_data';
import 'package:moor_ffi/src/ffi/utils.dart';
import 'package:ffi/ffi.dart' as ffi;
/// Pointer to arbitrary blobs in C.
class CBlob extends Struct {
static Pointer<CBlob> allocate(Uint8List blob, {int paddingAtEnd = 0}) {
final str = ffi.allocate<Uint8>(count: blob.length + paddingAtEnd);
final asList = str.asTypedList(blob.length + paddingAtEnd);
asList.setAll(0, blob);
if (paddingAtEnd != 0) {
asList.fillRange(blob.length, blob.length + paddingAtEnd, 0);
}
return str.cast();
}
/// Allocates a 0-terminated string, encoded as utf8 and read from the
/// [string].
static Pointer<CBlob> allocateString(String string) {
final encoded = utf8.encode(string);
final data = Uint8List(encoded.length + 1) // already filled with zeroes
..setAll(0, encoded);
return CBlob.allocate(data);
}
}
extension CBlobPointer on Pointer<CBlob> {
/// Reads a 0-terminated string, decoded with utf8.
///
/// Warning: This method is very, very slow. If there is any way to know the
/// length of the string to read, [readAsStringWithLength] will be orders of
/// magnitude faster.
String readString() {
if (isNullPointer) return null;
var len = 0;
final asUintPointer = cast<Uint8>();
while (asUintPointer[++len] != 0) {}
final units = readBytes(len);
return utf8.decode(units);
}
/// More efficient version of [readString] that doesn't have to find a nil-
/// terminator. [length] is the amount of bytes to read. The string will be
/// decoded via [utf8].
String readAsStringWithLength(int length) {
return utf8.decode(readBytes(length));
}
/// Reads [length] bytes from this address.
Uint8List readBytes(int length) {
assert(length >= 0);
if (isNullPointer) return null;
final data = cast<Uint8>().asTypedList(length);
return Uint8List.fromList(data);
}
}

View File

@ -1,19 +0,0 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart' as ffi;
extension FreePointerExtension on Pointer {
bool get isNullPointer => this == nullptr;
void free() {
ffi.free(this);
}
}
/// Loads a null-pointer with a specified type.
///
/// The [nullptr] getter from `dart:ffi` can be slow due to being a
/// `Pointer<Null>` on which the VM has to perform runtime type checks. See also
/// https://github.com/dart-lang/sdk/issues/39488
@pragma('vm:prefer-inline')
Pointer<T> nullPtr<T extends NativeType>() => nullptr.cast<T>();

View File

@ -1,270 +0,0 @@
import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'package:ffi/ffi.dart';
import 'package:meta/meta.dart';
import 'package:moor_ffi/database.dart';
import 'package:moor_ffi/src/api/result.dart';
import 'package:moor_ffi/src/bindings/constants.dart';
import 'package:moor_ffi/src/bindings/signatures.dart';
import 'package:moor_ffi/src/bindings/types.dart' as types;
import 'package:moor_ffi/src/bindings/bindings.dart';
import 'package:moor_ffi/src/ffi/blob.dart';
import 'package:moor_ffi/src/ffi/utils.dart';
part 'errors.dart';
part 'moor_functions.dart';
part 'prepared_statement.dart';
const _openingFlags = Flags.SQLITE_OPEN_READWRITE | Flags.SQLITE_OPEN_CREATE;
const _readOnlyOpeningFlags = Flags.SQLITE_OPEN_READONLY;
/// A opened sqlite database.
class Database {
final Pointer<types.Database> _db;
final List<PreparedStatement> _preparedStmt = [];
final List<Pointer<Void>> _furtherAllocations = [];
bool _isClosed = false;
Database._(this._db);
/// Opens the [file] as a sqlite3 database. The file will be created if it
/// doesn't exist.
factory Database.openFile(File file) => Database.open(file.absolute.path);
/// Opens an in-memory sqlite3 database.
factory Database.memory() => Database.open(':memory:');
/// Opens an sqlite3 database from a filename.
///
/// Unless [readOnly] is set to true, database is opened in read/write mode.
factory Database.open(String fileName, {bool readOnly = false}) {
final dbOut = allocate<Pointer<types.Database>>();
final pathC = CBlob.allocateString(fileName);
final openingFlags =
(readOnly ?? false) ? _readOnlyOpeningFlags : _openingFlags;
final resultCode =
bindings.sqlite3_open_v2(pathC, dbOut, openingFlags, nullPtr());
final dbPointer = dbOut.value;
dbOut.free();
pathC.free();
if (resultCode == Errors.SQLITE_OK) {
// Turn extended result code to on.
bindings.sqlite3_extended_result_codes(dbPointer, 1);
return Database._(dbPointer);
} else {
bindings.sqlite3_close_v2(dbPointer);
throw SqliteException._fromErrorCode(dbPointer, resultCode);
}
}
void _ensureOpen() {
if (_isClosed) {
throw Exception('This database has already been closed');
}
}
/// Closes this database connection and releases the resources it uses. If
/// an error occurs while closing the database, an exception will be thrown.
/// The allocated memory will be freed either way.
void close() {
if (_isClosed) return;
// close all prepared statements first
_isClosed = true;
for (final stmt in _preparedStmt) {
stmt.close();
}
final code = bindings.sqlite3_close_v2(_db);
SqliteException exception;
if (code != Errors.SQLITE_OK) {
exception = SqliteException._fromErrorCode(_db, code);
}
_isClosed = true;
for (final additional in _furtherAllocations) {
additional.free();
}
// we don't need to deallocate the _db pointer, sqlite takes care of that
if (exception != null) {
throw exception;
}
}
void _handleStmtFinalized(PreparedStatement stmt) {
if (!_isClosed) {
_preparedStmt.remove(stmt);
}
}
/// Executes the [sql] statement and ignores the result. Will throw if an
/// error occurs while executing.
void execute(String sql) {
_ensureOpen();
final sqlPtr = CBlob.allocateString(sql);
final errorOut = allocate<Pointer<CBlob>>();
final result =
bindings.sqlite3_exec(_db, sqlPtr, nullPtr(), nullPtr(), errorOut);
sqlPtr.free();
final errorPtr = errorOut.value;
errorOut.free();
String errorMsg;
if (!errorPtr.isNullPointer) {
errorMsg = errorPtr.readString();
// the message was allocated from sqlite, we need to free it
bindings.sqlite3_free(errorPtr.cast());
}
if (result != Errors.SQLITE_OK) {
throw SqliteException(result, errorMsg);
}
}
/// Prepares the [sql] statement.
PreparedStatement prepare(String sql) {
_ensureOpen();
final stmtOut = allocate<Pointer<types.Statement>>();
final sqlPtr = CBlob.allocateString(sql);
final resultCode =
bindings.sqlite3_prepare_v2(_db, sqlPtr, -1, stmtOut, nullPtr());
sqlPtr.free();
final stmt = stmtOut.value;
stmtOut.free();
if (resultCode != Errors.SQLITE_OK) {
// we don't need to worry about freeing the statement. If preparing the
// statement was unsuccessful, stmtOut.load() will be null
throw SqliteException._fromErrorCode(_db, resultCode);
}
final prepared = PreparedStatement._(stmt, this);
_preparedStmt.add(prepared);
return prepared;
}
/// Registers a custom sqlite function by its [name].
///
/// The function must take [argumentCount] arguments, and it may not take more
/// than 127 arguments. If it can take a variable amount of arguments,
/// [argumentCount] should be set to `-1`.
///
/// When the output of the function depends solely on its input,
/// [isDeterministic] should be set. This allows sqlite's query planer to make
/// further optimizations.
/// When [directOnly] is set (defaults to true), the function can't be used
/// outside a query (e.g. in triggers, views, check constraints, index
/// expressions, etc.). Unless necessary, this should be enabled for security
/// purposes. See the discussion at the link for more details
/// The length of the utf8 encoding of [name] must not exceed 255 bytes.
///
/// See also:
/// - https://sqlite.org/c3ref/create_function.html
@visibleForTesting
void createFunction(
String name,
int argumentCount,
Pointer<NativeFunction<sqlite3_function_handler>> implementation, {
bool isDeterministic = false,
bool directOnly = true,
}) {
_ensureOpen();
final encodedName = Uint8List.fromList(utf8.encode(name));
// length of encoded name is limited to 255 bytes in utf8, excluding the 0
// terminator
if (encodedName.length > 255) {
throw ArgumentError.value(
name, 'name', 'Must be at most 255 bytes when encoded as utf8');
}
// argument length should be between -1 and 127
if (argumentCount < -1 || argumentCount > 127) {
throw ArgumentError.value(
argumentCount, 'argumentCount', 'Should be between -1 and 127');
}
final namePtr = CBlob.allocate(encodedName, paddingAtEnd: 1);
_furtherAllocations.add(namePtr.cast());
var textFlag = TextEncodings.SQLITE_UTF8;
if (isDeterministic) textFlag |= FunctionFlags.SQLITE_DETERMINISTIC;
if (directOnly) textFlag |= FunctionFlags.SQLITE_DIRECTONLY;
final result = bindings.sqlite3_create_function_v2(
_db,
namePtr.cast(),
argumentCount,
textFlag,
nullPtr(), // *pApp, we don't use that
implementation,
nullPtr(), // *xStep, null for regular functions
nullPtr(), // *xFinal, null for regular functions
nullPtr(), // finalizer for *pApp,
);
if (result != Errors.SQLITE_OK) {
throw SqliteException._fromErrorCode(_db, result);
}
}
/// Enables non-standard functions that ship with `moor_ffi`.
///
/// After calling [enableMoorFfiFunctions], the following functions can
/// be used in sql: `power`, `pow`, `sqrt`, `sin`, `cos`, `tan`, `asin`,
/// `acos`, `atan` and `regexp`.
///
/// At the moment, these functions are only available in statements. In
/// particular, they're not available in triggers, check constraints, index
/// expressions.
///
/// This should only be called once per database.
void enableMoorFfiFunctions() {
_registerOn(this);
}
/// Get the application defined version of this database.
int userVersion() {
final stmt = prepare('PRAGMA user_version');
final result = stmt.select();
stmt.close();
return result.first.columnAt(0) as int;
}
/// Update the application defined version of this database.
void setUserVersion(int version) {
execute('PRAGMA user_version = $version');
}
/// Returns the amount of rows affected by the last INSERT, UPDATE or DELETE
/// statement.
int getUpdatedRows() {
_ensureOpen();
return bindings.sqlite3_changes(_db);
}
/// Returns the row-id of the last inserted row.
int getLastInsertId() {
_ensureOpen();
return bindings.sqlite3_last_insert_rowid(_db);
}
}

View File

@ -1,49 +0,0 @@
part of 'database.dart';
class SqliteException implements Exception {
final String message;
final String explanation;
/// SQLite extended result code.
///
/// As defined in https://sqlite.org/rescode.html, it represent an error code,
/// providing some idea of the cause of the failure.
final int extendedResultCode;
/// SQLite primary result code.
///
/// As defined in https://sqlite.org/rescode.html, it represent an error code,
/// providing some idea of the cause of the failure.
int get resultCode => extendedResultCode & 0xFF;
SqliteException(this.extendedResultCode, this.message, [this.explanation]);
factory SqliteException._fromErrorCode(Pointer<types.Database> db, int code) {
// We don't need to free the pointer returned by sqlite3_errmsg: "Memory to
// hold the error message string is managed internally. The application does
// not need to worry about freeing the result."
// https://www.sqlite.org/c3ref/errcode.html
final dbMessage = bindings.sqlite3_errmsg(db).readString();
String explanation;
if (code != null) {
// Getting hold of more explanatory error code as SQLITE_IOERR error group
// has an extensive list of extended error codes
final extendedCode = bindings.sqlite3_extended_errcode(db);
final errStr = bindings.sqlite3_errstr(extendedCode).readString();
explanation = '$errStr (code $extendedCode)';
}
return SqliteException(code, dbMessage, explanation);
}
@override
String toString() {
if (explanation == null) {
return 'SqliteException($extendedResultCode): $message';
} else {
return 'SqliteException($extendedResultCode): $message, $explanation';
}
}
}

View File

@ -1,184 +0,0 @@
part of 'database.dart';
void _powImpl(Pointer<FunctionContext> ctx, int argCount,
Pointer<Pointer<SqliteValue>> args) {
// sqlite will ensure that this is only called with 2 arguments
final first = args[0].value;
final second = args[1].value;
if (first == null || second == null || first is! num || second is! num) {
ctx.resultNull();
return;
}
final result = pow(first as num, second as num);
ctx.resultNum(result);
}
/// Base implementation for a sqlite function that takes one numerical argument
/// and returns one numerical argument.
///
/// If [argCount] is not `1` or the single argument is not of a numerical type,
/// [ctx] will complete to null. Otherwise, it will complete to the result of
/// [calculation] with the casted argument.
void _unaryNumFunction(Pointer<FunctionContext> ctx, int argCount,
Pointer<Pointer<SqliteValue>> args, num Function(num) calculation) {
// sqlite will ensure that this is only called with one argument
final value = args[0].value;
if (value is num) {
ctx.resultNum(calculation(value));
} else {
ctx.resultNull();
}
}
void _sinImpl(Pointer<FunctionContext> ctx, int argCount,
Pointer<Pointer<SqliteValue>> args) {
_unaryNumFunction(ctx, argCount, args, sin);
}
void _cosImpl(Pointer<FunctionContext> ctx, int argCount,
Pointer<Pointer<SqliteValue>> args) {
_unaryNumFunction(ctx, argCount, args, cos);
}
void _tanImpl(Pointer<FunctionContext> ctx, int argCount,
Pointer<Pointer<SqliteValue>> args) {
_unaryNumFunction(ctx, argCount, args, tan);
}
void _sqrtImpl(Pointer<FunctionContext> ctx, int argCount,
Pointer<Pointer<SqliteValue>> args) {
_unaryNumFunction(ctx, argCount, args, sqrt);
}
void _asinImpl(Pointer<FunctionContext> ctx, int argCount,
Pointer<Pointer<SqliteValue>> args) {
_unaryNumFunction(ctx, argCount, args, asin);
}
void _acosImpl(Pointer<FunctionContext> ctx, int argCount,
Pointer<Pointer<SqliteValue>> args) {
_unaryNumFunction(ctx, argCount, args, acos);
}
void _atanImpl(Pointer<FunctionContext> ctx, int argCount,
Pointer<Pointer<SqliteValue>> args) {
_unaryNumFunction(ctx, argCount, args, atan);
}
void _regexpImpl(Pointer<FunctionContext> ctx, int argCount,
Pointer<Pointer<SqliteValue>> args) {
var multiLine = false;
var caseSensitive = true;
var unicode = false;
var dotAll = false;
if (argCount < 2 || argCount > 3) {
ctx.resultError('Expected two or three arguments to regexp');
return;
}
final firstParam = args[0].value;
final secondParam = args[1].value;
if (firstParam == null || secondParam == null) {
ctx.resultNull();
return;
}
if (firstParam is! String || secondParam is! String) {
ctx.resultError('Expected two strings as parameters to regexp');
return;
}
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].value;
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 catch (e) {
ctx.resultError('Invalid regex: $e');
return;
}
ctx.resultBool(regex.hasMatch(secondParam as String));
}
void _containsImpl(Pointer<FunctionContext> ctx, int argCount,
Pointer<Pointer<SqliteValue>> args) {
if (argCount < 2 || argCount > 3) {
ctx.resultError('Expected 2 or 3 arguments to moor_contains');
return;
}
final first = args[0].value;
final second = args[1].value;
if (first is! String || second is! String) {
ctx.resultError('First two args must be strings');
return;
}
final caseSensitive = argCount == 3 && args[2].value == 1;
final firstAsString = first as String;
final secondAsString = second as String;
final result = caseSensitive
? firstAsString.contains(secondAsString)
: firstAsString.toLowerCase().contains(secondAsString.toLowerCase());
ctx.resultInt(result ? 1 : 0);
}
void _registerOn(Database db) {
final powImplPointer =
Pointer.fromFunction<sqlite3_function_handler>(_powImpl);
db.createFunction('power', 2, powImplPointer, isDeterministic: true);
db.createFunction('pow', 2, powImplPointer, isDeterministic: true);
db.createFunction('sqrt', 1, Pointer.fromFunction(_sqrtImpl),
isDeterministic: true);
db.createFunction('sin', 1, Pointer.fromFunction(_sinImpl),
isDeterministic: true);
db.createFunction('cos', 1, Pointer.fromFunction(_cosImpl),
isDeterministic: true);
db.createFunction('tan', 1, Pointer.fromFunction(_tanImpl),
isDeterministic: true);
db.createFunction('asin', 1, Pointer.fromFunction(_asinImpl),
isDeterministic: true);
db.createFunction('acos', 1, Pointer.fromFunction(_acosImpl),
isDeterministic: true);
db.createFunction('atan', 1, Pointer.fromFunction(_atanImpl),
isDeterministic: true);
db.createFunction('regexp', 2, Pointer.fromFunction(_regexpImpl),
isDeterministic: true);
// Third argument can be used to set flags (like multiline, case sensitivity,
// etc.)
db.createFunction('regexp_moor_ffi', 3, Pointer.fromFunction(_regexpImpl));
final containsImplPointer =
Pointer.fromFunction<sqlite3_function_handler>(_containsImpl);
db.createFunction('moor_contains', 2, containsImplPointer,
isDeterministic: true);
db.createFunction('moor_contains', 3, containsImplPointer,
isDeterministic: true);
}

View File

@ -1,167 +0,0 @@
part of 'database.dart';
/// A prepared statement that can be executed multiple times.
class PreparedStatement {
final Pointer<types.Statement> _stmt;
final Database _db;
bool _closed = false;
bool _bound = false;
final List<Pointer> _allocatedWhileBinding = [];
PreparedStatement._(this._stmt, this._db);
/// Closes this prepared statement and releases its resources.
void close() {
if (!_closed) {
_reset();
bindings.sqlite3_finalize(_stmt);
_db._handleStmtFinalized(this);
}
_closed = true;
}
/// Returns the amount of parameters in this prepared statement.
///
/// See also:
/// - `sqlite3_bind_parameter_count`: https://www.sqlite.org/c3ref/bind_parameter_count.html
int get parameterCount => bindings.sqlite3_bind_parameter_count(_stmt);
void _ensureNotFinalized() {
if (_closed) {
throw StateError('Tried to operate on a released prepared statement');
}
}
/// Executes this prepared statement as a select statement. The returned rows
/// will be returned.
Result select([List<dynamic> params]) {
assert(
(params?.length ?? 0) == parameterCount,
'Expected $parameterCount params, but got ${params?.length ?? 0}.',
);
_ensureNotFinalized();
_reset();
_bindParams(params);
final columnCount = bindings.sqlite3_column_count(_stmt);
// not using a Map<String, int> for indexed because column names are not
// guaranteed to be unique
final names = List<String>(columnCount);
final rows = <List<dynamic>>[];
for (var i = 0; i < columnCount; i++) {
// name pointer doesn't need to be disposed, that happens when we finalize
names[i] = bindings.sqlite3_column_name(_stmt, i).readString();
}
int resultCode;
while ((resultCode = _step()) == Errors.SQLITE_ROW) {
rows.add([for (var i = 0; i < columnCount; i++) _readValue(i)]);
}
if (resultCode != Errors.SQLITE_OK && resultCode != Errors.SQLITE_DONE) {
throw SqliteException._fromErrorCode(_db._db, resultCode);
}
return Result(names, rows);
}
dynamic _readValue(int index) {
final type = bindings.sqlite3_column_type(_stmt, index);
switch (type) {
case Types.SQLITE_INTEGER:
return bindings.sqlite3_column_int64(_stmt, index);
case Types.SQLITE_FLOAT:
return bindings.sqlite3_column_double(_stmt, index);
case Types.SQLITE_TEXT:
final length = bindings.sqlite3_column_bytes(_stmt, index);
return bindings
.sqlite3_column_text(_stmt, index)
.readAsStringWithLength(length);
case Types.SQLITE_BLOB:
final length = bindings.sqlite3_column_bytes(_stmt, index);
if (length == 0) {
// sqlite3_column_blob returns a null pointer for non-null blobs with
// a length of 0. Note that we can distinguish this from a proper null
// by checking the type (which isn't SQLITE_NULL)
return Uint8List(0);
}
return bindings.sqlite3_column_blob(_stmt, index).readBytes(length);
case Types.SQLITE_NULL:
default:
return null;
}
}
/// Executes this prepared statement.
void execute([List<dynamic> params]) {
_ensureNotFinalized();
_reset();
_bindParams(params);
final result = _step();
if (result != Errors.SQLITE_OK && result != Errors.SQLITE_DONE) {
throw SqliteException._fromErrorCode(_db._db, result);
}
}
void _reset() {
if (_bound) {
bindings.sqlite3_reset(_stmt);
_bound = false;
}
for (final pointer in _allocatedWhileBinding) {
pointer.free();
}
_allocatedWhileBinding.clear();
}
void _bindParams(List<dynamic> params) {
if (params != null && params.isNotEmpty) {
// variables in sqlite are 1-indexed
for (var i = 1; i <= params.length; i++) {
final param = params[i - 1];
if (param == null) {
bindings.sqlite3_bind_null(_stmt, i);
} else if (param is int) {
bindings.sqlite3_bind_int64(_stmt, i, param);
} else if (param is num) {
bindings.sqlite3_bind_double(_stmt, i, param.toDouble());
} else if (param is String) {
final ptr = CBlob.allocateString(param);
_allocatedWhileBinding.add(ptr);
bindings.sqlite3_bind_text(_stmt, i, ptr, -1, nullPtr());
} else if (param is Uint8List) {
if (param.isEmpty) {
// malloc(0) is implementation-defined and might return a null
// pointer, which is not what we want: Passing a null-pointer to
// sqlite3_bind_blob will always bind NULL. So, we just pass 0x1 and
// set a length of 0
bindings.sqlite3_bind_blob(
_stmt, i, Pointer.fromAddress(1), param.length, nullPtr());
} else {
final ptr = CBlob.allocate(param);
bindings.sqlite3_bind_blob(_stmt, i, ptr, param.length, nullPtr());
_allocatedWhileBinding.add(ptr);
}
} else {
throw ArgumentError.value(
param,
'params[$i]',
'Allowed parameters must either be null or an int, num, String or '
'Uint8List.',
);
}
}
}
_bound = true;
}
int _step() => bindings.sqlite3_step(_stmt);
}

View File

@ -1,131 +0,0 @@
import 'dart:ffi';
import 'dart:io';
import 'dart:math';
import 'package:meta/meta.dart';
/// Signature responsible for loading the dynamic sqlite3 library that moor will
/// use.
typedef OpenLibrary = DynamicLibrary Function();
enum OperatingSystem {
android,
linux,
iOS,
macOS,
windows,
fuchsia,
}
/// The instance managing different approaches to load the [DynamicLibrary] for
/// sqlite when needed. See the documentation for [OpenDynamicLibrary] to learn
/// how the default opening behavior can be overridden.
final OpenDynamicLibrary open = OpenDynamicLibrary._();
DynamicLibrary _defaultOpen() {
if (Platform.isLinux || Platform.isAndroid) {
try {
return DynamicLibrary.open('libsqlite3.so');
} catch (_) {
if (Platform.isAndroid) {
// On some (especially old) Android devices, we somehow can't dlopen
// libraries shipped with the apk. We need to find the full path of the
// library (/data/data/<id>/lib/libsqlite3.so) and open that one.
// For details, see https://github.com/simolus3/moor/issues/420
final appIdAsBytes = File('/proc/self/cmdline').readAsBytesSync();
// app id ends with the first \0 character in here.
final endOfAppId = max(appIdAsBytes.indexOf(0), 0);
final appId = String.fromCharCodes(appIdAsBytes.sublist(0, endOfAppId));
return DynamicLibrary.open('/data/data/$appId/lib/libsqlite3.so');
}
rethrow;
}
}
if (Platform.isMacOS || Platform.isIOS) {
// todo: Consider including sqlite3 in the build and use DynamicLibrary.
// executable()
return DynamicLibrary.open('/usr/lib/libsqlite3.dylib');
}
if (Platform.isWindows) {
return DynamicLibrary.open('sqlite3.dll');
}
throw UnsupportedError(
'moor_ffi does not support ${Platform.operatingSystem} yet');
}
/// Manages functions that define how to load the [DynamicLibrary] for sqlite.
///
/// The default behavior will use `DynamicLibrary.open('libsqlite3.so')` on
/// Linux and Android, `DynamicLibrary.open('libsqlite3.dylib')` on iOS and
/// macOS and `DynamicLibrary.open('sqlite3.dll')` on Windows.
///
/// The default behavior can be overridden for a specific OS by using
/// [overrideFor]. To override the behavior on all platforms, use
/// [overrideForAll].
class OpenDynamicLibrary {
final Map<OperatingSystem, OpenLibrary> _overriddenPlatforms = {};
OpenLibrary _overriddenForAll;
OpenDynamicLibrary._();
/// Returns the current [OperatingSystem] as read from the [Platform] getters.
OperatingSystem get os {
if (Platform.isAndroid) return OperatingSystem.android;
if (Platform.isLinux) return OperatingSystem.linux;
if (Platform.isIOS) return OperatingSystem.iOS;
if (Platform.isMacOS) return OperatingSystem.macOS;
if (Platform.isWindows) return OperatingSystem.windows;
if (Platform.isFuchsia) return OperatingSystem.fuchsia;
return null;
}
/// Opens the [DynamicLibrary] from which `moor_ffi` is going to
/// [DynamicLibrary.lookup] sqlite's methods that will be used. This method is
/// meant to be called by `moor_ffi` only.
DynamicLibrary openSqlite() {
if (_overriddenForAll != null) {
return _overriddenForAll();
}
final forPlatform = _overriddenPlatforms[os];
if (forPlatform != null) {
return forPlatform();
}
return _defaultOpen();
}
/// Makes `moor_ffi` use the [open] function when running on the specified
/// [os]. This can be used to override the loading behavior on some platforms.
/// To override that behavior on all platforms, consider using
/// [overrideForAll].
/// This method must be called before opening any database.
///
/// When using the asynchronous API over isolates, [open] __must be__ a top-
/// level function or a static method.
void overrideFor(OperatingSystem os, OpenLibrary open) {
_overriddenPlatforms[os] = open;
}
// ignore: use_setters_to_change_properties
/// Makes `moor_ffi` use the [OpenLibrary] function for all Dart platforms.
/// If this method has been called, it takes precedence over [overrideFor].
/// This method must be called before opening any database.
///
/// When using the asynchronous API over isolates, [open] __must be__ a top-
/// level function or a static method.
void overrideForAll(OpenLibrary open) {
_overriddenForAll = open;
}
/// Clears all associated open helpers for all platforms.
@visibleForTesting
void reset() {
_overriddenForAll = null;
_overriddenPlatforms.clear();
}
}

View File

@ -1,122 +0,0 @@
part of 'package:moor_ffi/moor_ffi.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 = Database.openFile(file);
} else {
_db = Database.memory();
}
_db.enableMoorFfiFunctions();
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.close();
}
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.close();
}
}
@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.getLastInsertId();
}
@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.close();
return Future.value(QueryResult(result.columnNames, result.rows));
}
@override
Future<void> close() async {
_db.close();
}
}
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.setUserVersion(version);
return Future.value();
}
}

View File

@ -1,23 +0,0 @@
name: moor_ffi
description: "Provides sqlite bindings using dart:ffi, including a moor executor"
version: 0.8.0
homepage: https://github.com/simolus3/moor/tree/develop/moor_ffi
issue_tracker: https://github.com/simolus3/moor/issues
environment:
sdk: ">=2.6.0 <3.0.0"
flutter: any
dependencies:
moor: ^3.0.0
ffi: ^0.1.3
collection: ^1.0.0
meta: ^1.0.2
# moor_ffi used to include sqlite on Flutter builds, so we depend on sqlite3_flutter_libs to continue this behavior.
# This makes moor_ffi a Flutter-only package - not what we want, but now it's deprecated anyways. Non-Flutter users
# should use the `sqlite3` package instead.
sqlite3_flutter_libs: ^0.2.0
dev_dependencies:
test: ^1.6.0
path: ^1.6.0

View File

@ -1,95 +0,0 @@
import 'dart:io';
import 'package:moor_ffi/database.dart';
import 'package:path/path.dart';
import 'package:test/test.dart';
void main() {
test("database can't be used after close", () {
final db = Database.memory();
db.execute('SELECT 1');
db.close();
expect(() => db.execute('SELECT 1'), throwsA(anything));
});
test('getUpdatedRows', () {
final db = Database.memory();
db
..execute('CREATE TABLE foo (bar INT);')
..execute('INSERT INTO foo VALUES (3);');
expect(db.getUpdatedRows(), 1);
});
test('closing multiple times works', () {
final db = Database.memory();
db.execute('SELECT 1');
db.close();
db.close(); // shouldn't throw or crash
});
test('throws exception on an invalid statement', () {
final db = Database.memory();
db.execute('CREATE TABLE foo (bar INTEGER CHECK (bar > 10));');
expect(
() => db.execute('INSERT INTO foo VALUES (3);'),
throwsA(const TypeMatcher<SqliteException>().having(
(e) => e.message, 'message', contains('CHECK constraint failed'))),
);
db.close();
});
test('throws when preparing an invalid statement', () {
final db = Database.memory();
expect(
() => db.prepare('INSERT INTO foo VALUES (3);'),
throwsA(const TypeMatcher<SqliteException>()
.having((e) => e.message, 'message', contains('no such table'))),
);
db.close();
});
test('open read-only', () async {
final path = join('.dart_tool', 'moor_ffi', 'test', 'read_only.db');
// Make sure the path exists
try {
await Directory(dirname(path)).create(recursive: true);
} catch (_) {}
// but not the db
try {
await File(path).delete();
} catch (_) {}
// Opening a non-existent database should fail
try {
Database.open(path, readOnly: true);
fail('should fail');
} on SqliteException catch (_) {}
// Open in read-write mode to create the database
var db = Database.open(path);
// Change the user version to test read-write access
db.setUserVersion(1);
db.close();
// Open in read-only
db = Database.open(path, readOnly: true);
// Change the user version to test read-only mode
try {
db.setUserVersion(2);
fail('should fail');
} on SqliteException catch (_) {}
// Check that it has not changed
expect(db.userVersion(), 1);
db.close();
});
}

View File

@ -1,16 +0,0 @@
import 'package:moor_ffi/database.dart';
import 'package:test/test.dart';
void main() {
test('insert statements report their id', () {
final opened = Database.memory();
opened.execute('CREATE TABLE tbl(a INTEGER PRIMARY KEY AUTOINCREMENT)');
for (var i = 0; i < 5; i++) {
opened.execute('INSERT INTO tbl DEFAULT VALUES');
expect(opened.getLastInsertId(), i + 1);
}
opened.close();
});
}

View File

@ -1,18 +0,0 @@
import 'package:moor_ffi/database.dart';
import 'package:test/test.dart';
void main() {
test('can bind and retreive 64 bit ints', () {
const value = 1 << 62;
final opened = Database.memory();
final stmt = opened.prepare('SELECT ?');
final result = stmt.select([value]);
expect(result, [
{'?': value}
]);
opened.close();
});
}

View File

@ -1,177 +0,0 @@
import 'dart:math';
import 'package:moor_ffi/database.dart';
import 'package:test/test.dart';
void main() {
Database db;
setUp(() => db = Database.memory()..enableMoorFfiFunctions());
tearDown(() => db.close());
dynamic selectSingle(String expression) {
final stmt = db.prepare('SELECT $expression AS r;');
final rows = stmt.select();
stmt.close();
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.close();
});
});
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),
];

View File

@ -1,147 +0,0 @@
import 'dart:ffi';
import 'dart:typed_data';
import 'package:moor_ffi/database.dart';
import 'package:test/test.dart';
void main() {
test('prepared statements can be used multiple times', () {
final opened = Database.memory();
opened.execute('CREATE TABLE tbl (a TEXT);');
final stmt = opened.prepare('INSERT INTO tbl(a) VALUES(?)');
stmt.execute(['a']);
stmt.execute(['b']);
stmt.close();
final select = opened.prepare('SELECT * FROM tbl ORDER BY a');
final result = select.select();
expect(result, hasLength(2));
expect(result.map((row) => row['a']), ['a', 'b']);
select.close();
opened.close();
});
test('prepared statements cannot be used after close', () {
final opened = Database.memory();
final stmt = opened.prepare('SELECT ?');
stmt.close();
expect(stmt.select, throwsA(anything));
opened.close();
});
test('prepared statements cannot be used after db is closed', () {
final opened = Database.memory();
final stmt = opened.prepare('SELECT 1');
opened.close();
expect(stmt.select, throwsA(anything));
});
Uint8List _insertBlob(Uint8List value) {
final opened = Database.memory();
opened.execute('CREATE TABLE tbl (x BLOB);');
final insert = opened.prepare('INSERT INTO tbl VALUES (?)');
insert.execute([value]);
insert.close();
final select = opened.prepare('SELECT * FROM tbl');
final result = select.select().single;
opened.close();
return result['x'] as Uint8List;
}
test('can bind empty blob in prepared statements', () {
expect(_insertBlob(Uint8List(0)), isEmpty);
});
test('can bind null blob in prepared statements', () {
expect(_insertBlob(null), isNull);
});
test('can bind and read non-empty blob', () {
const bytes = [1, 2, 3];
expect(_insertBlob(Uint8List.fromList(bytes)), bytes);
});
test('throws when sql statement has an error', () {
final db = Database.memory();
db.execute('CREATE TABLE foo (id INTEGER CHECK (id > 10));');
final stmt = db.prepare('INSERT INTO foo VALUES (9)');
expect(
stmt.execute,
throwsA(const TypeMatcher<SqliteException>()
.having((e) => e.message, 'message', contains('foo'))),
);
db.close();
});
test('throws an exception when iterating over result rows', () {
final db = Database.memory()
..createFunction(
'raise_if_two',
1,
Pointer.fromFunction(_raiseIfTwo),
);
db.execute(
'CREATE TABLE tbl (a INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT)');
// insert with a = 1..3
for (var i = 0; i < 3; i++) {
db.execute('INSERT INTO tbl DEFAULT VALUES');
}
final statement = db.prepare('SELECT raise_if_two(a) FROM tbl ORDER BY a');
expect(
statement.select,
throwsA(isA<SqliteException>()
.having((e) => e.message, 'message', contains('was two'))),
);
});
test('throws an exception when passing an invalid type as argument', () {
final db = Database.memory();
final stmt = db.prepare('SELECT ?');
expect(() => stmt.execute([false]), throwsArgumentError);
db.close();
});
group('asserts that the amount of parameters are correct', () {
final db = Database.memory();
test('when no parameters are set', () {
final stmt = db.prepare('SELECT ?');
expect(stmt.select, throwsA(isA<AssertionError>()));
});
test('when the wrong amount of parameters are set', () {
final stmt = db.prepare('SELECT ?, ?');
expect(() => stmt.select([1]), throwsA(isA<AssertionError>()));
});
tearDownAll(db.close);
});
}
void _raiseIfTwo(Pointer<FunctionContext> ctx, int argCount,
Pointer<Pointer<SqliteValue>> args) {
final value = args[0].value;
if (value == 2) {
ctx.resultError('parameter was two');
} else {
ctx.resultNull();
}
}

View File

@ -1,24 +0,0 @@
import 'package:moor_ffi/database.dart';
import 'package:test/test.dart';
void main() {
test('select statements return expected value', () {
final opened = Database.memory();
final prepared = opened.prepare('SELECT ?');
final result1 = prepared.select([1]);
expect(result1.columnNames, ['?']);
expect(result1.single.columnAt(0), 1);
final result2 = prepared.select([2]);
expect(result2.columnNames, ['?']);
expect(result2.single.columnAt(0), 2);
final result3 = prepared.select(['']);
expect(result3.columnNames, ['?']);
expect(result3.single.columnAt(0), '');
opened.close();
});
}

View File

@ -1,125 +0,0 @@
import 'dart:io';
import 'package:moor_ffi/database.dart';
import 'package:moor_ffi/src/bindings/constants.dart';
import 'package:path/path.dart';
import 'package:test/test.dart';
void main() {
test('open read-only exception', () async {
final path =
join('.dart_tool', 'moor_ffi', 'test', 'read_only_exception.db');
// Make sure the path exists
try {
await Directory(dirname(path)).create(recursive: true);
} catch (_) {}
// but not the db
try {
await File(path).delete();
} catch (_) {}
// Opening a non-existent database should fail
try {
Database.open(path, readOnly: true);
fail('should fail');
} on SqliteException catch (e) {
expect(e.extendedResultCode, Errors.SQLITE_CANTOPEN);
expect(e.toString(), startsWith('SqliteException(14): '));
}
});
test('statement exception', () async {
// Only testing some common errors...
final db = Database.memory();
// Basic syntax error
try {
db.execute('DUMMY');
fail('should fail');
} on SqliteException catch (e) {
expect(e.extendedResultCode, Errors.SQLITE_ERROR);
expect(e.resultCode, Errors.SQLITE_ERROR);
expect(e.toString(), startsWith('SqliteException(1): '));
}
// No table
try {
db.execute('SELECT * FROM missing_table');
fail('should fail');
} on SqliteException catch (e) {
expect(e.extendedResultCode, Errors.SQLITE_ERROR);
expect(e.resultCode, Errors.SQLITE_ERROR);
}
// Constraint primary key
db.execute('CREATE TABLE Test (name TEXT PRIMARY KEY)');
db.execute("INSERT INTO Test(name) VALUES('test1')");
try {
db.execute("INSERT INTO Test(name) VALUES('test1')");
fail('should fail');
} on SqliteException catch (e) {
// SQLITE_CONSTRAINT_PRIMARYKEY (1555)
expect(e.extendedResultCode, 1555);
expect(e.resultCode, Errors.SQLITE_CONSTRAINT);
expect(e.toString(), startsWith('SqliteException(1555): '));
}
// Constraint using prepared statement
db.execute('CREATE TABLE Test2 (id PRIMARY KEY, name TEXT UNIQUE)');
final prepared = db.prepare('INSERT INTO Test2(name) VALUES(?)');
prepared.execute(['test2']);
try {
prepared.execute(['test2']);
fail('should fail');
} on SqliteException catch (e) {
// SQLITE_CONSTRAINT_UNIQUE (2067)
expect(e.extendedResultCode, 2067);
expect(e.resultCode, Errors.SQLITE_CONSTRAINT);
}
db.close();
});
test('busy exception', () async {
final path = join('.dart_tool', 'moor_ffi', 'test', 'busy.db');
// Make sure the path exists
try {
await Directory(dirname(path)).create(recursive: true);
} catch (_) {}
// but not the db
try {
await File(path).delete();
} catch (_) {}
final db1 = Database.open(path);
final db2 = Database.open(path);
db1.execute('BEGIN EXCLUSIVE TRANSACTION');
try {
db2.execute('BEGIN EXCLUSIVE TRANSACTION');
fail('should fail');
} on SqliteException catch (e) {
expect(e.extendedResultCode, Errors.SQLITE_BUSY);
expect(e.resultCode, Errors.SQLITE_BUSY);
}
db1.close();
db2.close();
});
test('invalid format', () async {
final path = join('.dart_tool', 'moor_ffi', 'test', 'invalid_format.db');
// Make sure the path exists
try {
await Directory(dirname(path)).create(recursive: true);
} catch (_) {}
await File(path).writeAsString('not a database file');
final db = Database.open(path);
try {
db.setUserVersion(1);
fail('should fail');
} on SqliteException catch (e) {
expect(e.extendedResultCode, Errors.SQLITE_NOTADB);
expect(e.resultCode, Errors.SQLITE_NOTADB);
}
db.close();
});
}

View File

@ -1,105 +0,0 @@
import 'dart:ffi';
import 'package:moor/moor.dart';
import 'package:moor_ffi/database.dart';
import 'package:test/test.dart';
final _params = <dynamic>[];
void _testFunImpl(Pointer<FunctionContext> ctx, int argCount,
Pointer<Pointer<SqliteValue>> args) {
_params.clear();
for (var i = 0; i < argCount; i++) {
_params.add(args[i].value);
}
ctx.resultNull();
}
void _testNullImpl(Pointer<FunctionContext> ctx, int argCount,
Pointer<Pointer<SqliteValue>> args) {
ctx.resultNull();
}
void _testIntImpl(Pointer<FunctionContext> ctx, int argCount,
Pointer<Pointer<SqliteValue>> args) {
ctx.resultInt(420);
}
void _testDoubleImpl(Pointer<FunctionContext> ctx, int argCount,
Pointer<Pointer<SqliteValue>> args) {
ctx.resultDouble(133.7);
}
void main() {
test('can read arguments of user defined functions', () {
final db = Database.memory();
db.createFunction('test_fun', 6, Pointer.fromFunction(_testFunImpl));
db.execute(
r'''SELECT test_fun(1, 2.5, 'hello world', X'ff00ff', X'', NULL)''');
db.close();
expect(_params, [
1,
2.5,
'hello world',
Uint8List.fromList([255, 0, 255]),
Uint8List(0),
null,
]);
});
group('can return', () {
Database db;
setUp(() => db = Database.memory());
tearDown(() => db.close());
test('null', () {
db.createFunction('test_null', 0, Pointer.fromFunction(_testNullImpl));
final stmt = db.prepare('SELECT test_null() AS result');
expect(stmt.select(), [
{'result': null}
]);
});
test('integers', () {
db.createFunction('test_int', 0, Pointer.fromFunction(_testIntImpl));
final stmt = db.prepare('SELECT test_int() AS result');
expect(stmt.select(), [
{'result': 420}
]);
});
test('doubles', () {
db.createFunction(
'test_double', 0, Pointer.fromFunction(_testDoubleImpl));
final stmt = db.prepare('SELECT test_double() AS result');
expect(stmt.select(), [
{'result': 133.7}
]);
});
});
test('throws when using a long function name', () {
final db = Database.memory();
expect(
() => db.createFunction('foo' * 100, 10, nullptr), throwsArgumentError);
db.close();
});
test('throws when using an invalid argument count', () {
final db = Database.memory();
expect(() => db.createFunction('foo', -2, nullptr), throwsArgumentError);
expect(() => db.createFunction('foo', 128, nullptr), throwsArgumentError);
db.close();
});
}

View File

@ -1,29 +0,0 @@
import 'dart:io';
import 'package:moor_ffi/database.dart';
import 'package:test/test.dart';
import 'package:path/path.dart' as p;
void main() {
test('can set the user version on a database', () {
final file = File(p.join(
Directory.systemTemp.absolute.path, 'moor_ffi_test_user_version.db'));
final opened = Database.openFile(file);
var version = opened.userVersion();
expect(version, 0);
opened.setUserVersion(3);
version = opened.userVersion();
expect(version, 3);
// ensure that the version is stored on file
opened.close();
final another = Database.openFile(file);
expect(another.userVersion(), 3);
another.close();
file.deleteSync();
});
}

View File

@ -1,22 +0,0 @@
import 'package:moor/moor.dart';
import 'package:moor_ffi/src/ffi/blob.dart';
import 'package:moor_ffi/src/ffi/utils.dart';
import 'package:test/test.dart';
void main() {
test('utf8 store and load test', () {
const content = 'Hasta Mañana';
final blob = CBlob.allocateString(content);
expect(blob.readString(), content);
blob.free();
});
test('blob load and store test', () {
final data = List.generate(256, (x) => x);
final blob = CBlob.allocate(Uint8List.fromList(data));
expect(blob.readBytes(256), data);
blob.free();
});
}

View File

@ -1,24 +0,0 @@
import 'package:moor_ffi/database.dart';
import 'package:test/test.dart';
void main() {
Database database;
setUp(() => database = Database.memory());
tearDown(() => database.close());
test('violating constraint throws exception with extended error code', () {
database.execute('CREATE TABLE tbl(a INTEGER NOT NULL)');
final statement = database.prepare('INSERT INTO tbl DEFAULT VALUES');
expect(
statement.execute,
throwsA(
isA<SqliteException>().having(
(e) => e.explanation, 'explanation', endsWith(' (code 1299)')),
),
);
});
}

View File

@ -1,17 +0,0 @@
import 'package:test/test.dart';
import 'package:moor_ffi/open_helper.dart';
void main() {
tearDown(open.reset);
test('opening behavior can be overridden', () {
var called = false;
open.overrideFor(open.os, () {
called = true;
return null;
});
expect(open.openSqlite(), isNull);
expect(called, isTrue);
});
}