Skip to content
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
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const db = open({ name: 'myDb.sqlite' })
| Method | Sync | Async | Description |
|--------|------|-------|-------------|
| **Execute** | `db.execute(query, params?)` | `db.executeAsync(query, params?)` | Run a single SQL statement. |
| **Prepared statement** | `db.prepare(query)` | Statement `executeAsync(params?)` | Prepare once and execute repeatedly with different parameters. |
| **Batch** | `db.executeBatch(commands)` | `db.executeBatchAsync(commands)` | Run multiple statements in one transaction. |
| **Load file** | `db.loadFile(path)` | `db.loadFileAsync(path)` | Execute SQL from a file. |
| **Transaction** | — | `db.transaction(async (tx) => { ... })` | Run multiple statements in a transaction (async only). |
Expand Down Expand Up @@ -136,6 +137,21 @@ const { rowsAffected } = db.executeBatch(commands)
// Or: await db.executeBatchAsync(commands)
```

## Prepared statements

Use `db.prepare()` when the same SQL statement is executed repeatedly with different parameters. Call `finalize()` once the statement is no longer needed, and always finalize it before closing its database connection.

```typescript
const insertUser = db.prepare(
'INSERT INTO users (id, name) VALUES (?, ?)',
)

insertUser.execute([1, 'Ada'])
await insertUser.executeAsync([2, 'Grace'])

