Skip to content

chore: folder structure changes #40

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

Merged
merged 2 commits into from
Apr 16, 2024
Merged
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ pubspec.lock
test-db
sqlite-autoconf-*
doc

build
11 changes: 6 additions & 5 deletions example/custom_functions_example.dart
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import 'dart:async';
import 'dart:io';
import 'dart:isolate';

import 'package:sqlite3/common.dart';
import 'package:sqlite_async/sqlite_async.dart';
import 'package:sqlite3/sqlite3.dart' as sqlite;

/// Since the functions need to be created on every SQLite connection,
/// we do this in a SqliteOpenFactory.
class TestOpenFactory extends DefaultSqliteOpenFactory {
TestOpenFactory({required super.path, super.sqliteOptions});

@override
sqlite.Database open(SqliteOpenOptions options) {
final db = super.open(options);
FutureOr<CommonDatabase> open(SqliteOpenOptions options) async {
final db = await super.open(options);

db.createFunction(
functionName: 'sleep',
argumentCount: const sqlite.AllowedArgumentCount(1),
argumentCount: const AllowedArgumentCount(1),
function: (args) {
final millis = args[0] as int;
sleep(Duration(milliseconds: millis));
Expand All @@ -25,7 +26,7 @@ class TestOpenFactory extends DefaultSqliteOpenFactory {

db.createFunction(
functionName: 'isolate_name',
argumentCount: const sqlite.AllowedArgumentCount(0),
argumentCount: const AllowedArgumentCount(0),
function: (args) {
return Isolate.current.debugName;
},
Expand Down
5 changes: 3 additions & 2 deletions example/linux_cli_example.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import 'dart:async';
import 'dart:ffi';

import 'package:sqlite3/common.dart';
import 'package:sqlite_async/sqlite_async.dart';
import 'package:sqlite3/open.dart' as sqlite_open;
import 'package:sqlite3/sqlite3.dart' as sqlite;

const defaultSqlitePath = 'libsqlite3.so.0';

Expand All @@ -16,7 +17,7 @@ class TestOpenFactory extends DefaultSqliteOpenFactory {
this.sqlitePath = defaultSqlitePath});

@override
sqlite.Database open(SqliteOpenOptions options) {
FutureOr<CommonDatabase> open(SqliteOpenOptions options) async {
// For details, see:
// https://pub.dev/packages/sqlite3#manually-providing-sqlite3-libraries
sqlite_open.open.overrideFor(sqlite_open.OperatingSystem.linux, () {
Expand Down
2 changes: 2 additions & 0 deletions lib/sqlite3_common.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Exports common Sqlite3 exports which are available in different environments.
export 'package:sqlite3/common.dart';
6 changes: 6 additions & 0 deletions lib/sqlite_async.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
/// See [SqliteDatabase] as a starting point.
library;

export 'src/common/abstract_open_factory.dart';
export 'src/common/connection/sync_sqlite_connection.dart';
export 'src/common/isolate_connection_factory.dart';
export 'src/common/mutex.dart';
export 'src/common/port_channel.dart';
export 'src/common/sqlite_database.dart';
export 'src/isolate_connection_factory.dart';
export 'src/sqlite_connection.dart';
export 'src/sqlite_database.dart';
Expand Down
113 changes: 113 additions & 0 deletions lib/src/common/abstract_open_factory.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import 'dart:async';
import 'package:meta/meta.dart';

import 'package:sqlite_async/sqlite3_common.dart' as sqlite;
import 'package:sqlite_async/src/common/mutex.dart';
import 'package:sqlite_async/src/sqlite_connection.dart';
import 'package:sqlite_async/src/sqlite_options.dart';
import 'package:sqlite_async/src/update_notification.dart';

/// Factory to create new SQLite database connections.
///
/// Since connections are opened in dedicated background isolates, this class
/// must be safe to pass to different isolates.
abstract class SqliteOpenFactory<Database extends sqlite.CommonDatabase> {
String get path;

/// Opens a direct connection to the SQLite database
FutureOr<Database> open(SqliteOpenOptions options);

/// Opens an asynchronous [SqliteConnection]
FutureOr<SqliteConnection> openConnection(SqliteOpenOptions options);
}

class SqliteOpenOptions {
/// Whether this is the primary write connection for the database.
final bool primaryConnection;

/// Whether this connection is read-only.
final bool readOnly;

/// Mutex to use in [SqliteConnection]s
final Mutex? mutex;

/// Name used in debug logs
final String? debugName;

/// Stream of external update notifications
final Stream<UpdateNotification>? updates;

const SqliteOpenOptions(
{required this.primaryConnection,
required this.readOnly,
this.mutex,
this.debugName,
this.updates});

sqlite.OpenMode get openMode {
if (primaryConnection) {
return sqlite.OpenMode.readWriteCreate;
} else if (readOnly) {
return sqlite.OpenMode.readOnly;
} else {
return sqlite.OpenMode.readWrite;
}
}
}

/// The default database factory.
///
/// This takes care of opening the database, and running PRAGMA statements
/// to configure the connection.
///
/// Override the [open] method to customize the process.
abstract class AbstractDefaultSqliteOpenFactory<
Database extends sqlite.CommonDatabase>
implements SqliteOpenFactory<Database> {
@override
final String path;
final SqliteOptions sqliteOptions;

const AbstractDefaultSqliteOpenFactory(
{required this.path,
this.sqliteOptions = const SqliteOptions.defaults()});

List<String> pragmaStatements(SqliteOpenOptions options);

@protected

/// Opens a direct connection to a SQLite database connection
FutureOr<Database> openDB(SqliteOpenOptions options);

@override

/// Opens a direct connection to a SQLite database connection
/// and executes setup pragma statements to initialize the DB
FutureOr<Database> open(SqliteOpenOptions options) async {
var db = await openDB(options);

// Pragma statements don't have the same BUSY_TIMEOUT behavior as normal statements.
// We add a manual retry loop for those.
for (var statement in pragmaStatements(options)) {
for (var tries = 0; tries < 30; tries++) {
try {
db.execute(statement);
break;
} on sqlite.SqliteException catch (e) {
if (e.resultCode == sqlite.SqlError.SQLITE_BUSY && tries < 29) {
continue;
} else {
rethrow;
}
}
}
}
return db;
}

@override

/// Opens an asynchronous [SqliteConnection] to a SQLite database
/// and executes setup pragma statements to initialize the DB
FutureOr<SqliteConnection> openConnection(SqliteOpenOptions options);
}
116 changes: 116 additions & 0 deletions lib/src/common/connection/sync_sqlite_connection.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import 'package:sqlite3/common.dart';
import 'package:sqlite_async/src/common/mutex.dart';
import 'package:sqlite_async/src/sqlite_connection.dart';
import 'package:sqlite_async/src/sqlite_queries.dart';
import 'package:sqlite_async/src/update_notification.dart';

/// A simple "synchronous" connection which provides the async SqliteConnection
/// implementation using a synchronous SQLite connection
class SyncSqliteConnection extends SqliteConnection with SqliteQueries {
final CommonDatabase db;
late Mutex mutex;
@override
late final Stream<UpdateNotification> updates;

bool _closed = false;

SyncSqliteConnection(this.db, Mutex m) {
mutex = m.open();
updates = db.updates.map(
(event) {
return UpdateNotification({event.tableName});
},
);
}

@override
Future<T> readLock<T>(Future<T> Function(SqliteReadContext tx) callback,
{Duration? lockTimeout, String? debugContext}) {
return mutex.lock(() => callback(SyncReadContext(db)),
timeout: lockTimeout);
}

@override
Future<T> writeLock<T>(Future<T> Function(SqliteWriteContext tx) callback,
{Duration? lockTimeout, String? debugContext}) {
return mutex.lock(() => callback(SyncWriteContext(db)),
timeout: lockTimeout);
}

@override
Future<void> close() async {
_closed = true;
return db.dispose();
}

@override
bool get closed => _closed;

@override
Future<bool> getAutoCommit() async {
return db.autocommit;
}
}

class SyncReadContext implements SqliteReadContext {
CommonDatabase db;

SyncReadContext(this.db);

@override
Future<T> computeWithDatabase<T>(
Future<T> Function(CommonDatabase db) compute) {
return compute(db);
}

@override
Future<Row> get(String sql, [List<Object?> parameters = const []]) async {
return db.select(sql, parameters).first;
}

@override
Future<ResultSet> getAll(String sql,
[List<Object?> parameters = const []]) async {
return db.select(sql, parameters);
}

@override
Future<Row?> getOptional(String sql,
[List<Object?> parameters = const []]) async {
final rows = await getAll(sql, parameters);
return rows.isEmpty ? null : rows.first;
}

@override
bool get closed => false;

@override
Future<bool> getAutoCommit() async {
return db.autocommit;
}
}

class SyncWriteContext extends SyncReadContext implements SqliteWriteContext {
SyncWriteContext(super.db);

@override
Future<ResultSet> execute(String sql,
[List<Object?> parameters = const []]) async {
return db.select(sql, parameters);
}

@override
Future<void> executeBatch(
String sql, List<List<Object?>> parameterSets) async {
return computeWithDatabase((db) async {
final statement = db.prepare(sql, checkNoTail: true);
try {
for (var parameters in parameterSets) {
statement.execute(parameters);
}
} finally {
statement.dispose();
}
});
}
}
46 changes: 46 additions & 0 deletions lib/src/common/isolate_connection_factory.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import 'dart:async';
import 'package:sqlite_async/sqlite3_common.dart' as sqlite;
import 'package:sqlite_async/src/common/mutex.dart';
import 'package:sqlite_async/src/common/abstract_open_factory.dart';
import 'package:sqlite_async/src/impl/isolate_connection_factory_impl.dart';
import 'package:sqlite_async/src/sqlite_connection.dart';
import 'port_channel.dart';

mixin IsolateOpenFactoryMixin<Database extends sqlite.CommonDatabase> {
AbstractDefaultSqliteOpenFactory<Database> get openFactory;

/// Opens a synchronous sqlite.Database directly in the current isolate.
///
/// This gives direct access to the database, but:
/// 1. No app-level locking is performed automatically. Transactions may fail
/// with SQLITE_BUSY if another isolate is using the database at the same time.
/// 2. Other connections are not notified of any updates to tables made within
/// this connection.
FutureOr<Database> openRawDatabase({bool readOnly = false}) async {
return openFactory
.open(SqliteOpenOptions(primaryConnection: false, readOnly: readOnly));
}
}

/// A connection factory that can be passed to different isolates.
abstract class IsolateConnectionFactory<Database extends sqlite.CommonDatabase>
with IsolateOpenFactoryMixin {
Mutex get mutex;

SerializedPortClient get upstreamPort;

factory IsolateConnectionFactory(
{required openFactory,
required mutex,
required SerializedPortClient upstreamPort}) {
return IsolateConnectionFactoryImpl(
openFactory: openFactory,
mutex: mutex,
upstreamPort: upstreamPort) as IsolateConnectionFactory<Database>;
}

/// Open a new SqliteConnection.
///
/// This opens a single connection in a background execution isolate.
SqliteConnection open({String? debugName, bool readOnly = false});
}
32 changes: 32 additions & 0 deletions lib/src/common/mutex.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'package:sqlite_async/src/impl/mutex_impl.dart';

abstract class Mutex {
factory Mutex() {
return MutexImpl();
}

/// timeout is a timeout for acquiring the lock, not for the callback
Future<T> lock<T>(Future<T> Function() callback, {Duration? timeout});

/// Use [open] to get a [AbstractMutex] instance.
/// This is mainly used for shared mutexes
Mutex open() {
return this;
}

/// Release resources used by the Mutex.
///
/// Subsequent calls to [lock] may fail, or may never call the callback.
Future<void> close();
}

class LockError extends Error {
final String message;

LockError(this.message);

@override
String toString() {
return 'LockError: $message';
}
}
File renamed without changes.
Loading