Skip to content

Commit d73b3cf

Browse files
committed
feat: added support for loadExtension
1 parent a292f5a commit d73b3cf

File tree

11 files changed

+91
-1
lines changed

11 files changed

+91
-1
lines changed

README.md

Lines changed: 37 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,41 @@ 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+
// Platform-specific extension paths
264+
const extensionPath = Platform.OS === 'ios'
265+
? 'path/to/extension.dylib'
266+
: 'path/to/extension.so';
267+
268+
// Load the extension
269+
const success = db.loadExtension(extensionPath);
270+
271+
if (success) {
272+
// Use the extension's functionality
273+
const result = db.execute('SELECT extension_function(?) as result', [someValue]);
274+
console.log('Result:', result.rows._array[0].result);
275+
}
276+
} catch (e) {
277+
console.error('Failed to load extension:', e.message);
278+
}
279+
```
280+
281+
Notes about extensions:
282+
- Extensions must be compiled specifically for each platform (iOS, Android)
283+
- The extension path should be accessible from your app's bundle
284+
- Extensions are useful for adding mathematical functions, encryption, full-text search, and more
285+
- For iOS, extensions need to be included in the app bundle
286+
- For Android, extensions should be in the app's native libs directory
287+
252288
References: [Attach](https://www.sqlite.org/lang_attach.html) - [Detach](https://www.sqlite.org/lang_detach.html)
253289

254290
```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)