@capgo/capacitor-fast-sql 7.0.1
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/CapgoCapacitorFastSql.podspec +18 -0
- package/Package.swift +30 -0
- package/README.md +340 -0
- package/android/build.gradle +60 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/app/capgo/capacitor/fastsql/CapgoCapacitorFastSqlPlugin.java +220 -0
- package/android/src/main/java/app/capgo/capacitor/fastsql/SQLDatabase.java +231 -0
- package/android/src/main/java/app/capgo/capacitor/fastsql/SQLHTTPServer.java +229 -0
- package/dist/esm/definitions.d.ts +225 -0
- package/dist/esm/definitions.d.ts.map +1 -0
- package/dist/esm/definitions.js +10 -0
- package/dist/esm/helpers.d.ts +7 -0
- package/dist/esm/helpers.d.ts.map +1 -0
- package/dist/esm/helpers.js +6 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/native-sql.d.ts +42 -0
- package/dist/esm/native-sql.d.ts.map +1 -0
- package/dist/esm/native-sql.js +69 -0
- package/dist/esm/plugin.d.ts +3 -0
- package/dist/esm/plugin.d.ts.map +1 -0
- package/dist/esm/plugin.js +4 -0
- package/dist/esm/sql-connection.d.ts +94 -0
- package/dist/esm/sql-connection.d.ts.map +1 -0
- package/dist/esm/sql-connection.js +246 -0
- package/dist/esm/web.d.ts +67 -0
- package/dist/esm/web.d.ts.map +1 -0
- package/dist/esm/web.js +215 -0
- package/dist/plugin.cjs.js +557 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +560 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/CapgoCapacitorFastSqlPlugin/CapgoCapacitorFastSqlPlugin.swift +202 -0
- package/ios/Sources/CapgoCapacitorFastSqlPlugin/SQLDatabase.swift +215 -0
- package/ios/Sources/CapgoCapacitorFastSqlPlugin/SQLHTTPServer.swift +311 -0
- package/package.json +90 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
import SQLite3
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Native SQL Plugin for iOS
|
|
7
|
+
*
|
|
8
|
+
* This plugin uses a custom HTTP server for efficient data transfer,
|
|
9
|
+
* bypassing Capacitor's standard bridge for better performance.
|
|
10
|
+
*/
|
|
11
|
+
@objc(CapgoCapacitorFastSqlPlugin)
|
|
12
|
+
public class CapgoCapacitorFastSqlPlugin: CAPPlugin {
|
|
13
|
+
private var databases: [String: SQLDatabase] = [:]
|
|
14
|
+
private var server: SQLHTTPServer?
|
|
15
|
+
|
|
16
|
+
@objc func connect(_ call: CAPPluginCall) {
|
|
17
|
+
guard let database = call.getString("database") else {
|
|
18
|
+
call.reject("Database name is required")
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Check if already connected
|
|
23
|
+
if databases[database] != nil {
|
|
24
|
+
if let server = server {
|
|
25
|
+
call.resolve([
|
|
26
|
+
"port": server.port,
|
|
27
|
+
"token": server.token,
|
|
28
|
+
"database": database
|
|
29
|
+
])
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
do {
|
|
35
|
+
// Get database path
|
|
36
|
+
let dbPath = try getDatabasePath(database)
|
|
37
|
+
|
|
38
|
+
// Open database
|
|
39
|
+
let db = try SQLDatabase(path: dbPath)
|
|
40
|
+
databases[database] = db
|
|
41
|
+
|
|
42
|
+
// Start HTTP server if not already running
|
|
43
|
+
if server == nil {
|
|
44
|
+
server = try SQLHTTPServer(databases: databases)
|
|
45
|
+
try server?.start()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
guard let server = server else {
|
|
49
|
+
call.reject("Failed to start HTTP server")
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
call.resolve([
|
|
54
|
+
"port": server.port,
|
|
55
|
+
"token": server.token,
|
|
56
|
+
"database": database
|
|
57
|
+
])
|
|
58
|
+
} catch {
|
|
59
|
+
call.reject("Failed to connect to database: \(error.localizedDescription)")
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@objc func disconnect(_ call: CAPPluginCall) {
|
|
64
|
+
guard let database = call.getString("database") else {
|
|
65
|
+
call.reject("Database name is required")
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
guard let db = databases[database] else {
|
|
70
|
+
call.reject("Database '\(database)' is not connected")
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
db.close()
|
|
75
|
+
databases.removeValue(forKey: database)
|
|
76
|
+
|
|
77
|
+
// Stop server if no more databases
|
|
78
|
+
if databases.isEmpty {
|
|
79
|
+
server?.stop()
|
|
80
|
+
server = nil
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
call.resolve()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@objc func getServerInfo(_ call: CAPPluginCall) {
|
|
87
|
+
guard let database = call.getString("database") else {
|
|
88
|
+
call.reject("Database name is required")
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
guard databases[database] != nil else {
|
|
93
|
+
call.reject("Database '\(database)' is not connected")
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
guard let server = server else {
|
|
98
|
+
call.reject("Server is not running")
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
call.resolve([
|
|
103
|
+
"port": server.port,
|
|
104
|
+
"token": server.token
|
|
105
|
+
])
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@objc func execute(_ call: CAPPluginCall) {
|
|
109
|
+
guard let database = call.getString("database") else {
|
|
110
|
+
call.reject("Database name is required")
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
guard let statement = call.getString("statement") else {
|
|
115
|
+
call.reject("Statement is required")
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
guard let db = databases[database] else {
|
|
120
|
+
call.reject("Database '\(database)' is not connected")
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let params = call.getArray("params", JSValue.self) ?? []
|
|
125
|
+
|
|
126
|
+
do {
|
|
127
|
+
let result = try db.execute(statement: statement, params: params)
|
|
128
|
+
call.resolve(result)
|
|
129
|
+
} catch {
|
|
130
|
+
call.reject("Failed to execute statement: \(error.localizedDescription)")
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@objc func beginTransaction(_ call: CAPPluginCall) {
|
|
135
|
+
guard let database = call.getString("database") else {
|
|
136
|
+
call.reject("Database name is required")
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
guard let db = databases[database] else {
|
|
141
|
+
call.reject("Database '\(database)' is not connected")
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
do {
|
|
146
|
+
try db.beginTransaction()
|
|
147
|
+
call.resolve()
|
|
148
|
+
} catch {
|
|
149
|
+
call.reject("Failed to begin transaction: \(error.localizedDescription)")
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@objc func commitTransaction(_ call: CAPPluginCall) {
|
|
154
|
+
guard let database = call.getString("database") else {
|
|
155
|
+
call.reject("Database name is required")
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
guard let db = databases[database] else {
|
|
160
|
+
call.reject("Database '\(database)' is not connected")
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
do {
|
|
165
|
+
try db.commitTransaction()
|
|
166
|
+
call.resolve()
|
|
167
|
+
} catch {
|
|
168
|
+
call.reject("Failed to commit transaction: \(error.localizedDescription)")
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@objc func rollbackTransaction(_ call: CAPPluginCall) {
|
|
173
|
+
guard let database = call.getString("database") else {
|
|
174
|
+
call.reject("Database name is required")
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
guard let db = databases[database] else {
|
|
179
|
+
call.reject("Database '\(database)' is not connected")
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
do {
|
|
184
|
+
try db.rollbackTransaction()
|
|
185
|
+
call.resolve()
|
|
186
|
+
} catch {
|
|
187
|
+
call.reject("Failed to rollback transaction: \(error.localizedDescription)")
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private func getDatabasePath(_ database: String) throws -> String {
|
|
192
|
+
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
|
|
193
|
+
guard let documentsDirectory = paths.first else {
|
|
194
|
+
throw NSError(domain: "CapgoCapacitorNativeSQL", code: 1, userInfo: [
|
|
195
|
+
NSLocalizedDescriptionKey: "Could not find documents directory"
|
|
196
|
+
])
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
let dbPath = (documentsDirectory as NSString).appendingPathComponent("\(database).db")
|
|
200
|
+
return dbPath
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
import SQLite3
|
|
4
|
+
|
|
5
|
+
// SQLite constants that aren't imported to Swift
|
|
6
|
+
private let SQLITE_STATIC = unsafeBitCast(0, to: sqlite3_destructor_type.self)
|
|
7
|
+
private let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* SQLite database wrapper for iOS
|
|
11
|
+
*/
|
|
12
|
+
class SQLDatabase {
|
|
13
|
+
private var db: OpaquePointer?
|
|
14
|
+
private let path: String
|
|
15
|
+
private var inTransaction = false
|
|
16
|
+
|
|
17
|
+
init(path: String) throws {
|
|
18
|
+
self.path = path
|
|
19
|
+
|
|
20
|
+
let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX
|
|
21
|
+
let result = sqlite3_open_v2(path, &db, flags, nil)
|
|
22
|
+
|
|
23
|
+
guard result == SQLITE_OK else {
|
|
24
|
+
throw SQLError.openFailed(message: String(cString: sqlite3_errmsg(db)))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Enable foreign keys
|
|
28
|
+
try execute(statement: "PRAGMA foreign_keys = ON", params: [])
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
func close() {
|
|
32
|
+
if let db = db {
|
|
33
|
+
sqlite3_close(db)
|
|
34
|
+
self.db = nil
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func execute(statement: String, params: [Any]) throws -> [String: Any] {
|
|
39
|
+
guard let db = db else {
|
|
40
|
+
throw SQLError.notOpen
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
var stmt: OpaquePointer?
|
|
44
|
+
let result = sqlite3_prepare_v2(db, statement, -1, &stmt, nil)
|
|
45
|
+
|
|
46
|
+
guard result == SQLITE_OK else {
|
|
47
|
+
let error = String(cString: sqlite3_errmsg(db))
|
|
48
|
+
throw SQLError.prepareFailed(message: error)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
defer {
|
|
52
|
+
sqlite3_finalize(stmt)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Bind parameters
|
|
56
|
+
for (index, param) in params.enumerated() {
|
|
57
|
+
let bindResult = try bindParameter(stmt: stmt, index: Int32(index + 1), value: param)
|
|
58
|
+
if bindResult != SQLITE_OK {
|
|
59
|
+
let error = String(cString: sqlite3_errmsg(db))
|
|
60
|
+
throw SQLError.bindFailed(message: error)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Execute and collect rows
|
|
65
|
+
var rows: [[String: Any]] = []
|
|
66
|
+
var columnNames: [String] = []
|
|
67
|
+
var columnCount: Int32 = 0
|
|
68
|
+
|
|
69
|
+
while true {
|
|
70
|
+
let stepResult = sqlite3_step(stmt)
|
|
71
|
+
|
|
72
|
+
if stepResult == SQLITE_ROW {
|
|
73
|
+
if columnNames.isEmpty {
|
|
74
|
+
columnCount = sqlite3_column_count(stmt)
|
|
75
|
+
for i in 0..<columnCount {
|
|
76
|
+
if let name = sqlite3_column_name(stmt, i) {
|
|
77
|
+
columnNames.append(String(cString: name))
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
var row: [String: Any] = [:]
|
|
83
|
+
for i in 0..<columnCount {
|
|
84
|
+
let columnName = columnNames[Int(i)]
|
|
85
|
+
let value = getColumnValue(stmt: stmt, index: i)
|
|
86
|
+
row[columnName] = value
|
|
87
|
+
}
|
|
88
|
+
rows.append(row)
|
|
89
|
+
} else if stepResult == SQLITE_DONE {
|
|
90
|
+
break
|
|
91
|
+
} else {
|
|
92
|
+
let error = String(cString: sqlite3_errmsg(db))
|
|
93
|
+
throw SQLError.executeFailed(message: error)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let changes = sqlite3_changes(db)
|
|
98
|
+
let lastInsertId = sqlite3_last_insert_rowid(db)
|
|
99
|
+
|
|
100
|
+
return [
|
|
101
|
+
"rows": rows,
|
|
102
|
+
"rowsAffected": changes,
|
|
103
|
+
"insertId": lastInsertId > 0 ? lastInsertId : NSNull()
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
func beginTransaction() throws {
|
|
108
|
+
guard !inTransaction else {
|
|
109
|
+
throw SQLError.transactionAlreadyActive
|
|
110
|
+
}
|
|
111
|
+
try execute(statement: "BEGIN TRANSACTION", params: [])
|
|
112
|
+
inTransaction = true
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
func commitTransaction() throws {
|
|
116
|
+
guard inTransaction else {
|
|
117
|
+
throw SQLError.noTransactionActive
|
|
118
|
+
}
|
|
119
|
+
try execute(statement: "COMMIT", params: [])
|
|
120
|
+
inTransaction = false
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
func rollbackTransaction() throws {
|
|
124
|
+
guard inTransaction else {
|
|
125
|
+
throw SQLError.noTransactionActive
|
|
126
|
+
}
|
|
127
|
+
try execute(statement: "ROLLBACK", params: [])
|
|
128
|
+
inTransaction = false
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private func bindParameter(stmt: OpaquePointer?, index: Int32, value: Any) throws -> Int32 {
|
|
132
|
+
guard let stmt = stmt else {
|
|
133
|
+
return SQLITE_ERROR
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Handle null
|
|
137
|
+
if value is NSNull {
|
|
138
|
+
return sqlite3_bind_null(stmt, index)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Handle different types
|
|
142
|
+
if let str = value as? String {
|
|
143
|
+
return sqlite3_bind_text(stmt, index, str, -1, SQLITE_TRANSIENT)
|
|
144
|
+
} else if let num = value as? NSNumber {
|
|
145
|
+
// Check if it's a boolean
|
|
146
|
+
if CFGetTypeID(num as CFTypeRef) == CFBooleanGetTypeID() {
|
|
147
|
+
return sqlite3_bind_int(stmt, index, num.boolValue ? 1 : 0)
|
|
148
|
+
}
|
|
149
|
+
// Check if it's a double/float
|
|
150
|
+
if num.doubleValue != Double(num.intValue) {
|
|
151
|
+
return sqlite3_bind_double(stmt, index, num.doubleValue)
|
|
152
|
+
}
|
|
153
|
+
// It's an integer
|
|
154
|
+
return sqlite3_bind_int64(stmt, index, num.int64Value)
|
|
155
|
+
} else if let dict = value as? [String: Any],
|
|
156
|
+
let type = dict["_type"] as? String,
|
|
157
|
+
type == "binary",
|
|
158
|
+
let base64 = dict["_data"] as? String,
|
|
159
|
+
let data = Data(base64Encoded: base64) {
|
|
160
|
+
// Handle binary data
|
|
161
|
+
return data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in
|
|
162
|
+
sqlite3_bind_blob(stmt, index, bytes.baseAddress, Int32(data.count), SQLITE_TRANSIENT)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return SQLITE_ERROR
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private func getColumnValue(stmt: OpaquePointer?, index: Int32) -> Any {
|
|
170
|
+
guard let stmt = stmt else {
|
|
171
|
+
return NSNull()
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let type = sqlite3_column_type(stmt, index)
|
|
175
|
+
|
|
176
|
+
switch type {
|
|
177
|
+
case SQLITE_INTEGER:
|
|
178
|
+
return sqlite3_column_int64(stmt, index)
|
|
179
|
+
case SQLITE_FLOAT:
|
|
180
|
+
return sqlite3_column_double(stmt, index)
|
|
181
|
+
case SQLITE_TEXT:
|
|
182
|
+
if let text = sqlite3_column_text(stmt, index) {
|
|
183
|
+
return String(cString: text)
|
|
184
|
+
}
|
|
185
|
+
return NSNull()
|
|
186
|
+
case SQLITE_BLOB:
|
|
187
|
+
if let blob = sqlite3_column_blob(stmt, index) {
|
|
188
|
+
let size = sqlite3_column_bytes(stmt, index)
|
|
189
|
+
let data = Data(bytes: blob, count: Int(size))
|
|
190
|
+
return [
|
|
191
|
+
"_type": "binary",
|
|
192
|
+
"_data": data.base64EncodedString()
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
return NSNull()
|
|
196
|
+
case SQLITE_NULL:
|
|
197
|
+
return NSNull()
|
|
198
|
+
default:
|
|
199
|
+
return NSNull()
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* SQL error types
|
|
206
|
+
*/
|
|
207
|
+
enum SQLError: Error {
|
|
208
|
+
case notOpen
|
|
209
|
+
case openFailed(message: String)
|
|
210
|
+
case prepareFailed(message: String)
|
|
211
|
+
case bindFailed(message: String)
|
|
212
|
+
case executeFailed(message: String)
|
|
213
|
+
case transactionAlreadyActive
|
|
214
|
+
case noTransactionActive
|
|
215
|
+
}
|