Skip to content
Draft
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
10 changes: 5 additions & 5 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2237,7 +2237,7 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
FBLazyVector: 4ee5f665093abe339f4b579fc3d59f65c8af196c
hermes-engine: 56e1f8e0c324283e1cc2c35f56dc6037c141f67d
hermes-engine: 74e3a44c04bd3c8d303fc8dc8a44aa67436c788b
NitroModules: 587af3c2bc93f5c9f9b39aec315eb29509a7c537
OpenSSL-Universal: ecee7b138fa75a74ecf00d7ffd248fb584739b9e
QuickCrypto: 790e6537bb81a11d036a24ce3248b51f66857ceb
Expand All @@ -2249,7 +2249,7 @@ SPEC CHECKSUMS:
React: e3af85e498bf268511ed5899200e3d267e548ddf
React-callinvoker: 9dc4fbc61d34cd3e3d496fa4558c9cfbc1caa2ce
React-Core: 5dc2976a6931ed3c36157d19111c4a265bffcff6
React-Core-prebuilt: 1a645974bd5f850a1234bf55ccee6b8b07ef4610
React-Core-prebuilt: 39445f1e9cc9b58a3e4f5e15533e0c4f8c90b927
React-CoreModules: d80b3b37587eb5c199632c9abd7c2319c594f729
React-cxxreact: 851adbc413c9d58befb2792026bf3918e31aeb2f
React-debug: ae23c7439aa4b4b64162909de91eff3897d28263
Expand Down Expand Up @@ -2312,10 +2312,10 @@ SPEC CHECKSUMS:
ReactAppDependencyProvider: 6e87125b0a52058c037d674435761b4db4579f4b
ReactCodegen: ff6b8d9d619343c281b7700362fa3ac5ec0753bb
ReactCommon: 7525e252c88d254545e3fdaea0000d1959dfbf20
ReactNativeDependencies: 0cdc5c6985700a9d8f654762fa702169ef9cef9b
ReactNativeDependencies: bc490df2cf4e3d116055debc5b8daed94728d321
RNCClipboard: 7a7d4557bfd3370b35c99dfecd92ae7b9fc4948a
RNNitroSQLite: bad203a8435c5d843534398dc4822b3e80fbbd81
RNNitroSqliteVec: cca5db2aec2a31f4e26a911bda7cac8fc2cdc2e2
RNNitroSQLite: d32b18bd4e76ba95ba8542ebd966b29dd42f67e4
RNNitroSqliteVec: 62179f9556fea49c3ec293e8256d51d6b2da8f93
RNScreens: dd5c879d56b543c7ff8e593739eeb66093d60263
Yoga: d68c8a4bde0af9c17b15540fffa330d26398d150

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')
}
}
})
})
}
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