Skip to content

Commit 7795344

Browse files
committed
feat: added support to load extension
1 parent a292f5a commit 7795344

File tree

11 files changed

+128
-1
lines changed

11 files changed

+128
-1
lines changed

README.md

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ db = {
7171
executeBatch: (commands: BatchQueryCommand[]) => BatchQueryResult,
7272
executeBatchAsync: (commands: BatchQueryCommand[]) => Promise<BatchQueryResult>,
7373
loadFile: (location: string) => FileLoadResult;,
74-
loadFileAsync: (location: string) => Promise<FileLoadResult>
74+
loadFileAsync: (location: string) => Promise<FileLoadResult>,
75+
loadExtension: (path: string, entryPoint?: string) => void
7576
}
7677
```
7778

@@ -249,6 +250,78 @@ You can, at any moment, detach a database that you don't need anymore. You don't
249250

250251
SQLite has a limit for attached databases: A default of 10, and a global max of 125
251252

253+
## Loading SQLite Extensions
254+
255+
SQLite supports loading extensions that can add new functions, virtual tables, collations, and more to your database connection. NitroSQLite provides the `loadExtension` method to dynamically load SQLite extensions.
256+
257+
```typescript
258+
import { open } from 'react-native-nitro-sqlite';
259+
260+
try {
261+
const db = open({ name: 'myDb.sqlite' });
262+
263+
// Load the extension
264+
db.loadExtension('/path/to/extension');
265+
266+
// Now you can use the extension's functionality
267+
const result = db.execute('SELECT extension_function(?) as result', [someValue]);
268+
} catch (e) {
269+
console.error('Failed to load extension:', e.message);
270+
}
271+
```
272+
273+
### Setting up extensions in your project
274+
275+
To use SQLite extensions with your app, you need to:
276+
277+
#### Android
278+
1. Place extension files (`.so` files) in your app's assets folder
279+
2. Add these files to your Android project
280+
3. At runtime, copy the file from assets to a readable location before loading
281+
282+
#### iOS
283+
1. Add extension files (`.dylib` files) to your Xcode project
284+
2. Make sure they're included in "Build Phases → Copy Bundle Resources"
285+
3. At runtime, the files will be available in your app's bundle
286+
287+
### Implementation example
288+
289+
```typescript
290+
async loadExtension(): Promise<void> {
291+
try {
292+
const appDir = getAppDirectory();
293+
294+
if (Platform.OS === 'android') {
295+
const libFileName = `libextension.so`;
296+
const destinationPath = `${appDir}/${libFileName}`;
297+
298+
// Copy from assets to app directory then load
299+
try {
300+
await RNFS.copyFileAssets(libFileName, destinationPath);
301+
this.db.loadExtension(`${appDir}/libextension`);
302+
} catch (error) {
303+
console.error('Failed to copy or load extension:', error);
304+
}
305+
} else if (Platform.OS === 'ios') {
306+
const sourcePath = `${RNFS.MainBundlePath}/extension.dylib`;
307+
const destPath = `${appDir}/libextension.dylib`;
308+
309+
// Copy and load
310+
try {
311+
await RNFS.copyFile(sourcePath, destPath);
312+
this.db.loadExtension(`${appDir}/libextension`);
313+
} catch (error) {
314+
console.error('Failed to copy or load extension:', error);
315+
}
316+
}
317+
} catch (error) {
318+
console.error('Failed to load SQLite extension:', error);
319+
}
320+
}
321+
```
322+
323+
You'll need the `react-native-fs` package for this implementation.
324+
252325
References: [Attach](https://www.sqlite.org/lang_attach.html) - [Detach](https://www.sqlite.org/lang_detach.html)
253326

254327
```ts

package/cpp/operations.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,4 +286,42 @@ SQLiteOperationResult sqliteExecuteLiteral(const std::string& dbName, const std:
286286
return {.rowsAffected = sqlite3_changes(db)};
287287
}
288288

