@capgo/capacitor-fast-sql 8.2.17 → 8.3.0

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.
Files changed (34) hide show
  1. package/Package.swift +2 -1
  2. package/README.md +27 -18
  3. package/android/build.gradle +4 -0
  4. package/android/consumer-rules.pro +14 -0
  5. package/android/src/main/java/app/capgo/capacitor/fastsql/BinaryFrame.java +152 -0
  6. package/android/src/main/java/app/capgo/capacitor/fastsql/CapgoCapacitorFastSqlPlugin.java +5 -1
  7. package/android/src/main/java/app/capgo/capacitor/fastsql/DatabaseConnection.java +2 -0
  8. package/android/src/main/java/app/capgo/capacitor/fastsql/EncryptedSQLDatabase.java +13 -2
  9. package/android/src/main/java/app/capgo/capacitor/fastsql/SQLDatabase.java +13 -2
  10. package/android/src/main/java/app/capgo/capacitor/fastsql/SQLHTTPServer.java +114 -57
  11. package/dist/esm/binary-frame.d.ts +6 -0
  12. package/dist/esm/binary-frame.d.ts.map +1 -0
  13. package/dist/esm/binary-frame.js +100 -0
  14. package/dist/esm/definitions.d.ts +28 -0
  15. package/dist/esm/definitions.d.ts.map +1 -1
  16. package/dist/esm/fast-sql.d.ts.map +1 -1
  17. package/dist/esm/fast-sql.js +13 -3
  18. package/dist/esm/native-sql-connection.d.ts +18 -7
  19. package/dist/esm/native-sql-connection.d.ts.map +1 -1
  20. package/dist/esm/native-sql-connection.js +145 -59
  21. package/dist/esm/web.d.ts +2 -0
  22. package/dist/esm/web.d.ts.map +1 -1
  23. package/dist/esm/web.js +24 -2
  24. package/dist/plugin.cjs.js +282 -64
  25. package/dist/plugin.cjs.js.map +1 -1
  26. package/dist/plugin.js +282 -64
  27. package/dist/plugin.js.map +1 -1
  28. package/ios/Sources/CapgoCapacitorFastSqlPlugin/BinaryFrame.swift +95 -0
  29. package/ios/Sources/CapgoCapacitorFastSqlPlugin/CapgoCapacitorFastSqlPlugin.swift +7 -3
  30. package/ios/Sources/CapgoCapacitorFastSqlPlugin/SQLDatabase.swift +20 -3
  31. package/ios/Sources/CapgoCapacitorFastSqlPlugin/SQLHTTPServer.swift +59 -76
  32. package/ios/Tests/CapgoCapacitorFastSqlPluginTests/BinaryFrameTests.swift +65 -0
  33. package/ios/Tests/CapgoCapacitorFastSqlPluginTests/Fixtures/binary-v1.bin +0 -0
  34. package/package.json +5 -2
package/Package.swift CHANGED
@@ -25,6 +25,7 @@ let package = Package(
25
25
  .testTarget(
26
26
  name: "CapgoCapacitorFastSqlPluginTests",
27
27
  dependencies: ["CapgoCapacitorFastSqlPlugin"],
28
- path: "ios/Tests/CapgoCapacitorFastSqlPluginTests")
28
+ path: "ios/Tests/CapgoCapacitorFastSqlPluginTests",
29
+ resources: [.copy("Fixtures")])
29
30
  ]
30
31
  )
package/README.md CHANGED
@@ -383,7 +383,7 @@ bypassing Capacitor's standard bridge for better performance with sync operation
383
383
  ### connect(...)
384
384
 
385
385
  ```typescript
386
- connect(options: SQLConnectionOptions) => Promise<{ port: number; token: string; database: string; }>
386
+ connect(options: SQLConnectionOptions) => Promise<{ port: number; token: string; database: string; persistent?: boolean; binaryProtocolVersion?: number; }>
387
387
  ```
388
388
 
389
389
  Initialize the database connection and start the HTTP server.
@@ -392,7 +392,7 @@ Initialize the database connection and start the HTTP server.
392
392
  | ------------- | --------------------------------------------------------------------- | -------------------- |
