@capgo/capacitor-fast-sql 8.2.16 → 8.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/android/src/main/java/app/capgo/capacitor/fastsql/CapgoCapacitorFastSqlPlugin.java +29 -6
- package/android/src/main/java/app/capgo/capacitor/fastsql/DatabaseConnection.java +2 -0
- package/android/src/main/java/app/capgo/capacitor/fastsql/EncryptedSQLDatabase.java +33 -46
- package/android/src/main/java/app/capgo/capacitor/fastsql/SQLDatabase.java +32 -50
- package/android/src/main/java/app/capgo/capacitor/fastsql/SQLHTTPServer.java +11 -23
- package/dist/esm/definitions.d.ts +6 -2
- package/dist/esm/definitions.d.ts.map +1 -1
- package/dist/esm/definitions.js +3 -1
- package/dist/esm/fast-sql.d.ts +4 -0
- package/dist/esm/fast-sql.d.ts.map +1 -1
- package/dist/esm/fast-sql.js +35 -3
- package/dist/esm/native-sql-connection.d.ts +1 -0
- package/dist/esm/native-sql-connection.d.ts.map +1 -1
- package/dist/esm/native-sql-connection.js +13 -6
- package/dist/esm/transaction-isolation.d.ts +3 -0
- package/dist/esm/transaction-isolation.d.ts.map +1 -0
- package/dist/esm/transaction-isolation.js +6 -0
- package/dist/esm/vector-store.d.ts.map +1 -1
- package/dist/esm/vector-store.js +5 -2
- package/dist/esm/web-sql-connection.d.ts.map +1 -1
- package/dist/esm/web-sql-connection.js +2 -0
- package/dist/esm/web.d.ts.map +1 -1
- package/dist/esm/web.js +10 -2
- package/dist/plugin.cjs.js +71 -14
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +71 -14
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCapacitorFastSqlPlugin/CapgoCapacitorFastSqlPlugin.swift +25 -3
- package/ios/Sources/CapgoCapacitorFastSqlPlugin/SQLDatabase.swift +15 -5
- package/ios/Sources/CapgoCapacitorFastSqlPlugin/SQLHTTPServer.swift +6 -0
- package/ios/Tests/CapgoCapacitorFastSqlPluginTests/CapgoCapacitorFastSqlPluginTests.swift +13 -3
- package/ios/Tests/CapgoCapacitorFastSqlPluginTests/SQLResultMetadataTests.swift +15 -0
- package/package.json +1 -1
|
@@ -10,7 +10,7 @@ import SQLite3
|
|
|
10
10
|
*/
|
|
11
11
|
@objc(CapgoCapacitorFastSqlPlugin)
|
|
12
12
|
public class CapgoCapacitorFastSqlPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
13
|
-
private let pluginVersion: String = "8.2.
|
|
13
|
+
private let pluginVersion: String = "8.2.17"
|
|
14
14
|
public let identifier = "CapgoCapacitorFastSqlPlugin"
|
|
15
15
|
public let jsName = "CapgoCapacitorFastSql"
|
|
16
16
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -44,7 +44,11 @@ public class CapgoCapacitorFastSqlPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
// Check if already connected
|
|
47
|
-
if databases[database]
|
|
47
|
+
if let existing = databases[database] {
|
|
48
|
+
guard existing.isReadOnly == (call.getBool("readOnly") ?? false) else {
|
|
49
|
+
call.reject("Database is already open with a different readOnly option")
|
|
50
|
+
return
|
|
51
|
+
}
|
|
48
52
|
if let server = server {
|
|
49
53
|
call.resolve([
|
|
50
54
|
"port": server.port,
|
|
@@ -63,6 +67,7 @@ public class CapgoCapacitorFastSqlPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
63
67
|
let db = try SQLDatabase(
|
|
64
68
|
path: dbPath,
|
|
65
69
|
encrypted: encrypted,
|
|
70
|
+
readOnly: call.getBool("readOnly") ?? false,
|
|
66
71
|
encryptionKey: encryptionKey,
|
|
67
72
|
walMode: walMode,
|
|
68
73
|
performancePresets: performancePresets
|
|
@@ -177,6 +182,10 @@ public class CapgoCapacitorFastSqlPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
177
182
|
}
|
|
178
183
|
|
|
179
184
|
@objc func beginTransaction(_ call: CAPPluginCall) {
|
|
185
|
+
guard (call.getString("isolationLevel") ?? "SERIALIZABLE") == "SERIALIZABLE" else {
|
|
186
|
+
call.reject("Only SERIALIZABLE transaction isolation is supported")
|
|
187
|
+
return
|
|
188
|
+
}
|
|
180
189
|
guard let database = call.getString("database") else {
|
|
181
190
|
call.reject("Database name is required")
|
|
182
191
|
return
|
|
@@ -245,6 +254,12 @@ public class CapgoCapacitorFastSqlPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
245
254
|
}
|
|
246
255
|
|
|
247
256
|
private func getDatabasePath(_ database: String) throws -> String {
|
|
257
|
+
guard !database.isEmpty, database != ".", database != "..",
|
|
258
|
+
!database.contains("/"), !database.contains("\\"), !database.contains("\0") else {
|
|
259
|
+
throw NSError(domain: "CapgoCapacitorFastSQL", code: 2, userInfo: [
|
|
260
|
+
NSLocalizedDescriptionKey: "Database must be a non-empty file name without path separators"
|
|
261
|
+
])
|
|
262
|
+
}
|
|
248
263
|
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
|
|
249
264
|
guard let documentsDirectory = paths.first else {
|
|
250
265
|
throw NSError(domain: "CapgoCapacitorFastSQL", code: 1, userInfo: [
|
|
@@ -253,6 +268,13 @@ public class CapgoCapacitorFastSqlPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
253
268
|
}
|
|
254
269
|
|
|
255
270
|
let dbPath = (documentsDirectory as NSString).appendingPathComponent("\(database).db")
|
|
256
|
-
|
|
271
|
+
let root = URL(fileURLWithPath: documentsDirectory).resolvingSymlinksInPath().standardizedFileURL
|
|
272
|
+
let resolved = URL(fileURLWithPath: dbPath).resolvingSymlinksInPath().standardizedFileURL
|
|
273
|
+
guard resolved.deletingLastPathComponent().path == root.path else {
|
|
274
|
+
throw NSError(domain: "CapgoCapacitorFastSQL", code: 2, userInfo: [
|
|
275
|
+
NSLocalizedDescriptionKey: "Database path must remain inside app storage"
|
|
276
|
+
])
|
|
277
|
+
}
|
|
278
|
+
return resolved.path
|
|
257
279
|
}
|
|
258
280
|
}
|
|
@@ -13,6 +13,7 @@ private let sqliteTransient = unsafeBitCast(-1, to: sqlite3_destructor_type.self
|
|
|
13
13
|
* SQLite database wrapper for iOS
|
|
14
14
|
*/
|
|
15
15
|
class SQLDatabase {
|
|
16
|
+
let isReadOnly: Bool
|
|
16
17
|
private var db: OpaquePointer?
|
|
17
18
|
private let path: String
|
|
18
19
|
private var inTransaction = false
|
|
@@ -20,13 +21,15 @@ class SQLDatabase {
|
|
|
20
21
|
init(
|
|
21
22
|
path: String,
|
|
22
23
|
encrypted: Bool = false,
|
|
24
|
+
readOnly: Bool = false,
|
|
23
25
|
encryptionKey: String? = nil,
|
|
24
26
|
walMode: Bool = false,
|
|
25
27
|
performancePresets: Bool = false
|
|
26
28
|
) throws {
|
|
27
29
|
self.path = path
|
|
30
|
+
self.isReadOnly = readOnly
|
|
28
31
|
|
|
29
|
-
let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX
|
|
32
|
+
let flags = (readOnly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE) | SQLITE_OPEN_FULLMUTEX
|
|
30
33
|
let result = sqlite3_open_v2(path, &db, flags, nil)
|
|
31
34
|
|
|
32
35
|
guard result == SQLITE_OK else {
|
|
@@ -64,7 +67,7 @@ class SQLDatabase {
|
|
|
64
67
|
#endif
|
|
65
68
|
}
|
|
66
69
|
|
|
67
|
-
try applyPerformanceOptions(walMode: walMode, performancePresets: performancePresets)
|
|
70
|
+
try applyPerformanceOptions(walMode: walMode && !readOnly, performancePresets: performancePresets)
|
|
68
71
|
needsClose = false
|
|
69
72
|
}
|
|
70
73
|
|
|
@@ -148,8 +151,9 @@ class SQLDatabase {
|
|
|
148
151
|
}
|
|
149
152
|
}
|
|
150
153
|
|
|
151
|
-
let
|
|
152
|
-
let
|
|
154
|
+
let readOnly = sqlite3_stmt_readonly(stmt) != 0
|
|
155
|
+
let changes = readOnly ? 0 : sqlite3_changes(db)
|
|
156
|
+
let lastInsertId = readOnly ? 0 : sqlite3_last_insert_rowid(db)
|
|
153
157
|
|
|
154
158
|
return [
|
|
155
159
|
"rows": rows,
|
|
@@ -211,7 +215,10 @@ class SQLDatabase {
|
|
|
211
215
|
type == "binary",
|
|
212
216
|
let base64 = dict["_data"] as? String,
|
|
213
217
|
let data = Data(base64Encoded: base64) {
|
|
214
|
-
//
|
|
218
|
+
// SQLite treats a null blob pointer as SQL NULL, even with length zero.
|
|
219
|
+
if data.isEmpty {
|
|
220
|
+
return sqlite3_bind_zeroblob(stmt, index, 0)
|
|
221
|
+
}
|
|
215
222
|
return data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in
|
|
216
223
|
sqlite3_bind_blob(stmt, index, bytes.baseAddress, Int32(data.count), sqliteTransient)
|
|
217
224
|
}
|
|
@@ -238,6 +245,9 @@ class SQLDatabase {
|
|
|
238
245
|
}
|
|
239
246
|
return NSNull()
|
|
240
247
|
case SQLITE_BLOB:
|
|
248
|
+
if sqlite3_column_bytes(stmt, index) == 0 {
|
|
249
|
+
return ["_type": "binary", "_data": ""]
|
|
250
|
+
}
|
|
241
251
|
if let blob = sqlite3_column_blob(stmt, index) {
|
|
242
252
|
let size = sqlite3_column_bytes(stmt, index)
|
|
243
253
|
let data = Data(bytes: blob, count: Int(size))
|
|
@@ -283,6 +283,12 @@ class SQLHTTPServer {
|
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
private func handleBeginTransactionRequest(request: HTTPRequest) -> HTTPResponse {
|
|
286
|
+
if !request.body.isEmpty {
|
|
287
|
+
guard let body = try? JSONSerialization.jsonObject(with: request.body) as? [String: Any],
|
|
288
|
+
(body["isolationLevel"] as? String ?? "SERIALIZABLE") == "SERIALIZABLE" else {
|
|
289
|
+
return HTTPResponse(.badRequest, body: Data("Only SERIALIZABLE transaction isolation is supported".utf8))
|
|
290
|
+
}
|
|
291
|
+
}
|
|
286
292
|
// Authenticate
|
|
287
293
|
guard let authHeader = request.headers["Authorization"],
|
|
288
294
|
authHeader == "Bearer \(token)" else {
|
|
@@ -2,8 +2,18 @@ import XCTest
|
|
|
2
2
|
@testable import CapgoCapacitorFastSqlPlugin
|
|
3
3
|
|
|
4
4
|
final class CapgoCapacitorFastSqlPluginTests: XCTestCase {
|
|
5
|
-
func
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
func testEmptyBlobRemainsDistinctFromNull() throws {
|
|
6
|
+
let database = try SQLDatabase(path: ":memory:")
|
|
7
|
+
defer { database.close() }
|
|
8
|
+
let result = try database.execute(
|
|
9
|
+
statement: "SELECT typeof(?) AS kind, ? AS value, NULL AS missing",
|
|
10
|
+
params: [["_type": "binary", "_data": ""], ["_type": "binary", "_data": ""]]
|
|
11
|
+
)
|
|
12
|
+
let rows = try XCTUnwrap(result["rows"] as? [[String: Any]])
|
|
13
|
+
XCTAssertEqual(rows.first?["kind"] as? String, "blob")
|
|
14
|
+
let value = try XCTUnwrap(rows.first?["value"] as? [String: String])
|
|
15
|
+
XCTAssertEqual(value["_type"], "binary")
|
|
16
|
+
XCTAssertEqual(value["_data"], "")
|
|
17
|
+
XCTAssertTrue(rows.first?["missing"] is NSNull)
|
|
8
18
|
}
|
|
9
19
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import CapgoCapacitorFastSqlPlugin
|
|
3
|
+
|
|
4
|
+
final class SQLResultMetadataTests: XCTestCase {
|
|
5
|
+
func testSelectDoesNotInheritPreviousInsertMetadata() throws {
|
|
6
|
+
let database = try SQLDatabase(path: ":memory:")
|
|
7
|
+
defer { database.close() }
|
|
8
|
+
_ = try database.execute(statement: "CREATE TABLE sample (id INTEGER PRIMARY KEY)", params: [])
|
|
9
|
+
let inserted = try database.execute(statement: "INSERT INTO sample DEFAULT VALUES", params: [])
|
|
10
|
+
XCTAssertEqual(inserted["rowsAffected"] as? Int32, 1)
|
|
11
|
+
let selected = try database.execute(statement: "SELECT id FROM sample", params: [])
|
|
12
|
+
XCTAssertEqual(selected["rowsAffected"] as? Int32, 0)
|
|
13
|
+
XCTAssertTrue(selected["insertId"] is NSNull)
|
|
14
|
+
}
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/capacitor-fast-sql",
|
|
3
|
-
"version": "8.2.
|
|
3
|
+
"version": "8.2.17",
|
|
4
4
|
"description": "High-performance native SQLite plugin with custom protocol for efficient sync operations and IndexedDB replacement",
|
|
5
5
|
"main": "dist/plugin.cjs.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|