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
92 changes: 91 additions & 1 deletion example/tests/unit/specs/DatabaseQueue.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,31 @@ import {
} from '@tests/unit/common'
import { describe, it } from '@tests/TestApi'
import { testDb, testDbQueue } from '@tests/db'
import type { BatchQueryCommand } from 'react-native-nitro-sqlite'
import {
NitroSQLite,
NitroSQLiteError,
open,
type BatchQueryCommand,
} from 'react-native-nitro-sqlite'

const TEST_QUERY = 'SELECT * FROM [User];'

const TEST_BATCH_COMMANDS: BatchQueryCommand[] = [{ query: TEST_QUERY }]

function dropDatabaseIfExists(dbName: string, location?: string) {
try {
NitroSQLite.native.drop(dbName, location)
} catch (error) {
if (
error instanceof Error &&
error.message.includes('Database file not found')
) {
return
}
throw error
}
}

export default function registerDatabaseQueueUnitTests() {
describe('Database Queue', () => {
it('multiple transactions are queued', async () => {
Expand Down Expand Up @@ -178,5 +197,76 @@ export default function registerDatabaseQueueUnitTests() {
}
}
})

it('rejects a duplicate session open without replacing the original connection', () => {
const dbName = 'duplicate-session-open'
dropDatabaseIfExists(dbName)
dropDatabaseIfExists(dbName, '..')

const db = open({ name: dbName })

try {
db.execute('CREATE TABLE ConnectionMarker (value TEXT NOT NULL)')
db.execute('INSERT INTO ConnectionMarker (value) VALUES (?)', [
'original',
])

let duplicateError: unknown
try {
open({ name: dbName, location: '..' })
} catch (error) {
duplicateError = error
}

expect(duplicateError).toBeInstanceOf(NitroSQLiteError)
expect((duplicateError as Error).message).toContain('already open')
expect(
db.execute<{ value: string }>('SELECT value FROM ConnectionMarker')
.results,
).toEqual([{ value: 'original' }])
} finally {
db.close()
dropDatabaseIfExists(dbName)
dropDatabaseIfExists(dbName, '..')
}
})

it('rejects duplicate direct native opens', () => {
const dbName = 'duplicate-native-open'
dropDatabaseIfExists(dbName)

NitroSQLite.native.open(dbName)

try {
NitroSQLite.execute(
dbName,
'CREATE TABLE ConnectionMarker (value TEXT NOT NULL)',
)
NitroSQLite.execute(
dbName,
'INSERT INTO ConnectionMarker (value) VALUES (?)',
['original'],
)

let duplicateError: unknown
try {
NitroSQLite.native.open(dbName)
} catch (error) {
duplicateError = error
}

expect(duplicateError).toBeInstanceOf(Error)
expect((duplicateError as Error).message).toContain('already open')
expect(
NitroSQLite.execute<{ value: string }>(
dbName,
'SELECT value FROM ConnectionMarker',
).results,
).toEqual([{ value: 'original' }])
} finally {
NitroSQLite.native.close(dbName)
dropDatabaseIfExists(dbName)
}
})
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ class NitroSQLiteException : public std::exception {
return this->_exceptionString.c_str();
}

static NitroSQLiteException DatabaseAlreadyOpen(const std::string& dbName) {
return NitroSQLiteException(NitroSQLiteExceptionType::DatabaseCannotBeOpened,
"Database " + dbName + " is already open. There is already a connection to the database.");
}

static NitroSQLiteException DatabaseNotOpen(const std::string& dbName) {
return NitroSQLiteException(NitroSQLiteExceptionType::UnableToAttachToDatabase, dbName + " is not open");
}
Expand Down
23 changes: 16 additions & 7 deletions packages/react-native-nitro-sqlite/cpp/operations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ static constexpr double kInt64UpperBoundAsDouble = -kInt64MinAsDouble;
std::map<std::string, sqlite3*> dbMap = std::map<std::string, sqlite3*>();

void sqliteOpenDb(const std::string& dbName, const std::string& docPath) {
if (dbMap.contains(dbName)) {
throw NitroSQLiteException::DatabaseAlreadyOpen(dbName);
}

#ifdef NITRO_SQLITE_VEC
// Register before opening so the connection exposes vec0 + vec_*.
margelo::rnnitrosqlitevec::registerVectorExtensions();
Expand All @@ -41,15 +45,20 @@ void sqliteOpenDb(const std::string& dbName, const std::string& docPath) {

int sqlOpenFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX;

sqlite3* db;
int exit = 0;
exit = sqlite3_open_v2(dbPath.c_str(), &db, sqlOpenFlags, nullptr);
sqlite3* rawDatabase = nullptr;
const int openStatus = sqlite3_open_v2(dbPath.c_str(), &rawDatabase, sqlOpenFlags, nullptr);
std::unique_ptr<sqlite3, decltype(&sqlite3_close_v2)> database(rawDatabase, sqlite3_close_v2);

if (openStatus != SQLITE_OK) {
const std::string errorMessage = rawDatabase == nullptr ? sqlite3_errstr(openStatus) : sqlite3_errmsg(rawDatabase);
throw NitroSQLiteException(NitroSQLiteExceptionType::DatabaseCannotBeOpened, errorMessage);
}

if (exit != SQLITE_OK) {
throw NitroSQLiteException(NitroSQLiteExceptionType::DatabaseCannotBeOpened, sqlite3_errmsg(db));
} else {
dbMap[dbName] = db;
const bool inserted = dbMap.emplace(dbName, database.get()).second;
if (!inserted) {
throw NitroSQLiteException::DatabaseAlreadyOpen(dbName);
}
database.release();
}

void sqliteCloseDb(const std::string& dbName) {
Expand Down