393
393
  | **`options`** | <code><a href="#sqlconnectionoptions">SQLConnectionOptions</a></code> | - Connection options |
394
394
 
395
- **Returns:** <code>Promise&lt;{ port: number; token: string; database: string; }&gt;</code>
395
+ **Returns:** <code>Promise&lt;{ port: number; token: string; database: string; persistent?: boolean; binaryProtocolVersion?: number; }&gt;</code>
396
396
 
397
397
  **Since:** 0.0.1
398
398
 
@@ -419,7 +419,7 @@ Close database connection and stop the HTTP server.
419
419
  ### getServerInfo(...)
420
420
 
421
421
  ```typescript
422
- getServerInfo(options: { database: string; }) => Promise<{ port: number; token: string; }>
422
+ getServerInfo(options: { database: string; }) => Promise<{ port: number; token: string; persistent?: boolean; binaryProtocolVersion?: number; }>
423
423
  ```
424
424
 
425
425
  Get the HTTP server port and token for direct communication.
@@ -428,7 +428,7 @@ Get the HTTP server port and token for direct communication.
428
428
  | ------------- | ---------------------------------- | --------------- |
429
429
  | **`options`** | <code>{ database: string; }</code> | - Database name |
430
430
 
431
- **Returns:** <code>Promise&lt;{ port: number; token: string; }&gt;</code>
431
+ **Returns:** <code>Promise&lt;{ port: number; token: string; persistent?: boolean; binaryProtocolVersion?: number; }&gt;</code>
432
432
 
433
433
  **Since:** 0.0.1
434
434
 
@@ -509,12 +509,12 @@ Rollback the current transaction.
509
509
  ### getPluginVersion()
510
510
 
511
511
  ```typescript
512
- getPluginVersion() => Promise<{ version: string; }>
512
+ getPluginVersion() => Promise<{ version: string; binaryProtocolVersion?: number; }>
513
513
  ```
514
514
 
515
515
  Get the native Capacitor plugin version.
516
516
 
517
- **Returns:** <code>Promise&lt;{ version: string; }&gt;</code>
517
+ **Returns:** <code>Promise&lt;{ version: string; binaryProtocolVersion?: number; }&gt;</code>
518
518
 
519
519
  **Since:** 0.0.1
520
520
 
@@ -548,14 +548,15 @@ or supply a custom Worker. This method is a no-op on iOS and Android.
548
548
 
549
549
  Database connection options
550
550
 
551
- | Prop | Type | Description | Since |
552
- | ------------------------ | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ |
553
- | **`database`** | <code>string</code> | Database name (file will be created in app data directory) | |
554
- | **`encrypted`** | <code>boolean</code> | Enable encryption (iOS/Android only) | |
555
- | **`encryptionKey`** | <code>string</code> | Encryption key (required if encrypted is true) | |
556
- | **`readOnly`** | <code>boolean</code> | Open an existing native database read-only. Writes and missing-file opens fail. WAL mode changes are skipped for read-only connections. The web Worker transport rejects this option instead of silently opening writable. | |
557
- | **`walMode`** | <code>boolean</code> | Enable WAL (Write-Ahead Logging) journal mode for better concurrency and performance. Executes `PRAGMA journal_mode = WAL` immediately after opening the database. | 8.0.49 |
558
- | **`performancePresets`** | <code>boolean</code> | Apply recommended SQLite performance PRAGMAs after opening the database: `synchronous = NORMAL`, `busy_timeout = 5000`, `cache_size = -2000`, and `foreign_keys = ON`. | 8.0.49 |
551
+ | Prop | Type | Description | Default | Since |
552
+ | ------------------------ | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------ | ------ |
553
+ | **`transport`** | <code><a href="#nativesqltransport">NativeSQLTransport</a></code> | Native HTTP data encoding. Binary v1 sends BLOBs as raw bytes without base64. Opt-in on iOS/Android only; requires a native plugin advertising binary v1. JSON remains the default. Binary is experimental; reconsider the default only in a future major after device performance and conformance testing. No failed write is retried on another transport. | <code>'http-json'</code> | |
554
+ | **`database`** | <code>string</code> | Database name (file will be created in app data directory) | | |
555
+ | **`encrypted`** | <code>boolean</code> | Enable encryption (iOS/Android only) | | |
556
+ | **`encryptionKey`** | <code>string</code> | Encryption key (required if encrypted is true) | | |
557
+ | **`readOnly`** | <code>boolean</code> | Open an existing native database read-only. Writes and missing-file opens fail. WAL mode changes are skipped for read-only connections. The web Worker transport rejects this option instead of silently opening writable. | | |
558
+ | **`walMode`** | <code>boolean</code> | Enable WAL (Write-Ahead Logging) journal mode for better concurrency and performance. Executes `PRAGMA journal_mode = WAL` immediately after opening the database. | | 8.0.49 |
559
+ | **`performancePresets`** | <code>boolean</code> | Apply recommended SQLite performance PRAGMAs after opening the database: `synchronous = NORMAL`, `busy_timeout = 5000`, `cache_size = -2000`, and `foreign_keys = ON`. | | 8.0.49 |
559
560
 