insertUser.finalize()
```

# Column metadata

When you need column types or names for the result set, use the `metadata` field on the query result. Keys are column names; values include `name`, `type` (e.g. from `ColumnType`), and `index`.
Expand Down
6 changes: 3 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1908,7 +1908,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- RNNitroSQLite (9.6.0):
- RNNitroSQLite (9.7.0):
- hermes-engine
- NitroModules
- RCTRequired
Expand All @@ -1931,7 +1931,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- RNNitroSqliteVec (9.6.0):
- RNNitroSqliteVec (9.7.0):
- RNNitroSQLite
- RNScreens (4.18.0):
- hermes-engine
Expand Down Expand Up @@ -2314,8 +2314,8 @@ SPEC CHECKSUMS:
ReactCommon: 7525e252c88d254545e3fdaea0000d1959dfbf20
ReactNativeDependencies: 0cdc5c6985700a9d8f654762fa702169ef9cef9b
RNCClipboard: 7a7d4557bfd3370b35c99dfecd92ae7b9fc4948a
RNNitroSQLite: bad203a8435c5d843534398dc4822b3e80fbbd81
RNNitroSqliteVec: cca5db2aec2a31f4e26a911bda7cac8fc2cdc2e2
RNNitroSQLite: 6264d13622c7aa031831de174d2c7ec39c62f58b
RNNitroSqliteVec: bac14e5dad54e7dffdb5dd464d5e8853ae4d2540
RNScreens: dd5c879d56b543c7ff8e593739eeb66093d60263
Yoga: d68c8a4bde0af9c17b15540fffa330d26398d150

Expand Down
6 changes: 3 additions & 3 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@
"chance": "^1.1.9",
"events": "^3.3.0",
"expo-status-bar": "^1.12.1",
"react": "19.2.3",
"react": "19.2.8",
"react-native": "0.85.0-rc.0",
"react-native-nitro-modules": "*",
"react-native-nitro-sqlite": "9.6.0",
"react-native-nitro-sqlite-vec": "9.6.0",
"react-native-nitro-sqlite": "9.7.0",
"react-native-nitro-sqlite-vec": "9.7.0",
"react-native-quick-base64": "^3.0.0",
"react-native-quick-crypto": "^1.1.5",
"react-native-safe-area-context": "^5.5.2",
Expand Down
2 changes: 2 additions & 0 deletions example/tests/unit/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { setupTestDb } from './common'
import registerExecuteUnitTests from './specs/operations/execute.spec'
import registerTransactionUnitTests from './specs/operations/transaction.spec'
import registerExecuteBatchUnitTests from './specs/operations/executeBatch.spec'
import registerPreparedStatementUnitTests from './specs/operations/preparedStatement.spec'
import registerTypeORMUnitTestsSpecs from './specs/typeorm.spec'
import registerDatabaseQueueUnitTests from './specs/DatabaseQueue.spec'
import registerSqliteVecUnitTestsSpecs from './specs/sqlite-vec.spec'
Expand All @@ -14,6 +15,7 @@ export function registerUnitTests() {
registerExecuteUnitTests()
registerTransactionUnitTests()
registerExecuteBatchUnitTests()
registerPreparedStatementUnitTests()
})

registerDatabaseQueueUnitTests()
Expand Down
85 changes: 85 additions & 0 deletions example/tests/unit/specs/operations/preparedStatement.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { chance, expect, isNitroSQLiteError } from '@tests/unit/common'
import { describe, it } from '@tests/TestApi'
import { testDb } from '@tests/db'

export default function registerPreparedStatementUnitTests() {
describe('prepared statements', () => {
it('reuses one statement with different parameter values', () => {
const insert = testDb.prepare(
'INSERT INTO User (id, name, age, networth) VALUES (?, ?, ?, ?)',
)
const firstUser = {
id: chance.integer(),
name: chance.name(),
age: chance.integer(),
networth: chance.floating(),
}
const secondUser = {
id: chance.integer(),
name: chance.name(),
age: chance.integer(),
networth: chance.floating(),
}

expect(insert.isFinalized).toBe(false)
expect(
insert.execute([
firstUser.id,
firstUser.name,
firstUser.age,
firstUser.networth,
]).rowsAffected,
).toBe(1)
expect(
insert.execute([
secondUser.id,
secondUser.name,
secondUser.age,
secondUser.networth,
]).rowsAffected,
).toBe(1)

const select = testDb.prepare('SELECT * FROM User WHERE id = ?')
expect(select.execute([firstUser.id]).rows._array).toEqual([firstUser])
expect(select.execute([secondUser.id]).rows._array).toEqual([secondUser])

insert.finalize()
select.finalize()
expect(insert.isFinalized).toBe(true)
expect(select.isFinalized).toBe(true)
})

it('executes asynchronously', async () => {
const id = chance.integer()
const statement = testDb.prepare(
'INSERT INTO User (id, name, age, networth) VALUES (?, ?, ?, ?)',
)

const result = await statement.executeAsync([
id,
chance.name(),
chance.integer(),
chance.floating(),
])

expect(result.rowsAffected).toBe(1)
expect(testDb.execute('SELECT * FROM User WHERE id = ?', [id]).rows.length).toBe(1)
statement.finalize()
})

it('rejects execution after finalization', () => {
const statement = testDb.prepare('SELECT * FROM User')
statement.finalize()

try {
statement.execute()
throw new Error('Expected execution to throw after finalization')
} catch (error) {
expect(isNitroSQLiteError(error)).toBe(true)
if (isNitroSQLiteError(error)) {
expect(error.message).toContain('Prepared statement has been finalized')
}
}
})
})
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-nitro-sqlite-workspace",
"version": "9.6.0",
"version": "9.7.0",
"packageManager": "bun@1.3.1",
"private": "true",
"workspaces": [
Expand Down
7 changes: 4 additions & 3 deletions packages/react-native-nitro-sqlite-vec/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-nitro-sqlite-vec",
"version": "9.6.0",
"version": "9.7.0",
"description": "Vector search (sqlite-vec) for react-native-nitro-sqlite. Native sqlite-vec is statically linked into the core's single sqlite3 — no second SQLite, no runtime extension loading.",
"source": "./src/index",
"react-native": "./src/index",
Expand Down Expand Up @@ -35,13 +35,14 @@
},
"license": "MIT",
"publishConfig": {
"registry": "https://registry.npmjs.org/"
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"peerDependencies": {
"react-native-nitro-sqlite": ">=9.0.0"
},
"devDependencies": {
"react-native-nitro-sqlite": "9.6.0",
"react-native-nitro-sqlite": "9.7.0",
"typescript": "^5.8.3"
},
"release-it": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "HybridNitroSQLite.hpp"
#include "HybridNitroSQLitePreparedStatement.hpp"
#include "HybridNitroSQLiteQueryResult.hpp"
#include "NitroSQLiteException.hpp"
#include "importSqlFile.hpp"
Expand Down Expand Up @@ -109,6 +110,10 @@ HybridNitroSQLite::executeAsync(const std::string& dbName, const std::string& qu
});
};

std::shared_ptr<HybridNitroSQLitePreparedStatementSpec> HybridNitroSQLite::prepare(const std::string& dbName, const std::string& query) {
return std::make_shared<HybridNitroSQLitePreparedStatement>(sqlitePrepare(dbName, query));
}

BatchQueryResult HybridNitroSQLite::executeBatch(const std::string& dbName, const std::vector<BatchQueryCommand>& batchParams) {
const auto commands = batchParamsToCommands(batchParams);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include "HybridNitroSQLitePreparedStatementSpec.hpp"
#include "HybridNitroSQLiteQueryResultSpec.hpp"
#include "HybridNitroSQLiteSpec.hpp"
#include "types.hpp"
Expand Down Expand Up @@ -34,6 +35,8 @@ class HybridNitroSQLite : public HybridNitroSQLiteSpec {
std::shared_ptr<Promise<std::shared_ptr<HybridNitroSQLiteQueryResultSpec>>>
executeAsync(const std::string& dbName, const std::string& query, const std::optional<SQLiteQueryParams>& params) override;

std::shared_ptr<HybridNitroSQLitePreparedStatementSpec> prepare(const std::string& dbName, const std::string& query) override;

BatchQueryResult executeBatch(const std::string& dbName, const std::vector<BatchQueryCommand>& commands) override;
std::shared_ptr<Promise<BatchQueryResult>> executeBatchAsync(const std::string& dbName,
const std::vector<BatchQueryCommand>& commands) override;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#include "HybridNitroSQLitePreparedStatement.hpp"
#include "HybridNitroSQLiteQueryResult.hpp"
#include "operations.hpp"
#include <NitroModules/ArrayBuffer.hpp>

namespace margelo::nitro::rnnitrosqlite {

namespace {

std::optional<SQLiteQueryParams> copyArrayBufferParamsForBackground(const std::optional<SQLiteQueryParams>& params) {
if (!params) {
return std::nullopt;
}

SQLiteQueryParams copiedParams;
copiedParams.reserve(params->size());

for (const auto& value : *params) {
if (std::holds_alternative<std::shared_ptr<ArrayBuffer>>(value)) {
copiedParams.push_back(ArrayBuffer::copy(std::get<std::shared_ptr<ArrayBuffer>>(value)));
} else {
copiedParams.push_back(value);
}
}

return copiedParams;
}

} // namespace

HybridNitroSQLitePreparedStatement::HybridNitroSQLitePreparedStatement(
std::shared_ptr<::margelo::rnnitrosqlite::SQLitePreparedStatement> statement)
: HybridObject(TAG), _statement(std::move(statement)) {}

std::shared_ptr<HybridNitroSQLiteQueryResultSpec>
HybridNitroSQLitePreparedStatement::execute(const std::optional<SQLiteQueryParams>& params) {
return _statement->execute(params);
}

std::shared_ptr<Promise<std::shared_ptr<HybridNitroSQLiteQueryResultSpec>>>
HybridNitroSQLitePreparedStatement::executeAsync(const std::optional<SQLiteQueryParams>& params) {
const auto copiedParams = copyArrayBufferParamsForBackground(params);
const auto statement = _statement;

return Promise<std::shared_ptr<HybridNitroSQLiteQueryResultSpec>>::async(
[statement, copiedParams]() -> std::shared_ptr<HybridNitroSQLiteQueryResultSpec> { return statement->execute(copiedParams); });
}

void HybridNitroSQLitePreparedStatement::finalize() {
_statement->finalize();
}

bool HybridNitroSQLitePreparedStatement::getIsFinalized() {
return _statement->isFinalized();
}

size_t HybridNitroSQLitePreparedStatement::getExternalMemorySize() noexcept {
return sizeof(*this) + _statement->getExternalMemorySize();
}

} // namespace margelo::nitro::rnnitrosqlite
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#pragma once

#include "HybridNitroSQLitePreparedStatementSpec.hpp"
#include "types.hpp"
#include <memory>

using namespace margelo::rnnitrosqlite;

namespace margelo::rnnitrosqlite {
class SQLitePreparedStatement;
}

namespace margelo::nitro::rnnitrosqlite {

class HybridNitroSQLitePreparedStatement : public HybridNitroSQLitePreparedStatementSpec {
public:
explicit HybridNitroSQLitePreparedStatement(std::shared_ptr<::margelo::rnnitrosqlite::SQLitePreparedStatement> statement);

std::shared_ptr<HybridNitroSQLiteQueryResultSpec> execute(const std::optional<SQLiteQueryParams>& params) override;
std::shared_ptr<Promise<std::shared_ptr<HybridNitroSQLiteQueryResultSpec>>>
executeAsync(const std::optional<SQLiteQueryParams>& params) override;
void finalize() override;
bool getIsFinalized() override;

size_t getExternalMemorySize() noexcept override;

private:
std::shared_ptr<::margelo::rnnitrosqlite::SQLitePreparedStatement> _statement;
};

} // namespace margelo::nitro::rnnitrosqlite
Loading
Loading