Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added commit / rollback hooks #268

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions sqlite3/assets/sqlite3.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ void *sqlite3_update_hook(sqlite3 *,
void (*)(void *, int, sqlite3_char const *,
sqlite3_char const *, int64_t),
void *);
void *sqlite3_commit_hook(sqlite3*, int(*)(void *), void*);
void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
int sqlite3_get_autocommit(sqlite3 *db);

// Statements
Expand Down
10 changes: 6 additions & 4 deletions sqlite3/assets/wasm/bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@ import_dart("function_xInverse") extern void dartXInverse(
import_dart("function_xFinal") extern void dartXFinal(sqlite3_context *ctx);
import_dart("function_xValue") extern void dartXValue(sqlite3_context *ctx);
import_dart("function_forget") extern void dartForgetAboutFunction(void *ptr);
import_dart("function_hook") extern void dartUpdateHook(void *id, int kind,
const char *db,
const char *table,
sqlite3_int64 rowid);
import_dart("function_update_hook") extern void dartUpdateHook(void *id, int kind,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep the existing name unchanged, newer versions of package:sqlite3 need to be compatible with older WebAssembly bundles too.

const char *db,
const char *table,
sqlite3_int64 rowid);
import_dart("function_commit_hook") extern int dartCommitHook(void *id);
import_dart("function_rollback_hook") extern void dartRollbackHook(void *id);
import_dart("function_compare") extern int dartXCompare(void *id, int lengthA,
const void *a,
int lengthB,
Expand Down
8 changes: 8 additions & 0 deletions sqlite3/assets/wasm/helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,14 @@ SQLITE_API void dart_sqlite3_updates(sqlite3 *db, int id) {
sqlite3_update_hook(db, id >= 0 ? &dartUpdateHook : NULL, (void *)id);
}

SQLITE_API void dart_sqlite3_commits(sqlite3 *db, int id) {
sqlite3_commit_hook(db, id >= 0 ? &dartCommitHook : NULL, (void *)id);
}

SQLITE_API void dart_sqlite3_rollbacks(sqlite3 *db, int id) {
sqlite3_rollback_hook(db, id >= 0 ? &dartRollbackHook : NULL, (void *)id);
}

SQLITE_API int dart_sqlite3_create_collation(sqlite3 *db, const char *zName,
int eTextRep, int id) {
return sqlite3_create_collation_v2(db, zName, eTextRep, (void *)id,
Expand Down
29 changes: 29 additions & 0 deletions sqlite3/lib/src/database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,35 @@ abstract class CommonDatabase {
/// - [Data Change Notification Callbacks](https://www.sqlite.org/c3ref/update_hook.html)
Stream<SqliteUpdate> get updates;

/// The [VoidPredicate] that is used to filter out transactions before commiting.
///
/// This is run before every commit, i.e. before the end of an explicit
/// transaction and before the end of an implicit transactions created by
/// an insert / update / delete operation.
///
/// If the filter returns `false`, the commit is converted into a rollback.
///
/// The function should not do anything that modifies the database connection,
/// e.g. run SQL statements, prepare statements or step.
///
/// See also:
/// - [Commit Hooks](https://www.sqlite.org/c3ref/commit_hook.html)
VoidPredicate? get commitFilter;
set commitFilter(VoidPredicate? commitFilter);

/// An async stream that fires after each rollback.
///
/// Listening to this stream will register an "update hook" on the native
/// database. Each rollback that sqlite3 reports through that hook will then
/// be added to the stream.
///
/// Note that the stream reports updates _asynchronously_, e.g. one event
/// loop iteration after sqlite reports them.
///
/// See also:
/// - [Commit Hooks](https://www.sqlite.org/c3ref/commit_hook.html)
Stream<void> get rollbacks;

/// Executes the [sql] statement with the provided [parameters], ignoring any
/// rows returned by the statement.
///
Expand Down
56 changes: 56 additions & 0 deletions sqlite3/lib/src/ffi/bindings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,8 @@ final class FfiDatabase extends RawSqliteDatabase {
final BindingsWithLibrary bindings;
final Pointer<sqlite3> db;
NativeCallable<_UpdateHook>? _installedUpdateHook;
NativeCallable<_CommitHook>? _installedCommitHook;
NativeCallable<_RollbackHook>? _installedRollbackHook;

FfiDatabase(this.bindings, this.db);

Expand Down Expand Up @@ -560,6 +562,37 @@ final class FfiDatabase extends RawSqliteDatabase {
previous?.close();
}

@override
void sqlite3_commit_hook(RawCommitHook? hook) {
final previous = _installedCommitHook;

if (hook == null) {
_installedCommitHook = null;
bindings.bindings.sqlite3_commit_hook(db, nullPtr(), nullPtr());
} else {
final native = _installedCommitHook = hook.toNative();
bindings.bindings
.sqlite3_commit_hook(db, native.nativeFunction, nullPtr());
}

previous?.close();
}

@override
void sqlite3_rollback_hook(RawRollbackHook? hook) {
final previous = _installedRollbackHook;

if (hook == null) {
bindings.bindings.sqlite3_rollback_hook(db, nullPtr(), nullPtr());
} else {
final native = _installedRollbackHook = hook.toNative();
bindings.bindings
.sqlite3_rollback_hook(db, native.nativeFunction, nullPtr());
}

previous?.close();
}

@override
int sqlite3_db_config(int op, int value) {
final result = bindings.bindings.sqlite3_db_config(
Expand Down Expand Up @@ -970,6 +1003,8 @@ typedef _XCompare = Int Function(
Pointer<Void>, Int, Pointer<Void>, Int, Pointer<Void>);
typedef _UpdateHook = Void Function(
Pointer<Void>, Int, Pointer<sqlite3_char>, Pointer<sqlite3_char>, Int64);
typedef _CommitHook = Int Function(Pointer<Void>);
typedef _RollbackHook = Void Function(Pointer<Void>);

extension on RawXFunc {
NativeCallable<_XFunc> toNative(Bindings bindings) {
Expand Down Expand Up @@ -1023,3 +1058,24 @@ extension on RawUpdateHook {
)..keepIsolateAlive = false;
}
}

extension on RawCommitHook {
NativeCallable<_CommitHook> toNative() {
return NativeCallable.isolateLocal(
(Pointer<Void> _) {
return this();
},
exceptionalReturn: 1,
)..keepIsolateAlive = false;
}
}

extension on RawRollbackHook {
NativeCallable<_RollbackHook> toNative() {
return NativeCallable.isolateLocal(
(Pointer<Void> _) {
this();
},
)..keepIsolateAlive = false;
}
}
54 changes: 54 additions & 0 deletions sqlite3/lib/src/ffi/sqlite3.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,60 @@ class Bindings {
ffi.Int64)>>,
ffi.Pointer<ffi.Void>)>();

ffi.Pointer<ffi.Void> sqlite3_commit_hook(
ffi.Pointer<sqlite3> arg0,
ffi.Pointer<ffi.NativeFunction<ffi.Int Function(ffi.Pointer<ffi.Void>)>>
arg1,
ffi.Pointer<ffi.Void> arg2,
) {
return _sqlite3_commit_hook(
arg0,
arg1,
arg2,
);
}

late final _sqlite3_commit_hookPtr = _lookup<
ffi.NativeFunction<
ffi.Pointer<ffi.Void> Function(
ffi.Pointer<sqlite3>,
ffi.Pointer<
ffi.NativeFunction<ffi.Int Function(ffi.Pointer<ffi.Void>)>>,
ffi.Pointer<ffi.Void>)>>('sqlite3_commit_hook');
late final _sqlite3_commit_hook = _sqlite3_commit_hookPtr.asFunction<
ffi.Pointer<ffi.Void> Function(
ffi.Pointer<sqlite3>,
ffi.Pointer<
ffi.NativeFunction<ffi.Int Function(ffi.Pointer<ffi.Void>)>>,
ffi.Pointer<ffi.Void>)>();

ffi.Pointer<ffi.Void> sqlite3_rollback_hook(
ffi.Pointer<sqlite3> arg0,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>
arg1,
ffi.Pointer<ffi.Void> arg2,
) {
return _sqlite3_rollback_hook(
arg0,
arg1,
arg2,
);
}

late final _sqlite3_rollback_hookPtr = _lookup<
ffi.NativeFunction<
ffi.Pointer<ffi.Void> Function(
ffi.Pointer<sqlite3>,
ffi.Pointer<
ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>,
ffi.Pointer<ffi.Void>)>>('sqlite3_rollback_hook');
late final _sqlite3_rollback_hook = _sqlite3_rollback_hookPtr.asFunction<
ffi.Pointer<ffi.Void> Function(
ffi.Pointer<sqlite3>,
ffi.Pointer<
ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>,
ffi.Pointer<ffi.Void>)>();

int sqlite3_get_autocommit(
ffi.Pointer<sqlite3> db,
) {
Expand Down
3 changes: 3 additions & 0 deletions sqlite3/lib/src/functions.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import 'package:meta/meta.dart';

/// A filter function without any arguments.
typedef VoidPredicate = bool Function();

/// A collating function provided to a sql collation.
///
/// The function must return a `int`.
Expand Down
6 changes: 6 additions & 0 deletions sqlite3/lib/src/implementation/bindings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ typedef RawXFunc = void Function(RawSqliteContext, List<RawSqliteValue>);
typedef RawXStep = void Function(RawSqliteContext, List<RawSqliteValue>);
typedef RawXFinal = void Function(RawSqliteContext);
typedef RawUpdateHook = void Function(int kind, String tableName, int rowId);
typedef RawCommitHook = int Function();
typedef RawRollbackHook = void Function();
typedef RawCollation = int Function(String? a, String? b);

abstract base class RawSqliteDatabase {
Expand All @@ -79,6 +81,10 @@ abstract base class RawSqliteDatabase {

void sqlite3_update_hook(RawUpdateHook? hook);

void sqlite3_commit_hook(RawCommitHook? hook);

void sqlite3_rollback_hook(RawRollbackHook? hook);

/// Returns a compiler able to create prepared statements from the utf8-
/// encoded SQL string passed as its argument.
RawStatementCompiler newCompiler(List<int> utf8EncodedSql);
Expand Down
58 changes: 58 additions & 0 deletions sqlite3/lib/src/implementation/database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ base class DatabaseImplementation implements CommonDatabase {
final FinalizableDatabase finalizable;

final List<MultiStreamController<SqliteUpdate>> _updateListeners = [];
final List<MultiStreamController<void>> _rollbackListeners = [];

VoidPredicate? _commitFilter;

var _isClosed = false;

Expand Down Expand Up @@ -228,6 +231,8 @@ base class DatabaseImplementation implements CommonDatabase {
listener.close();
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should also close the rollback listeners here.

database.sqlite3_update_hook(null);
database.sqlite3_commit_hook(null);
database.sqlite3_rollback_hook(null);

finalizable.dispose();
}
Expand Down Expand Up @@ -459,6 +464,59 @@ base class DatabaseImplementation implements CommonDatabase {
isBroadcast: true,
);
}

@override
Stream<void> get rollbacks {
return Stream.multi(
(newListener) {
if (_isClosed) {
newListener.closeSync();
return;
}

void addRollbackListener() {
final isFirstListener = _rollbackListeners.isEmpty;
_rollbackListeners.add(newListener);

if (isFirstListener) {
// Add native rollback hook
database.sqlite3_rollback_hook(() {
for (final listener in _rollbackListeners) {
listener.add(null);
}
});
}
}

void removeRollbackListener() {
_rollbackListeners.remove(newListener);

if (_rollbackListeners.isEmpty && !_isClosed) {
database.sqlite3_rollback_hook(null); // Remove native hook
}
}

newListener
..onPause = removeRollbackListener
..onCancel = removeRollbackListener
..onResume = addRollbackListener;

// Since this is a onListen callback, add listener now
addRollbackListener();
},
isBroadcast: true,
);
}

@override
VoidPredicate? get commitFilter => _commitFilter;

@override
set commitFilter(VoidPredicate? commitFilter) {
_commitFilter = commitFilter;
database.sqlite3_commit_hook(
commitFilter == null ? null : () => commitFilter() ? 0 : 1);
}
}

extension on RawSqliteContext {
Expand Down
14 changes: 14 additions & 0 deletions sqlite3/lib/src/wasm/bindings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,20 @@ final class WasmDatabase extends RawSqliteDatabase {
bindings.dart_sqlite3_updates(db, hook != null ? 1 : -1);
}

@override
void sqlite3_commit_hook(RawCommitHook? hook) {
bindings.callbacks.installedCommitHook = hook;

bindings.dart_sqlite3_commits(db, hook != null ? 1 : -1);
}

@override
void sqlite3_rollback_hook(RawRollbackHook? hook) {
bindings.callbacks.installedRollbackHook = hook;

bindings.dart_sqlite3_rollbacks(db, hook != null ? 1 : -1);
}

@override
int sqlite3_get_autocommit() {
return bindings.sqlite3_get_autocommit(db);
Expand Down
Loading