560
561
 
561
562
  #### SQLResult
@@ -658,15 +659,23 @@ OPFS persistence requires Cross-Origin Isolation headers on your web server:
658
659
  `Cross-Origin-Opener-Policy: same-origin` and
659
660
  `Cross-Origin-Embedder-Policy: require-corp`.
660
661
 
661
- | Prop | Type | Description | Default |
662
- | ------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------- |
663
- | **`useOpfs`** | <code>boolean</code> | Prefer OPFS persistence via a Web Worker (default: `true`). When OPFS is unavailable, the plugin falls back to a non-persistent database and logs a warning. | <code>true</code> |
664
- | **`worker`** | <code><a href="#sqlitewebworker">SqliteWebWorker</a></code> | Custom Worker instance or factory for SQLite Wasm. Advanced: override the default worker from `@sqlite.org/sqlite-wasm`. | |
662
+ | Prop | Type | Description | Default |
663
+ | ------------------------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ |
664
+ | **`useOpfs`** | <code>boolean</code> | Prefer OPFS persistence via a Web Worker (default: `true`). When OPFS is unavailable, the plugin falls back to a non-persistent database and logs a warning. | <code>true</code> |
665
+ | **`requirePersistence`** | <code>boolean</code> | Reject web connections unless the Worker confirms persistent storage. Defaults to false for compatibility. Consider requiring persistence by default in the next Capacitor major, rather than changing fallback behavior in a minor. | <code>false</code> |
666
+ | **`worker`** | <code><a href="#sqlitewebworker">SqliteWebWorker</a></code> | Custom Worker instance or factory for SQLite Wasm. Advanced: override the default worker from `@sqlite.org/sqlite-wasm`. | |
665
667
 
666
668
 
667
669
  ### Type Aliases
668
670
 
669
671
 
672
+ #### NativeSQLTransport
673
+
674
+ Native HTTP data encoding. JSON remains the compatibility default.
675
+
676
+ <code>'http-json' | 'http-binary'</code>
677
+
678
+
670
679
  #### SQLValue
671
680
 
672
681
  SQL value types supported by the plugin
@@ -26,6 +26,7 @@ android {
26
26
  versionCode 1
27
27
  versionName "1.0"
28
28
  testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
29
+ consumerProguardFiles 'consumer-rules.pro'
29
30
  }