289+
void sqliteLoadExtension(const std::string& dbName, const std::string& path, const std::optional<std::string>& entryPoint) {
290+
// Check if db connection is opened
291+
if (dbMap.count(dbName) == 0) {
292+
throw NitroSQLiteException::DatabaseNotOpen(dbName);
293+
}
294+
295+
sqlite3* db = dbMap[dbName];
296+
297+
// Enable extension loading (required before loading extensions)
298+
int result = sqlite3_enable_load_extension(db, 1);
299+
if (result != SQLITE_OK) {
300+
throw NitroSQLiteException::SqlExecution("Failed to enable extensions: " + std::string(sqlite3_errmsg(db)));
301+
}
302+
303+
// Load the extension
304+
char* error_msg = nullptr;
305+
result = sqlite3_load_extension(
306+
db,
307+
path.c_str(),
308+
entryPoint.has_value() ? entryPoint.value().c_str() : nullptr,
309+
&error_msg
310+
);
311+
312+
// Check for errors
313+
if (result != SQLITE_OK) {
314+
std::string error = error_msg ? std::string(error_msg) : "Unknown error loading extension";
315+
sqlite3_free(error_msg);
316+
317+
// Disable extension loading
318+
sqlite3_enable_load_extension(db, 0);
319+
320+
throw NitroSQLiteException::SqlExecution("Failed to load extension: " + error);
321+
}
322+
323+
// Disable extension loading (security best practice)
324+
sqlite3_enable_load_extension(db, 0);
325+
}
326+
289327
} // namespace margelo::rnnitrosqlite

package/cpp/operations.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ SQLiteOperationResult sqliteExecuteLiteral(const std::string& dbName, const std:
2121

2222
void sqliteCloseAll();
2323

24+
void sqliteLoadExtension(const std::string& dbName, const std::string& path, const std::optional<std::string>& entryPoint);
25+
2426
} // namespace margelo::rnnitrosqlite

package/cpp/specs/HybridNitroSQLite.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,8 @@ std::shared_ptr<Promise<FileLoadResult>> HybridNitroSQLite::loadFileAsync(const
9393
});
9494
};
9595

96+
void HybridNitroSQLite::loadExtension(const std::string& dbName, const std::string& path, const std::optional<std::string>& entryPoint) {
97+
sqliteLoadExtension(dbName, path, entryPoint);
98+
};
99+
96100
} // namespace margelo::nitro::rnnitrosqlite

package/cpp/specs/HybridNitroSQLite.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class HybridNitroSQLite : public HybridNitroSQLiteSpec {
3737

3838
FileLoadResult loadFile(const std::string& dbName, const std::string& location) override;
3939
std::shared_ptr<Promise<FileLoadResult>> loadFileAsync(const std::string& dbName, const std::string& location) override;
40+
41+
void loadExtension(const std::string& dbName, const std::string& path, const std::optional<std::string>& entryPoint);
4042
};
4143

4244
inline std::string HybridNitroSQLite::docPath = "";

package/nitrogen/generated/shared/c++/HybridNitroSQLiteSpec.cpp

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package/nitrogen/generated/shared/c++/HybridNitroSQLiteSpec.hpp

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export const NitroSQLite = {
2121
executeAsync,
2222
executeBatch,
2323
executeBatchAsync,
24+
// Add loadExtension to make it accessible from the NitroSQLite object
25+
loadExtension: HybridNitroSQLite.loadExtension,
2426
}
2527

2628
export { open } from './operations/session'

package/src/operations/session.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ export function open(
4141
HybridNitroSQLite.loadFile(options.name, location),
4242
loadFileAsync: (location: string) =>
4343
HybridNitroSQLite.loadFileAsync(options.name, location),
44+
loadExtension: (path: string, entryPoint?: string) =>
45+
HybridNitroSQLite.loadExtension(options.name, path, entryPoint),
4446
}
4547
}
4648

package/src/specs/NitroSQLite.nitro.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,5 @@ export interface NitroSQLite
3939
): Promise<BatchQueryResult>
4040
loadFile(dbName: string, location: string): FileLoadResult
4141
loadFileAsync(dbName: string, location: string): Promise<FileLoadResult>
42+
loadExtension(dbName: string, path: string, entryPoint?: string): void
4243
}

0 commit comments

Comments
 (0)