Skip to content

Commit

Permalink
feat(op-sqlite): allow extensions to be loaded (#469)
Browse files Browse the repository at this point in the history
  • Loading branch information
DominicGBauer authored Jan 21, 2025
1 parent 6580f29 commit 3a37054
Show file tree
Hide file tree
Showing 13 changed files with 742 additions and 1,794 deletions.
6 changes: 6 additions & 0 deletions .changeset/hip-poems-repair.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@powersync/op-sqlite': minor
---

* Allow users to load additional sqlite extensions
* Remove `getBundledPath` function as `getDylibPath` can now be used instead
36 changes: 36 additions & 0 deletions packages/powersync-op-sqlite/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,42 @@ const factory = new OPSqliteOpenFactory({
});
```

### Loading SQLite extensions

To load additional SQLite extensions include the `extensions` option in `sqliteOptions` which expects an array of objects with a `path` and an `entryPoint`:

```js
sqliteOptions: {
extensions: [
{ path: libPath, entryPoint: 'sqlite3_powersync_init' }
]
}
```

More info can be found in the [OP-SQLite docs](https://op-engineering.github.io/op-sqlite/docs/api/#loading-extensions).

Example usage:

```ts
import { getDylibPath } from '@op-engineering/op-sqlite';

let libPath: string
if (Platform.OS === 'ios') {
libPath = getDylibPath('co.powersync.sqlitecore', 'powersync-sqlite-core')
} else {
libPath = 'libpowersync';
}

const factory = new OPSqliteOpenFactory({
dbFilename: 'sqlite.db',
sqliteOptions: {
extensions: [
{ path: libPath, entryPoint: 'sqlite3_powersync_init' }
]
}
});
```

## Native Projects

This package uses native libraries. Create native Android and iOS projects (if not created already) by running:
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,16 @@ import java.util.HashMap

class PowerSyncOpSqlitePackage : TurboReactPackage() {
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
return if (name == PowerSyncOpSqliteModule.NAME) {
PowerSyncOpSqliteModule(reactContext)
} else {
null
}
return null
}

override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
return ReactModuleInfoProvider {
val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
val isTurboModule: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
moduleInfos[PowerSyncOpSqliteModule.NAME] = ReactModuleInfo(
PowerSyncOpSqliteModule.NAME,
PowerSyncOpSqliteModule.NAME,
moduleInfos["PowerSyncOpSqlite"] = ReactModuleInfo(
"PowerSyncOpSqlite",
"PowerSyncOpSqlite",
false, // canOverrideExistingModule
false, // needsEagerInit
true, // hasConstants
Expand Down

This file was deleted.

This file was deleted.

5 changes: 0 additions & 5 deletions packages/powersync-op-sqlite/ios/PowerSyncOpSqlite.mm
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,4 @@
@implementation PowerSyncOpSqlite
RCT_EXPORT_MODULE()

RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getBundlePath)
{
return [NSBundle mainBundle].bundlePath;
}

@end
8 changes: 0 additions & 8 deletions packages/powersync-op-sqlite/src/NativePowerSyncOpSqlite.ts

This file was deleted.

47 changes: 35 additions & 12 deletions packages/powersync-op-sqlite/src/db/OPSqliteAdapter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
import { BaseObserver, DBAdapter, DBAdapterListener, DBLockOptions, QueryResult, Transaction } from '@powersync/common';
import { ANDROID_DATABASE_PATH, IOS_LIBRARY_PATH, open, type DB } from '@op-engineering/op-sqlite';
import {
BaseObserver,
DBAdapter,
DBAdapterListener,
DBLockOptions,
QueryResult,
Transaction
} from '@powersync/common';
import {
ANDROID_DATABASE_PATH,
getDylibPath,
IOS_LIBRARY_PATH,
open,
type DB
} from '@op-engineering/op-sqlite';
import Lock from 'async-lock';
import { OPSQLiteConnection } from './OPSQLiteConnection';
import { NativeModules, Platform } from 'react-native';
import { Platform } from 'react-native';
import { SqliteOptions } from './SqliteOptions';

/**
Expand Down Expand Up @@ -44,7 +57,7 @@ export class OPSQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement
}

protected async init() {
const { lockTimeoutMs, journalMode, journalSizeLimit, synchronous, encryptionKey } = this.options.sqliteOptions;
const { lockTimeoutMs, journalMode, journalSizeLimit, synchronous } = this.options.sqliteOptions!;
const dbFilename = this.options.name;

this.writeConnection = await this.openConnection(dbFilename);
Expand Down Expand Up @@ -86,10 +99,11 @@ export class OPSQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement

protected async openConnection(filenameOverride?: string): Promise<OPSQLiteConnection> {
const dbFilename = filenameOverride ?? this.options.name;
const DB: DB = this.openDatabase(dbFilename, this.options.sqliteOptions.encryptionKey);
const DB: DB = this.openDatabase(dbFilename, this.options.sqliteOptions?.encryptionKey ?? undefined);

//Load extension for all connections
this.loadExtension(DB);
//Load extensions for all connections
this.loadAdditionalExtensions(DB);
this.loadPowerSyncExtension(DB);

await DB.execute('SELECT powersync_init()');

Expand Down Expand Up @@ -124,10 +138,17 @@ export class OPSQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement
}
}

private loadExtension(DB: DB) {
private loadAdditionalExtensions(DB: DB) {
if (this.options.sqliteOptions?.extensions && this.options.sqliteOptions.extensions.length > 0) {
for (const extension of this.options.sqliteOptions.extensions) {
DB.loadExtension(extension.path, extension.entryPoint);
}
}
}

private async loadPowerSyncExtension(DB: DB) {
if (Platform.OS === 'ios') {
const bundlePath: string = NativeModules.PowerSyncOpSqlite.getBundlePath();
const libPath = `${bundlePath}/Frameworks/powersync-sqlite-core.framework/powersync-sqlite-core`;
const libPath = getDylibPath('co.powersync.sqlitecore', 'powersync-sqlite-core');
DB.loadExtension(libPath, 'sqlite3_powersync_init');
} else {
DB.loadExtension('libpowersync', 'sqlite3_powersync_init');
Expand Down Expand Up @@ -271,8 +292,10 @@ export class OPSQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement
await this.initialized;
await this.writeConnection!.refreshSchema();

for (let readConnection of this.readConnections) {
await readConnection.connection.refreshSchema();
if(this.readConnections) {
for (let readConnection of this.readConnections) {
await readConnection.connection.refreshSchema();
}
}
}
}
14 changes: 12 additions & 2 deletions packages/powersync-op-sqlite/src/db/SqliteOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,16 @@ export interface SqliteOptions {
* Encryption key for the database.
* If set, the database will be encrypted using SQLCipher.
*/
encryptionKey?: string;
encryptionKey?: string | null;

/**
* Load extensions using the path and entryPoint.
* More info can be found here https://op-engineering.github.io/op-sqlite/docs/api#loading-extensions.
*/
extensions?: Array<{
path: string;
entryPoint?: string;
}>;
}

// SQLite journal mode. Set on the primary connection.
Expand Down Expand Up @@ -57,5 +66,6 @@ export const DEFAULT_SQLITE_OPTIONS: Required<SqliteOptions> = {
synchronous: SqliteSynchronous.normal,
journalSizeLimit: 6 * 1024 * 1024,
lockTimeoutMs: 30000,
encryptionKey: null
encryptionKey: null,
extensions: []
};
34 changes: 4 additions & 30 deletions packages/powersync-op-sqlite/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,4 @@
import { NativeModules, Platform } from 'react-native';

const LINKING_ERROR =
`The package '@powersync/op-sqlite' doesn't seem to be linked. Make sure: \n\n` +
Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
'- You rebuilt the app after installing the package\n' +
'- You are not using Expo Go\n';

const isTurboModuleEnabled = global.__turboModuleProxy != null;

const PowerSyncOpSqliteModule = isTurboModuleEnabled
? require('./NativePowerSyncOpSqlite').default
: NativeModules.PowerSyncOpSqlite;

const PowerSyncOpSqlite = PowerSyncOpSqliteModule
? PowerSyncOpSqliteModule
: new Proxy(
{},
{
get() {
throw new Error(LINKING_ERROR);
}
}
);

export function getBundlePath(): string {
return PowerSyncOpSqlite.getBundlePath();
}

export { OPSqliteOpenFactory, OPSQLiteOpenFactoryOptions } from './db/OPSqliteDBOpenFactory';
export {
OPSqliteOpenFactory,
OPSQLiteOpenFactoryOptions
} from './db/OPSqliteDBOpenFactory';
1 change: 1 addition & 0 deletions packages/powersync-op-sqlite/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"noEmit": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"strict": true,
"esModuleInterop": true
},
"references": [
Expand Down
Loading

0 comments on commit 3a37054

Please sign in to comment.