30
31
  buildTypes {
31
32
  release {
@@ -40,6 +41,9 @@ android {
40
41
  sourceCompatibility JavaVersion.VERSION_21
41
42
  targetCompatibility JavaVersion.VERSION_21
42
43
  }
44
+ sourceSets {
45
+ test.resources.srcDir "../ios/Tests/CapgoCapacitorFastSqlPluginTests/Fixtures"
46
+ }
43
47
  testOptions {
44
48
  unitTests.includeAndroidResources = true
45
49
  }
@@ -0,0 +1,14 @@
1
+ # Consumer ProGuard rules for @capgo/capacitor-fast-sql (merged into app R8 when this AAR is a dependency).
2
+
3
+ # CapgoCapacitorFastSqlPlugin uses Class.forName on this class when encrypted databases are enabled.
4
+ # R8 must keep the type so the optional SQLCipher dependency still resolves at runtime.
5
+ -keep class net.zetetic.database.sqlcipher.SQLiteDatabase { *; }
6
+
7
+ # SQLCipher is compileOnly and also exposes types such as DatabaseErrorHandler
8
+ # directly under net.zetetic.database. Plain-SQLite apps omit the entire artifact.
9
+ -dontwarn net.zetetic.database.**
10
+
11
+ # EncryptedSQLDatabase loads the SQLCipher JNI library by name; keep JNI method names linked from Java.
12
+ -keepclasseswithmembernames class * {
13
+ native <methods>;
14
+ }
@@ -0,0 +1,152 @@
1
+ package app.capgo.capacitor.fastsql;
2
+
3
+ import java.io.ByteArrayOutputStream;
4
+ import java.io.IOException;
5
+ import java.nio.ByteBuffer;
6
+ import java.nio.ByteOrder;
7
+ import java.nio.charset.CodingErrorAction;
8
+ import java.nio.charset.StandardCharsets;
9
+ import java.util.Iterator;
10
+ import org.json.JSONArray;
11
+ import org.json.JSONObject;
12
+ import org.json.JSONTokener;
13
+
14
+ /** Shared binary v1 envelope. Byte arrays never pass through base64 or JSON. */
15
+ final class BinaryFrame {
16
+
17
+ static final String CONTENT_TYPE = "application/vnd.capgo.fast-sql.v1+binary";
18
+ static final int MAX_BYTES = 64 * 1024 * 1024;
19
+ private static final int MAX_METADATA = 1024 * 1024;
20
+ private static final int HEADER = 12;
21
+ private static final byte[] MAGIC = { 70, 83, 81, 49 };
22
+ private int nodes;
23
+ private int blobs;
24
+ private long referencedBytes;
25
+
26
+ static Object decode(byte[] frame) throws Exception {
27
+ if (frame.length < HEADER || frame.length > MAX_BYTES) throw new IOException("Invalid binary frame length");
28
+ for (int i = 0; i < MAGIC.length; i++) if (frame[i] != MAGIC[i]) throw new IOException("Invalid binary frame version");
29
+ ByteBuffer header = ByteBuffer.wrap(frame).order(ByteOrder.LITTLE_ENDIAN);
30
+ int metadataLength = header.getInt(4);
31
+ int payloadLength = header.getInt(8);
32
+ if (
33
+ metadataLength <= 0 ||
34
+ metadataLength > MAX_METADATA ||
35
+ payloadLength < 0 ||
36
+ (long) HEADER + metadataLength + payloadLength != frame.length
37
+ ) throw new IOException("Invalid binary frame lengths");
38
+ String metadata = StandardCharsets.UTF_8.newDecoder()
39
+ .onMalformedInput(CodingErrorAction.REPORT)
40
+ .onUnmappableCharacter(CodingErrorAction.REPORT)
41
+ .decode(ByteBuffer.wrap(frame, HEADER, metadataLength))
42
+ .toString();
43
+ checkNesting(metadata);
44
+ JSONTokener parser = new JSONTokener(metadata);
45
+ Object value = parser.nextValue();
46
+ if (parser.nextClean() != 0) throw new IOException("Trailing binary metadata");
47
+ return new BinaryFrame().decodeValue(value, frame, HEADER + metadataLength, payloadLength, 0);
48
+ }
49
+
50
+ private static void checkNesting(String json) throws IOException {
51
+ boolean quoted = false;
52
+ boolean escaped = false;
53
+ int depth = 0;
54
+ for (int i = 0; i < json.length(); i++) {
55
+ char value = json.charAt(i);
56
+ if (quoted) {
57
+ if (escaped) escaped = false;
58
+ else if (value == 92) escaped = true;
59
+ else if (value == 34) quoted = false;
60
+ } else if (value == 34) quoted = true;
61
+ else if (value == '[' || value == '{') {
62
+ if (++depth > 33) throw new IOException("Binary metadata nesting exceeds limits");
63
+ } else if (value == ']' || value == '}') depth--;
64
+ }
65
+ }
66
+
67
+ private Object decodeValue(Object value, byte[] frame, int payloadStart, int payloadLength, int depth) throws Exception {
68
+ if (++nodes > 100000 || depth > 32) throw new IOException("Binary metadata exceeds limits");
69
+ if (value instanceof JSONArray) {
70
+ JSONArray input = (JSONArray) value;
71
+ JSONArray result = new JSONArray();
72
+ for (int i = 0; i < input.length(); i++) result.put(decodeValue(input.get(i), frame, payloadStart, payloadLength, depth + 1));
73
+ return result;
74
+ }
75
+ if (!(value instanceof JSONObject)) return value;
76
+ JSONObject input = (JSONObject) value;
77
+ if (input.has("$blob") && input.length() == 1) {
78
+ JSONArray reference = input.getJSONArray("$blob");
79
+ if (
80
+ reference.length() != 2 || !(reference.get(0) instanceof Number) || !(reference.get(1) instanceof Number)
81
+ ) throw new IOException("Invalid binary attachment reference");
82
+ double offset = reference.getDouble(0);
83
+ double length = reference.getDouble(1);
84
+ if (
85
+ !Double.isFinite(offset) ||
86
+ !Double.isFinite(length) ||
87
+ offset < 0 ||
88
+ length < 0 ||
89
+ offset != Math.floor(offset) ||
90
+ length != Math.floor(length) ||
91
+ offset + length > payloadLength
92
+ ) throw new IOException("Binary attachment outside frame");
93
+ referencedBytes += (long) length;
94
+ if (++blobs > 4096 || referencedBytes > MAX_BYTES) throw new IOException("Decoded attachments exceed limits");
95
+ return java.util.Arrays.copyOfRange(frame, payloadStart + (int) offset, payloadStart + (int) offset + (int) length);
96
+ }
97
+ JSONObject result = new JSONObject();
98
+ Iterator<String> keys = input.keys();
99
+ while (keys.hasNext()) {
100
+ String key = keys.next();
101
+ result.put(key, decodeValue(input.get(key), frame, payloadStart, payloadLength, depth + 1));
102
+ }
103
+ return result;
104
+ }
105
+
106
+ static byte[] encode(Object value) throws Exception {
107
+ ByteArrayOutputStream attachments = new ByteArrayOutputStream();
108
+ BinaryFrame codec = new BinaryFrame();
109
+ Object metadataValue = codec.encodeValue(value, attachments, 0);
110
+ String wrapped = new JSONArray().put(metadataValue).toString();
111
+ byte[] metadata = wrapped.substring(1, wrapped.length() - 1).getBytes(StandardCharsets.UTF_8);
112
+ long total = (long) HEADER + metadata.length + attachments.size();
113
+ if (metadata.length > MAX_METADATA || total > MAX_BYTES) throw new IOException("Binary frame exceeds limits");
114
+ ByteArrayOutputStream output = new ByteArrayOutputStream((int) total);
115
+ output.write(MAGIC);
116
+ output.write(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putInt(metadata.length).putInt(attachments.size()).array());
117
+ output.write(metadata);
118
+ attachments.writeTo(output);
119
+ return output.toByteArray();
120
+ }
121
+
122
+ private Object encodeValue(Object value, ByteArrayOutputStream attachments, int depth) throws Exception {
123
+ if (++nodes > 100000 || depth > 32) throw new IOException("Binary metadata exceeds limits");
124
+ if (value instanceof byte[]) {
125
+ byte[] bytes = (byte[]) value;
126
+ if (++blobs > 4096 || (long) attachments.size() + bytes.length > MAX_BYTES - HEADER) throw new IOException(
127
+ "Binary attachments exceed limits"
128
+ );
129
+ JSONObject reference = new JSONObject();
130
+ reference.put("$blob", new JSONArray().put(attachments.size()).put(bytes.length));
131
+ attachments.write(bytes);
132
+ return reference;
133
+ }
134
+ if (value instanceof JSONArray) {
135
+ JSONArray input = (JSONArray) value;
136
+ JSONArray result = new JSONArray();
137
+ for (int i = 0; i < input.length(); i++) result.put(encodeValue(input.get(i), attachments, depth + 1));
138
+ return result;
139
+ }
140
+ if (value instanceof JSONObject) {
141
+ JSONObject input = (JSONObject) value;
142
+ JSONObject result = new JSONObject();
143
+ Iterator<String> keys = input.keys();
144
+ while (keys.hasNext()) {
145
+ String key = keys.next();
146
+ result.put(key, encodeValue(input.get(key), attachments, depth + 1));
147
+ }
148
+ return result;
149
+ }
150
+ return value;
151
+ }
152
+ }
@@ -19,7 +19,7 @@ import java.util.Map;
19
19
  @CapacitorPlugin(name = "CapgoCapacitorFastSql")
20
20
  public class CapgoCapacitorFastSqlPlugin extends Plugin {
21
21
 
22
- private final String pluginVersion = "8.2.17";
22
+ private final String pluginVersion = "8.3.0";
23
23
 
24
24
  private Map<String, DatabaseConnection> databases = new HashMap<>();
25
25
  private SQLHTTPServer server;
@@ -51,6 +51,7 @@ public class CapgoCapacitorFastSqlPlugin extends Plugin {
51
51
  JSObject ret = new JSObject();
52
52
  ret.put("port", server.getPort());
53
53
  ret.put("token", server.getToken());
54
+ ret.put("binaryProtocolVersion", 1);
54
55
  ret.put("database", database);
55
56
  call.resolve(ret);
56
57
  return;
@@ -89,6 +90,7 @@ public class CapgoCapacitorFastSqlPlugin extends Plugin {
89
90
  JSObject ret = new JSObject();
90
91
  ret.put("port", server.getPort());
91
92
  ret.put("token", server.getToken());
93
+ ret.put("binaryProtocolVersion", 1);
92
94
  ret.put("database", database);
93
95
  call.resolve(ret);
94
96
  } catch (Exception e) {
@@ -143,6 +145,7 @@ public class CapgoCapacitorFastSqlPlugin extends Plugin {
143
145
  JSObject ret = new JSObject();
144
146
  ret.put("port", server.getPort());
145
147
  ret.put("token", server.getToken());
148
+ ret.put("binaryProtocolVersion", 1);
146
149
  call.resolve(ret);
147
150
  }
148
151
 
@@ -256,6 +259,7 @@ public class CapgoCapacitorFastSqlPlugin extends Plugin {
256
259
  public void getPluginVersion(PluginCall call) {
257
260
  JSObject ret = new JSObject();
258
261
  ret.put("version", pluginVersion);
262
+ ret.put("binaryProtocolVersion", 1);
259
263
  call.resolve(ret);
260
264
  }
261
265
 
@@ -6,6 +6,8 @@ import com.getcapacitor.JSObject;
6
6
  public interface DatabaseConnection {
7
7
  JSObject execute(String statement, JSArray params) throws Exception;
8
8
 
9
+ JSObject executeBinary(String statement, JSArray params) throws Exception;
10
+
9
11
  void beginTransaction() throws Exception;
10
12
 
11
13
  void commitTransaction() throws Exception;
@@ -56,6 +56,14 @@ public class EncryptedSQLDatabase implements DatabaseConnection {
56
56
  }
57
57
 
58
58
  public JSObject execute(String statement, JSArray params) throws Exception {
59
+ return execute(statement, params, false);
60
+ }
61
+
62
+ public JSObject executeBinary(String statement, JSArray params) throws Exception {
63
+ return execute(statement, params, true);
64
+ }
65
+
66
+ private JSObject execute(String statement, JSArray params, boolean binaryResults) throws Exception {
59
67
  if (db == null || !db.isOpen()) {
60
68
  throw new Exception("Database is not open");
61
69
  }
@@ -83,7 +91,7 @@ public class EncryptedSQLDatabase implements DatabaseConnection {
83
91
  JSObject row = new JSObject();
84
92
  for (int i = 0; i < cursor.getColumnCount(); i++) {
85
93
  String columnName = cursor.getColumnName(i);
86
- Object value = getColumnValue(cursor, i);
94
+ Object value = getColumnValue(cursor, i, binaryResults);
87
95
  row.put(columnName, value);
88
96
  }
89
97
  rows.put(row);
@@ -165,6 +173,8 @@ public class EncryptedSQLDatabase implements DatabaseConnection {
165
173
 
166
174
  if (value == null || value == JSONObject.NULL) {
167
175
  stmt.bindNull(index);
176
+ } else if (value instanceof byte[]) {
177
+ stmt.bindBlob(index, (byte[]) value);
168
178
  } else if (value instanceof String) {
169
179
  stmt.bindString(index, (String) value);
170
180
  } else if (value instanceof Integer) {
@@ -192,7 +202,7 @@ public class EncryptedSQLDatabase implements DatabaseConnection {
192
202
  }
193
203
  }
194
204
 
195
- private Object getColumnValue(Cursor cursor, int index) {
205
+ private Object getColumnValue(Cursor cursor, int index, boolean binaryResults) {
196
206
  int type = cursor.getType(index);
197
207
  switch (type) {
198
208
  case Cursor.FIELD_TYPE_NULL:
@@ -205,6 +215,7 @@ public class EncryptedSQLDatabase implements DatabaseConnection {
205
215
  return cursor.getString(index);
206
216
  case Cursor.FIELD_TYPE_BLOB:
207
217
  byte[] blob = cursor.getBlob(index);
218
+ if (binaryResults) return blob;
208
219
  String base64 = Base64.encodeToString(blob, Base64.NO_WRAP);
209
220
  JSObject blobObj = new JSObject();
210
221
  blobObj.put("_type", "binary");
@@ -41,6 +41,14 @@ public class SQLDatabase implements DatabaseConnection {
41
41
  }
42
42
 
43
43
  public JSObject execute(String statement, JSArray params) throws Exception {
44
+ return execute(statement, params, false);
45
+ }
46
+
47
+ public JSObject executeBinary(String statement, JSArray params) throws Exception {
48
+ return execute(statement, params, true);
49
+ }
50
+
51
+ private JSObject execute(String statement, JSArray params, boolean binaryResults) throws Exception {
44
52
  if (db == null || !db.isOpen()) {
45
53
  throw new Exception("Database is not open");
46
54
  }
@@ -69,7 +77,7 @@ public class SQLDatabase implements DatabaseConnection {
69
77
  JSObject row = new JSObject();
70
78
  for (int i = 0; i < cursor.getColumnCount(); i++) {
71
79
  String columnName = cursor.getColumnName(i);
72
- Object value = getColumnValue(cursor, i);
80
+ Object value = getColumnValue(cursor, i, binaryResults);
73
81
  row.put(columnName, value);
74
82
  }
75
83
  rows.put(row);
@@ -155,6 +163,8 @@ public class SQLDatabase implements DatabaseConnection {
155
163
 
156
164
  if (value == null || value == JSONObject.NULL) {
157
165
  stmt.bindNull(index);
166
+ } else if (value instanceof byte[]) {
167
+ stmt.bindBlob(index, (byte[]) value);
158
168
  } else if (value instanceof String) {
159
169
  stmt.bindString(index, (String) value);
160
170
  } else if (value instanceof Integer) {
@@ -183,7 +193,7 @@ public class SQLDatabase implements DatabaseConnection {
183
193
  }
184
194
  }
185
195
 
186
- private Object getColumnValue(Cursor cursor, int index) {
196
+ private Object getColumnValue(Cursor cursor, int index, boolean binaryResults) {
187
197
  int type = cursor.getType(index);
188
198
  switch (type) {
189
199
  case Cursor.FIELD_TYPE_NULL:
@@ -196,6 +206,7 @@ public class SQLDatabase implements DatabaseConnection {
196
206
  return cursor.getString(index);
197
207
  case Cursor.FIELD_TYPE_BLOB:
198
208
  byte[] blob = cursor.getBlob(index);
209
+ if (binaryResults) return blob;
199
210
  String base64 = Base64.encodeToString(blob, Base64.NO_WRAP);
200
211
  JSObject blobObj = new JSObject();
201
212
  blobObj.put("_type", "binary");