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
4 changes: 4 additions & 0 deletions example/ios/NitroSQLiteExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
7B5735B8E367752583C44170 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
D463F2F53B094E469119C69E /* load-file-with-error.sql in Resources */ = {isa = PBXBuildFile; fileRef = D463F2F53B094E469119C69F /* load-file-with-error.sql */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand All @@ -24,6 +25,7 @@
761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = NitroSQLiteExample/AppDelegate.swift; sourceTree = "<group>"; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = NitroSQLiteExample/LaunchScreen.storyboard; sourceTree = "<group>"; };
C3E89C92C507841F368080BD /* Pods_NitroSQLiteExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NitroSQLiteExample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D463F2F53B094E469119C69F /* load-file-with-error.sql */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "load-file-with-error.sql"; path = "NitroSQLiteExample/load-file-with-error.sql"; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */

Expand All @@ -42,6 +44,7 @@
13B07FAE1A68108700A75B9A /* NitroSQLiteExample */ = {
isa = PBXGroup;
children = (
D463F2F53B094E469119C69F /* load-file-with-error.sql */,
13B07FB51A68108700A75B9A /* Images.xcassets */,
761780EC2CA45674006654EE /* AppDelegate.swift */,
13B07FB61A68108700A75B9A /* Info.plist */,
Expand Down Expand Up @@ -158,6 +161,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D463F2F53B094E469119C69E /* load-file-with-error.sql in Resources */,
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
7B5735B8E367752583C44170 /* PrivacyInfo.xcprivacy in Resources */,
Expand Down
7 changes: 7 additions & 0 deletions example/ios/NitroSQLiteExample/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
if let loadFileFixturePath = Bundle.main.path(
forResource: "load-file-with-error",
ofType: "sql"
) {
UserDefaults.standard.set(loadFileFixturePath, forKey: "loadFileFixturePath")
}

let delegate = ReactNativeDelegate()
let factory = RCTReactNativeFactory(delegate: delegate)
delegate.dependencyProvider = RCTAppDependencyProvider()
Expand Down
2 changes: 2 additions & 0 deletions example/ios/NitroSQLiteExample/load-file-with-error.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
INSERT INTO LoadFileRegression (value) VALUES ('rolled back');
THIS IS NOT VALID SQL;
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 registerLoadFileUnitTests from './specs/operations/loadFile.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()
registerLoadFileUnitTests()
})

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

export default function registerLoadFileUnitTests() {
if (Platform.OS !== 'ios') {
return
}

const loadFileFixturePath = Settings.get('loadFileFixturePath')

describe('loadFile', () => {
it('preserves the SQL error context and rolls back the completed commands', () => {
expect(loadFileFixturePath).toBeTypeOf('string')
testDb.execute(
'CREATE TABLE LoadFileRegression (value TEXT NOT NULL) STRICT;',
)

let errorMessage: string | undefined
try {
testDb.loadFile(loadFileFixturePath as string)
} catch (error) {
if (!(error instanceof Error)) {
throw error
}

errorMessage = error.message
}

expect(errorMessage).toContain('Could not load file:')
expect(errorMessage).toContain('load-file-with-error.sql')
expect(errorMessage).toContain('line 2')
expect(errorMessage).toContain('THIS IS NOT VALID SQL;')
expect(errorMessage).toContain('syntax error')

const rollbackResult = testDb.execute(
'SELECT COUNT(*) AS count FROM LoadFileRegression;',
)
expect(rollbackResult.rows?._array).toEqual([{ count: 0 }])

testDb.execute(
"INSERT INTO LoadFileRegression (value) VALUES ('connection remains usable');",
)
const usableConnectionResult = testDb.execute(
'SELECT COUNT(*) AS count FROM LoadFileRegression;',
)
expect(usableConnectionResult.rows?._array).toEqual([{ count: 1 }])
})
})
}
74 changes: 47 additions & 27 deletions packages/react-native-nitro-sqlite/cpp/importSqlFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,61 @@
#include "operations.hpp"
#include <fstream>
#include <iostream>
#include <optional>

namespace margelo::rnnitrosqlite {

SQLiteOperationResult importSqlFile(const std::string& dbName, const std::string& fileLocation) {
std::string line;
std::ifstream sqFile(fileLocation);
if (sqFile.is_open()) {
try {
int rowsAffected = 0;
int commands = 0;
sqliteExecuteCommand(dbName, "BEGIN EXCLUSIVE TRANSACTION");
while (std::getline(sqFile, line, '\n')) {
if (!line.empty()) {
try {
SQLiteOperationResult result = sqliteExecuteCommand(dbName, line);
rowsAffected += result.rowsAffected;
commands++;
} catch (NitroSQLiteException& e) {
sqliteExecuteCommand(dbName, "ROLLBACK");
sqFile.close();
throw NitroSQLiteException::CouldNotLoadFile(fileLocation, "Transaction was rolled back");
}
}
if (!sqFile.is_open()) {
throw NitroSQLiteException::CouldNotLoadFile(fileLocation);
}

int rowsAffected = 0;
int commands = 0;
int lineNumber = 0;
bool transactionStarted = false;
std::string command = "BEGIN EXCLUSIVE TRANSACTION";
std::optional<int> commandLine;

try {
sqliteExecuteCommand(dbName, command);
transactionStarted = true;

std::string line;
while (std::getline(sqFile, line, '\n')) {
lineNumber++;
if (!line.empty()) {
command = line;
commandLine = lineNumber;
SQLiteOperationResult result = sqliteExecuteCommand(dbName, command);
rowsAffected += result.rowsAffected;
commands++;
}
}

sqFile.close();
sqliteExecuteCommand(dbName, "COMMIT");
return {.rowsAffected = rowsAffected, .commands = commands};
} catch (...) {
sqFile.close();
sqliteExecuteCommand(dbName, "ROLLBACK");
throw NitroSQLiteException(NitroSQLiteExceptionType::UnknownError, "Unexpected error. Transaction was rolled back");
command = "COMMIT";
commandLine.reset();
sqliteExecuteCommand(dbName, command);
transactionStarted = false;
return {.rowsAffected = rowsAffected, .commands = commands};
} catch (const std::exception& primaryError) {
std::string errorContext;
if (commandLine) {
errorContext = "line " + std::to_string(*commandLine) + " failed to execute `" + command + "`: " + primaryError.what();
} else {
errorContext = "Failed to execute `" + command + "`: " + primaryError.what();
}
} else {
throw NitroSQLiteException::CouldNotLoadFile(fileLocation);

if (transactionStarted) {
try {
sqliteExecuteCommand(dbName, "ROLLBACK");
} catch (const std::exception& rollbackError) {
errorContext += ". ROLLBACK failed: " + std::string(rollbackError.what());
}
}

throw NitroSQLiteException::CouldNotLoadFile(fileLocation, errorContext);
}
}

Expand Down