expo-sqlite 58.0.8 → 58.0.10
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/CHANGELOG.md +18 -0
- package/android/build.gradle +2 -2
- package/android/src/main/cpp/NativeDatabaseBinding.cpp +0 -22
- package/android/src/main/cpp/NativeDatabaseBinding.h +0 -1
- package/android/src/main/cpp/NativeStatementBinding.cpp +3 -1
- package/android/src/main/cpp/NativeStatementBinding.h +1 -1
- package/android/src/main/java/expo/modules/sqlite/NativeDatabase.kt +5 -0
- package/android/src/main/java/expo/modules/sqlite/NativeDatabaseBinding.kt +0 -1
- package/android/src/main/java/expo/modules/sqlite/NativeStatement.kt +9 -1
- package/android/src/main/java/expo/modules/sqlite/SQLiteModule.kt +78 -48
- package/dev-plugin-dist/+not-found.html +1 -1
- package/dev-plugin-dist/_expo/static/js/web/{__common-b5c30377ac28ec1a3c7472c7feddcab4.js → __common-9b94dd2fbdbb825a662f42cf8d2077ee.js} +36 -36
- package/dev-plugin-dist/_expo/static/js/web/{_layout-bdc15c267ba9298fc3df49fa915a8895.js → _layout-b2894030c1dfbd95b717a85901b6c271.js} +21 -21
- package/dev-plugin-dist/_expo/static/js/web/{entry-182fd72f164b90bb353bfd4ca3257c12.js → entry-04fdb7d857a65c0ed638ef2d774a4b6e.js} +547 -550
- package/dev-plugin-dist/_expo/static/js/web/{index-5d44464b52f351ee1a8d579d6ec31de6.js → index-4bf2da199f699eb47a42074d5e183c07.js} +1740 -1740
- package/dev-plugin-dist/_expo/static/js/web/{worker-634941622d077e92764ccbb4960edffc.js → worker-d51aa6be89e271e571111274ab8e7f5c.js} +17 -17
- package/dev-plugin-dist/_sitemap.html +1 -1
- package/dev-plugin-dist/index.html +1 -1
- package/ios/Benchmarks/JavaScriptBenchmarks.swift +65 -0
- package/ios/DatabaseChangeEvent.swift +4 -3
- package/ios/NativeDatabase.swift +14 -5
- package/ios/NativeStatement.swift +179 -15
- package/ios/SQLiteBindValue.swift +39 -0
- package/ios/SQLiteColumnValue.swift +54 -0
- package/ios/SQLiteModule.swift +40 -227
- package/ios/Tests/NativeStatementJavaScriptTests.swift +124 -0
- package/ios/Tests/NativeStatementTests.swift +122 -0
- package/ios/Tests/SQLiteModuleTests.swift +18 -1
- package/ios/Tests/test_StatementCleanup.swift +153 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10-sources.jar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10-sources.jar.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10-sources.jar.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10-sources.jar.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10-sources.jar.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.aar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.aar.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.aar.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.aar.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.aar.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/{58.0.8/expo.modules.sqlite-58.0.8.module → 58.0.10/expo.modules.sqlite-58.0.10.module} +22 -22
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.module.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.module.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.module.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.module.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/{58.0.8/expo.modules.sqlite-58.0.8.pom → 58.0.10/expo.modules.sqlite-58.0.10.pom} +1 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.pom.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.pom.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.pom.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.pom.sha512 +1 -0
- package/package.json +4 -4
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.8/expo.modules.sqlite-58.0.8-sources.jar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.8/expo.modules.sqlite-58.0.8-sources.jar.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.8/expo.modules.sqlite-58.0.8-sources.jar.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.8/expo.modules.sqlite-58.0.8-sources.jar.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.8/expo.modules.sqlite-58.0.8-sources.jar.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.8/expo.modules.sqlite-58.0.8.aar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.8/expo.modules.sqlite-58.0.8.aar.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.8/expo.modules.sqlite-58.0.8.aar.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.8/expo.modules.sqlite-58.0.8.aar.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.8/expo.modules.sqlite-58.0.8.aar.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.8/expo.modules.sqlite-58.0.8.module.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.8/expo.modules.sqlite-58.0.8.module.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.8/expo.modules.sqlite-58.0.8.module.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.8/expo.modules.sqlite-58.0.8.module.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.8/expo.modules.sqlite-58.0.8.pom.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.8/expo.modules.sqlite-58.0.8.pom.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.8/expo.modules.sqlite-58.0.8.pom.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.8/expo.modules.sqlite-58.0.8.pom.sha512 +0 -1
|
@@ -314,5 +314,5 @@ input::-webkit-search-cancel-button,input::-webkit-search-decoration,input::-web
|
|
|
314
314
|
@keyframes r-imtty0{0%{opacity:0;}100%{opacity:1;}}
|
|
315
315
|
@keyframes r-q67da2{0%{transform:translateX(-100%);}100%{transform:translateX(400%);}}
|
|
316
316
|
@keyframes r-t2lo5v{0%{opacity:1;}100%{opacity:0;}}</style><script type="module">globalThis.__EXPO_ROUTER_HYDRATE__=true;</script><link rel="preload" href="/_expo/plugins/expo-sqlite/_expo/static/css/global-b1bdf830cdbd0ec4238cc5f230f99460.css" as="style">
|
|
317
|
-
<link rel="stylesheet" href="/_expo/plugins/expo-sqlite/_expo/static/css/global-b1bdf830cdbd0ec4238cc5f230f99460.css"></head><body><div id="root"><div class="css-g5y9jx r-13awgt0"></div></div><script src="/_expo/plugins/expo-sqlite/_expo/static/js/web/__expo-metro-runtime-8c088df87a31e4e6accd81c554ec290d.js" defer></script><script src="/_expo/plugins/expo-sqlite/_expo/static/js/web/_layout-
|
|
317
|
+
<link rel="stylesheet" href="/_expo/plugins/expo-sqlite/_expo/static/css/global-b1bdf830cdbd0ec4238cc5f230f99460.css"></head><body><div id="root"><div class="css-g5y9jx r-13awgt0"></div></div><script src="/_expo/plugins/expo-sqlite/_expo/static/js/web/__expo-metro-runtime-8c088df87a31e4e6accd81c554ec290d.js" defer></script><script src="/_expo/plugins/expo-sqlite/_expo/static/js/web/_layout-b2894030c1dfbd95b717a85901b6c271.js" defer></script><script src="/_expo/plugins/expo-sqlite/_expo/static/js/web/__common-9b94dd2fbdbb825a662f42cf8d2077ee.js" defer></script><script src="/_expo/plugins/expo-sqlite/_expo/static/js/web/entry-04fdb7d857a65c0ed638ef2d774a4b6e.js" defer></script>
|
|
318
318
|
</body></html>
|
|
@@ -314,5 +314,5 @@ input::-webkit-search-cancel-button,input::-webkit-search-decoration,input::-web
|
|
|
314
314
|
@keyframes r-imtty0{0%{opacity:0;}100%{opacity:1;}}
|
|
315
315
|
@keyframes r-q67da2{0%{transform:translateX(-100%);}100%{transform:translateX(400%);}}
|
|
316
316
|
@keyframes r-t2lo5v{0%{opacity:1;}100%{opacity:0;}}</style><script type="module">globalThis.__EXPO_ROUTER_HYDRATE__=true;</script><link rel="preload" href="/_expo/plugins/expo-sqlite/_expo/static/css/global-b1bdf830cdbd0ec4238cc5f230f99460.css" as="style">
|
|
317
|
-
<link rel="stylesheet" href="/_expo/plugins/expo-sqlite/_expo/static/css/global-b1bdf830cdbd0ec4238cc5f230f99460.css"></head><body><div id="root"><div class="css-g5y9jx r-13awgt0"><!--$--><!--$--><div class="flex min-h-screen items-center justify-center bg-linear-to-br from-slate-50 to-slate-100 p-4"><div class="rounded-xl border bg-card text-card-foreground shadow w-full max-w-2xl"><div class="flex flex-col space-y-1.5 p-6 text-center"><div class="font-semibold tracking-tight text-3xl">SQLite Database Inspector</div><div class="text-muted-foreground text-base">Upload a database file or connect to your app</div></div><div class="p-6 pt-0"><div dir="ltr" data-orientation="horizontal" class="w-full"><div role="tablist" aria-orientation="horizontal" class="h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground grid w-full grid-cols-2" tabindex="-1" data-orientation="horizontal" style="outline:none"><button type="button" role="tab" aria-selected="true" aria-controls="radix-_R_jer3_-content-devtools" data-state="active" data-disabled="" disabled="" id="radix-_R_jer3_-trigger-devtools" class="inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow" tabindex="-1" data-orientation="horizontal" data-radix-collection-item="">From App </button><button type="button" role="tab" aria-selected="false" aria-controls="radix-_R_jer3_-content-upload" data-state="inactive" id="radix-_R_jer3_-trigger-upload" class="inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow" tabindex="-1" data-orientation="horizontal" data-radix-collection-item="">Upload File</button></div><div data-state="inactive" data-orientation="horizontal" role="tabpanel" aria-labelledby="radix-_R_jer3_-trigger-upload" hidden="" id="radix-_R_jer3_-content-upload" tabindex="0" class="mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"></div><div data-state="active" data-orientation="horizontal" role="tabpanel" aria-labelledby="radix-_R_jer3_-trigger-devtools" id="radix-_R_jer3_-content-devtools" tabindex="0" class="mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2" style="animation-duration:0s"><div class="space-y-4"><div class="flex items-center justify-between"><h3 class="text-sm font-medium">Available Databases</h3><button class="inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground h-8 rounded-md px-3 text-xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-refresh-cw h-4 w-4 mr-2" aria-hidden="true"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"></path><path d="M21 3v5h-5"></path><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"></path><path d="M8 16H3v5"></path></svg>Refresh</button></div><div class="text-center p-12 rounded-lg border-2 border-dashed border-muted-foreground/25"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-database h-12 w-12 text-muted-foreground mx-auto mb-4" aria-hidden="true"><ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M3 5V19A9 3 0 0 0 21 19V5"></path><path d="M3 12A9 3 0 0 0 21 12"></path></svg><p class="text-sm text-muted-foreground">No databases found. Make sure your app is using useSQLiteDevTool.</p></div></div></div></div></div></div></div><section aria-label="Notifications alt+T" tabindex="-1" aria-live="polite" aria-relevant="additions text" aria-atomic="false"></section><!--/$--><!--/$--></div></div><script src="/_expo/plugins/expo-sqlite/_expo/static/js/web/__expo-metro-runtime-8c088df87a31e4e6accd81c554ec290d.js" defer></script><script src="/_expo/plugins/expo-sqlite/_expo/static/js/web/_layout-
|
|
317
|
+
<link rel="stylesheet" href="/_expo/plugins/expo-sqlite/_expo/static/css/global-b1bdf830cdbd0ec4238cc5f230f99460.css"></head><body><div id="root"><div class="css-g5y9jx r-13awgt0"><!--$--><!--$--><div class="flex min-h-screen items-center justify-center bg-linear-to-br from-slate-50 to-slate-100 p-4"><div class="rounded-xl border bg-card text-card-foreground shadow w-full max-w-2xl"><div class="flex flex-col space-y-1.5 p-6 text-center"><div class="font-semibold tracking-tight text-3xl">SQLite Database Inspector</div><div class="text-muted-foreground text-base">Upload a database file or connect to your app</div></div><div class="p-6 pt-0"><div dir="ltr" data-orientation="horizontal" class="w-full"><div role="tablist" aria-orientation="horizontal" class="h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground grid w-full grid-cols-2" tabindex="-1" data-orientation="horizontal" style="outline:none"><button type="button" role="tab" aria-selected="true" aria-controls="radix-_R_jer3_-content-devtools" data-state="active" data-disabled="" disabled="" id="radix-_R_jer3_-trigger-devtools" class="inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow" tabindex="-1" data-orientation="horizontal" data-radix-collection-item="">From App </button><button type="button" role="tab" aria-selected="false" aria-controls="radix-_R_jer3_-content-upload" data-state="inactive" id="radix-_R_jer3_-trigger-upload" class="inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow" tabindex="-1" data-orientation="horizontal" data-radix-collection-item="">Upload File</button></div><div data-state="inactive" data-orientation="horizontal" role="tabpanel" aria-labelledby="radix-_R_jer3_-trigger-upload" hidden="" id="radix-_R_jer3_-content-upload" tabindex="0" class="mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"></div><div data-state="active" data-orientation="horizontal" role="tabpanel" aria-labelledby="radix-_R_jer3_-trigger-devtools" id="radix-_R_jer3_-content-devtools" tabindex="0" class="mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2" style="animation-duration:0s"><div class="space-y-4"><div class="flex items-center justify-between"><h3 class="text-sm font-medium">Available Databases</h3><button class="inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground h-8 rounded-md px-3 text-xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-refresh-cw h-4 w-4 mr-2" aria-hidden="true"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"></path><path d="M21 3v5h-5"></path><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"></path><path d="M8 16H3v5"></path></svg>Refresh</button></div><div class="text-center p-12 rounded-lg border-2 border-dashed border-muted-foreground/25"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-database h-12 w-12 text-muted-foreground mx-auto mb-4" aria-hidden="true"><ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M3 5V19A9 3 0 0 0 21 19V5"></path><path d="M3 12A9 3 0 0 0 21 12"></path></svg><p class="text-sm text-muted-foreground">No databases found. Make sure your app is using useSQLiteDevTool.</p></div></div></div></div></div></div></div><section aria-label="Notifications alt+T" tabindex="-1" aria-live="polite" aria-relevant="additions text" aria-atomic="false"></section><!--/$--><!--/$--></div></div><script src="/_expo/plugins/expo-sqlite/_expo/static/js/web/__expo-metro-runtime-8c088df87a31e4e6accd81c554ec290d.js" defer></script><script src="/_expo/plugins/expo-sqlite/_expo/static/js/web/_layout-b2894030c1dfbd95b717a85901b6c271.js" defer></script><script src="/_expo/plugins/expo-sqlite/_expo/static/js/web/index-4bf2da199f699eb47a42074d5e183c07.js" defer></script><script src="/_expo/plugins/expo-sqlite/_expo/static/js/web/__common-9b94dd2fbdbb825a662f42cf8d2077ee.js" defer></script><script src="/_expo/plugins/expo-sqlite/_expo/static/js/web/entry-04fdb7d857a65c0ed638ef2d774a4b6e.js" defer></script>
|
|
318
318
|
</body></html>
|
|
@@ -177,6 +177,71 @@ extension Benchmarks {
|
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
/// `SQLiteDatabase.runSync(source, { $name: value })`: named parameters resolve their index through
|
|
181
|
+
/// `sqlite3_bind_parameter_index` instead of the array position.
|
|
182
|
+
@Test
|
|
183
|
+
func `JS: insert with named parameters`() async throws {
|
|
184
|
+
try await sqliteBenchmarkCase { appContext in
|
|
185
|
+
let runtime = try prepareRuntime(appContext)
|
|
186
|
+
_ = try runtime.eval(
|
|
187
|
+
"""
|
|
188
|
+
var insert = new NativeStatement();
|
|
189
|
+
db.prepareSync(insert, 'INSERT INTO t (int_value, real_value, text_value, null_value) VALUES ($i, $r, $t, $n)');
|
|
190
|
+
"""
|
|
191
|
+
)
|
|
192
|
+
let run = try driver(
|
|
193
|
+
runtime,
|
|
194
|
+
"insert.runSync(db, { $i: i, $r: 1.5, $t: 'row', $n: null }, {}, false);"
|
|
195
|
+
)
|
|
196
|
+
try benchmark("JS: runSync insert with 4 named parameters", runtime: runtime) { iterations in
|
|
197
|
+
_ = try run.call(arguments: iterations)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/// `SQLiteDatabase.runSync(source, [blob])`: the blob goes through the separate blob parameters.
|
|
203
|
+
@Test
|
|
204
|
+
func `JS: insert with a blob parameter`() async throws {
|
|
205
|
+
try await sqliteBenchmarkCase { appContext in
|
|
206
|
+
let runtime = try prepareRuntime(appContext)
|
|
207
|
+
_ = try runtime.eval(
|
|
208
|
+
"""
|
|
209
|
+
var insert = new NativeStatement();
|
|
210
|
+
db.prepareSync(insert, 'INSERT INTO blobs (data) VALUES (?)');
|
|
211
|
+
var blob = new Uint8Array(1024);
|
|
212
|
+
"""
|
|
213
|
+
)
|
|
214
|
+
let run = try driver(runtime, "insert.runSync(db, {}, { 0: blob }, true);")
|
|
215
|
+
try benchmark("JS: runSync insert with a 1 KiB blob parameter", runtime: runtime) { iterations in
|
|
216
|
+
_ = try run.call(arguments: iterations)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/// `SQLiteStatement.executeSync(...)` iterated row by row: one native call per row.
|
|
222
|
+
@Test
|
|
223
|
+
func `JS: step through 100 rows`() async throws {
|
|
224
|
+
try await sqliteBenchmarkCase { appContext in
|
|
225
|
+
let runtime = try prepareRuntime(appContext)
|
|
226
|
+
_ = try runtime.eval(
|
|
227
|
+
"""
|
|
228
|
+
var cursor = new NativeStatement();
|
|
229
|
+
db.prepareSync(cursor, 'SELECT \(BenchmarkSchema.selectColumns) FROM t WHERE id <= 100');
|
|
230
|
+
"""
|
|
231
|
+
)
|
|
232
|
+
let run = try driver(
|
|
233
|
+
runtime,
|
|
234
|
+
"""
|
|
235
|
+
cursor.resetSync(db);
|
|
236
|
+
while (cursor.stepSync(db) != null) {}
|
|
237
|
+
"""
|
|
238
|
+
)
|
|
239
|
+
try benchmark("JS: stepSync through 100 rows", runtime: runtime) { iterations in
|
|
240
|
+
_ = try run.call(arguments: iterations)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
180
245
|
@Test
|
|
181
246
|
func `JS: column names of a prepared statement`() async throws {
|
|
182
247
|
try await sqliteBenchmarkCase { appContext in
|
|
@@ -8,8 +8,9 @@ struct DatabaseChangeEvent {
|
|
|
8
8
|
var databaseName: String
|
|
9
9
|
var databaseFilePath: String
|
|
10
10
|
var tableName: String
|
|
11
|
-
// `
|
|
12
|
-
//
|
|
13
|
-
|
|
11
|
+
// A `Double` rather than SQLite's `Int64` or an `Int`: JavaScript reads the row id as a number, an
|
|
12
|
+
// `Int64` would encode as a BigInt, and an `Int` throws above 2^53, which would drop the event. Above
|
|
13
|
+
// 2^53 it loses precision, like integer columns do.
|
|
14
|
+
var rowId: Double
|
|
14
15
|
var typeId: SQLAction
|
|
15
16
|
}
|
package/ios/NativeDatabase.swift
CHANGED
|
@@ -11,6 +11,10 @@ final class NativeDatabase: SharedObject, @unchecked Sendable {
|
|
|
11
11
|
let openOptions: OpenDatabaseOptions
|
|
12
12
|
var isClosed = false
|
|
13
13
|
let closeLock = NSLock()
|
|
14
|
+
// Protect statement registration and cleanup through the final close attempt.
|
|
15
|
+
// Running statements use their own locks, so interruption need not wait for them.
|
|
16
|
+
let statementLifecycleLock = NSRecursiveLock()
|
|
17
|
+
var statements: [NativeStatement] = []
|
|
14
18
|
var extraPointer: OpaquePointer?
|
|
15
19
|
private var refCount = AtomicInteger(1)
|
|
16
20
|
|
|
@@ -134,11 +138,16 @@ final class NativeDatabase: SharedObject, @unchecked Sendable {
|
|
|
134
138
|
}
|
|
135
139
|
|
|
136
140
|
private func prepare(statement: NativeStatement, source: String) throws {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
141
|
+
statementLifecycleLock.lock()
|
|
142
|
+
defer { statementLifecycleLock.unlock() }
|
|
143
|
+
try statement.lock.withLock { _ in
|
|
144
|
+
try statement.ensureNotFinalized()
|
|
145
|
+
try ensureOpen()
|
|
146
|
+
let sourceString = source.cString(using: .utf8)
|
|
147
|
+
if exsqlite3_prepare_v2(pointer, sourceString, -1, &statement.pointer, nil) != SQLITE_OK {
|
|
148
|
+
throw SQLiteErrorException(lastErrorMessage())
|
|
149
|
+
}
|
|
150
|
+
statements.append(statement)
|
|
142
151
|
}
|
|
143
152
|
}
|
|
144
153
|
|
|
@@ -57,13 +57,53 @@ final class NativeStatement: SharedObject, @unchecked Sendable {
|
|
|
57
57
|
try finalize(database: database)
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
@JS(.concurrent)
|
|
61
|
+
func runAsync(
|
|
62
|
+
database: NativeDatabase,
|
|
63
|
+
bindParams: [String: SQLiteBindValue?],
|
|
64
|
+
bindBlobParams: [String: ArrayBuffer],
|
|
65
|
+
shouldPassAsArray: Bool
|
|
66
|
+
) async throws -> SQLiteRunResult {
|
|
67
|
+
return try run(database: database, bindParams: bindParams, bindBlobParams: bindBlobParams, shouldPassAsArray: shouldPassAsArray)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@JS
|
|
71
|
+
func runSync(
|
|
72
|
+
database: NativeDatabase,
|
|
73
|
+
bindParams: [String: SQLiteBindValue?],
|
|
74
|
+
bindBlobParams: [String: ArrayBuffer],
|
|
75
|
+
shouldPassAsArray: Bool
|
|
76
|
+
) throws -> SQLiteRunResult {
|
|
77
|
+
return try run(database: database, bindParams: bindParams, bindBlobParams: bindBlobParams, shouldPassAsArray: shouldPassAsArray)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@JS(.concurrent)
|
|
81
|
+
func stepAsync(database: NativeDatabase) async throws -> [SQLiteColumnValue?]? {
|
|
82
|
+
return try step(database: database)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@JS
|
|
86
|
+
func stepSync(database: NativeDatabase) throws -> [SQLiteColumnValue?]? {
|
|
87
|
+
return try step(database: database)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@JS(.concurrent)
|
|
91
|
+
func getAllAsync(database: NativeDatabase) async throws -> [[SQLiteColumnValue?]] {
|
|
92
|
+
return try getAll(database: database)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@JS
|
|
96
|
+
func getAllSync(database: NativeDatabase) throws -> [[SQLiteColumnValue?]] {
|
|
97
|
+
return try getAll(database: database)
|
|
98
|
+
}
|
|
99
|
+
|
|
60
100
|
// MARK: - Implementation shared by the sync and async members
|
|
61
101
|
|
|
62
102
|
private func reset(database: NativeDatabase) throws {
|
|
63
|
-
try database.ensureOpen()
|
|
64
|
-
try ensureNotFinalized()
|
|
65
|
-
|
|
66
103
|
try lock.withLock { _ in
|
|
104
|
+
try ensureNotFinalized()
|
|
105
|
+
try database.ensureOpen()
|
|
106
|
+
|
|
67
107
|
if exsqlite3_reset(pointer) != SQLITE_OK {
|
|
68
108
|
throw SQLiteErrorException(database.lastErrorMessage())
|
|
69
109
|
}
|
|
@@ -71,26 +111,150 @@ final class NativeStatement: SharedObject, @unchecked Sendable {
|
|
|
71
111
|
}
|
|
72
112
|
|
|
73
113
|
private func getColumnNames() throws -> [String] {
|
|
74
|
-
try
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
114
|
+
return try lock.withLock { _ in
|
|
115
|
+
try ensureNotFinalized()
|
|
116
|
+
let columnCount = Int(exsqlite3_column_count(pointer))
|
|
117
|
+
var columnNames: [String] = Array(repeating: "", count: columnCount)
|
|
118
|
+
for i in 0..<columnCount {
|
|
119
|
+
columnNames[i] = String(cString: exsqlite3_column_name(pointer, Int32(i)))
|
|
120
|
+
}
|
|
121
|
+
return columnNames
|
|
79
122
|
}
|
|
80
|
-
return columnNames
|
|
81
123
|
}
|
|
82
124
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
125
|
+
func finalize(database: NativeDatabase) throws {
|
|
126
|
+
database.statementLifecycleLock.lock()
|
|
127
|
+
defer { database.statementLifecycleLock.unlock() }
|
|
87
128
|
try lock.withLock { _ in
|
|
88
|
-
|
|
129
|
+
try ensureNotFinalized()
|
|
130
|
+
try database.ensureOpen()
|
|
131
|
+
|
|
132
|
+
let ret = exsqlite3_finalize(pointer)
|
|
133
|
+
// SQLite destroys the statement even when returning an earlier execution error.
|
|
134
|
+
isFinalized = true
|
|
135
|
+
pointer = nil
|
|
136
|
+
database.statements.removeAll { $0 === self }
|
|
137
|
+
if ret != SQLITE_OK {
|
|
89
138
|
throw SQLiteErrorException(database.lastErrorMessage())
|
|
90
139
|
}
|
|
91
|
-
isFinalized = true
|
|
92
140
|
}
|
|
93
141
|
}
|
|
142
|
+
|
|
143
|
+
private func run(
|
|
144
|
+
database: NativeDatabase,
|
|
145
|
+
bindParams: [String: SQLiteBindValue?],
|
|
146
|
+
bindBlobParams: [String: ArrayBuffer],
|
|
147
|
+
shouldPassAsArray: Bool
|
|
148
|
+
) throws -> SQLiteRunResult {
|
|
149
|
+
return try lock.withLock { _ in
|
|
150
|
+
try ensureNotFinalized()
|
|
151
|
+
try database.ensureOpen()
|
|
152
|
+
|
|
153
|
+
exsqlite3_reset(pointer)
|
|
154
|
+
exsqlite3_clear_bindings(pointer)
|
|
155
|
+
for (key, param) in bindParams {
|
|
156
|
+
let index = try bindParamIndex(for: key, shouldPassAsArray: shouldPassAsArray)
|
|
157
|
+
guard index > 0 else {
|
|
158
|
+
continue
|
|
159
|
+
}
|
|
160
|
+
if let param {
|
|
161
|
+
param.bind(to: pointer, at: index)
|
|
162
|
+
} else {
|
|
163
|
+
exsqlite3_bind_null(pointer, index)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
for (key, param) in bindBlobParams {
|
|
167
|
+
let index = try bindParamIndex(for: key, shouldPassAsArray: shouldPassAsArray)
|
|
168
|
+
if index > 0 {
|
|
169
|
+
SQLiteBindValue.blob(param).bind(to: pointer, at: index)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let ret = exsqlite3_step(pointer)
|
|
174
|
+
if ret != SQLITE_ROW && ret != SQLITE_DONE {
|
|
175
|
+
throw SQLiteErrorException(database.lastErrorMessage())
|
|
176
|
+
}
|
|
177
|
+
return SQLiteRunResult(
|
|
178
|
+
lastInsertRowId: Double(exsqlite3_last_insert_rowid(database.pointer)),
|
|
179
|
+
changes: Int(exsqlite3_changes(database.pointer)),
|
|
180
|
+
firstRowValues: ret == SQLITE_ROW ? try columnValues() : []
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private func step(database: NativeDatabase) throws -> [SQLiteColumnValue?]? {
|
|
186
|
+
return try lock.withLock { _ in
|
|
187
|
+
try ensureNotFinalized()
|
|
188
|
+
try database.ensureOpen()
|
|
189
|
+
|
|
190
|
+
let ret = exsqlite3_step(pointer)
|
|
191
|
+
if ret == SQLITE_ROW {
|
|
192
|
+
return try columnValues()
|
|
193
|
+
}
|
|
194
|
+
if ret != SQLITE_DONE {
|
|
195
|
+
throw SQLiteErrorException(database.lastErrorMessage())
|
|
196
|
+
}
|
|
197
|
+
return nil
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private func getAll(database: NativeDatabase) throws -> [[SQLiteColumnValue?]] {
|
|
202
|
+
return try lock.withLock { _ in
|
|
203
|
+
try ensureNotFinalized()
|
|
204
|
+
try database.ensureOpen()
|
|
205
|
+
|
|
206
|
+
var rows: [[SQLiteColumnValue?]] = []
|
|
207
|
+
while true {
|
|
208
|
+
let ret = exsqlite3_step(pointer)
|
|
209
|
+
if ret == SQLITE_ROW {
|
|
210
|
+
rows.append(try columnValues())
|
|
211
|
+
continue
|
|
212
|
+
}
|
|
213
|
+
if ret == SQLITE_DONE {
|
|
214
|
+
break
|
|
215
|
+
}
|
|
216
|
+
throw SQLiteErrorException(database.lastErrorMessage())
|
|
217
|
+
}
|
|
218
|
+
return rows
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/// The values of the current row. Call it inside `lock`, after a step that returned `SQLITE_ROW`.
|
|
223
|
+
private func columnValues() throws -> [SQLiteColumnValue?] {
|
|
224
|
+
let columnCount = exsqlite3_column_count(pointer)
|
|
225
|
+
var values: [SQLiteColumnValue?] = []
|
|
226
|
+
values.reserveCapacity(Int(columnCount))
|
|
227
|
+
for index in 0..<columnCount {
|
|
228
|
+
values.append(try SQLiteColumnValue.read(from: pointer, column: index))
|
|
229
|
+
}
|
|
230
|
+
return values
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/// The 1-based SQLite index of a bind parameter: an array position for positional parameters, or the
|
|
234
|
+
/// index of the named parameter otherwise. Zero means the statement has no such parameter.
|
|
235
|
+
private func bindParamIndex(for key: String, shouldPassAsArray: Bool) throws -> Int32 {
|
|
236
|
+
if shouldPassAsArray {
|
|
237
|
+
guard let position = Int32(key) else {
|
|
238
|
+
throw InvalidBindParameterException()
|
|
239
|
+
}
|
|
240
|
+
return position + 1
|
|
241
|
+
}
|
|
242
|
+
return exsqlite3_bind_parameter_index(pointer, key)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/// The result of `NativeStatement.run`: the effect of the statement and the values of the first row
|
|
247
|
+
/// it returned, if any.
|
|
248
|
+
@Record
|
|
249
|
+
struct SQLiteRunResult {
|
|
250
|
+
// A `Double` rather than SQLite's `Int64` or an `Int`: JavaScript reads the row id as a number, an
|
|
251
|
+
// `Int64` would encode as a BigInt, and an `Int` throws above 2^53. The row id is connection-wide, so a
|
|
252
|
+
// throw would also fail every later run on the connection. Above 2^53 it loses precision, like integer
|
|
253
|
+
// columns do.
|
|
254
|
+
var lastInsertRowId: Double
|
|
255
|
+
// `sqlite3_changes` returns a 32-bit count, which always fits a JavaScript number.
|
|
256
|
+
var changes: Int
|
|
257
|
+
var firstRowValues: [SQLiteColumnValue?]
|
|
94
258
|
}
|
|
95
259
|
|
|
96
260
|
// `==` lives in an extension: an operator declared inside a type that carries a member-attribute macro
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Copyright 2015-present 650 Industries. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import ExpoModulesCore
|
|
4
|
+
|
|
5
|
+
/// A bind parameter as it comes from JavaScript: `number | string | boolean | bigint | ArrayBuffer`.
|
|
6
|
+
/// `null` arrives as `nil` of the optional around it.
|
|
7
|
+
///
|
|
8
|
+
/// A union decodes its cases in declaration order and the first match wins, so `double` goes first:
|
|
9
|
+
/// the `Int64` decode accepts any number and rounds it, which would turn `1.5` into `2`. Ordered after
|
|
10
|
+
/// `double`, `integer` only matches a `bigint`.
|
|
11
|
+
@Union
|
|
12
|
+
enum SQLiteBindValue: Sendable {
|
|
13
|
+
case double(Double)
|
|
14
|
+
case text(String)
|
|
15
|
+
case boolean(Bool)
|
|
16
|
+
case integer(Int64)
|
|
17
|
+
case blob(ArrayBuffer)
|
|
18
|
+
|
|
19
|
+
/// Binds the value to the parameter at `index` of `statement`.
|
|
20
|
+
@inline(__always)
|
|
21
|
+
func bind(to statement: OpaquePointer?, at index: Int32) {
|
|
22
|
+
switch self {
|
|
23
|
+
case .double(let value):
|
|
24
|
+
exsqlite3_bind_double(statement, index, value)
|
|
25
|
+
case .text(let value):
|
|
26
|
+
exsqlite3_bind_text(statement, index, value, -1, SQLITE_TRANSIENT)
|
|
27
|
+
case .boolean(let value):
|
|
28
|
+
exsqlite3_bind_int(statement, index, value ? 1 : 0)
|
|
29
|
+
case .integer(let value):
|
|
30
|
+
exsqlite3_bind_int64(statement, index, value)
|
|
31
|
+
case .blob(let value):
|
|
32
|
+
_ = value.withUnsafeBytes {
|
|
33
|
+
exsqlite3_bind_blob(statement, index, $0.baseAddress, Int32(value.byteLength), SQLITE_TRANSIENT)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private let SQLITE_TRANSIENT = unsafeBitCast(OpaquePointer(bitPattern: -1), to: sqlite3_destructor_type.self)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Copyright 2015-present 650 Industries. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import ExpoModulesCore
|
|
4
|
+
|
|
5
|
+
/// A column value as it goes to JavaScript: `number | string | ArrayBuffer`. `NULL` goes as `nil` of
|
|
6
|
+
/// the optional around it.
|
|
7
|
+
///
|
|
8
|
+
/// `INTEGER` columns are read as `double`, so they reach JavaScript as a number, not a `bigint`. That
|
|
9
|
+
/// loses precision above 2^53, as the type-erased conversion did before.
|
|
10
|
+
@Union
|
|
11
|
+
enum SQLiteColumnValue: Sendable, Equatable {
|
|
12
|
+
case double(Double)
|
|
13
|
+
case text(String)
|
|
14
|
+
case blob(ArrayBuffer)
|
|
15
|
+
|
|
16
|
+
/// Reads the value of the column at `index` in the current row of `statement`, or `nil` for `NULL`.
|
|
17
|
+
@inline(__always)
|
|
18
|
+
static func read(from statement: OpaquePointer?, column index: Int32) throws -> SQLiteColumnValue? {
|
|
19
|
+
switch exsqlite3_column_type(statement, index) {
|
|
20
|
+
case SQLITE_INTEGER:
|
|
21
|
+
return .double(Double(exsqlite3_column_int64(statement, index)))
|
|
22
|
+
case SQLITE_FLOAT:
|
|
23
|
+
return .double(exsqlite3_column_double(statement, index))
|
|
24
|
+
case SQLITE_TEXT:
|
|
25
|
+
guard let text = exsqlite3_column_text(statement, index) else {
|
|
26
|
+
throw InvalidConvertibleException("Null text")
|
|
27
|
+
}
|
|
28
|
+
return .text(String(cString: text))
|
|
29
|
+
case SQLITE_BLOB:
|
|
30
|
+
guard let blob = exsqlite3_column_blob(statement, index) else {
|
|
31
|
+
return .blob(ArrayBuffer(size: 0))
|
|
32
|
+
}
|
|
33
|
+
let size = exsqlite3_column_bytes(statement, index)
|
|
34
|
+
return .blob(ArrayBuffer.copy(of: blob, count: Int(size)))
|
|
35
|
+
case SQLITE_NULL:
|
|
36
|
+
return nil
|
|
37
|
+
case let type:
|
|
38
|
+
throw InvalidConvertibleException("Unsupported column type: \(type)")
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static func == (lhs: SQLiteColumnValue, rhs: SQLiteColumnValue) -> Bool {
|
|
43
|
+
switch (lhs, rhs) {
|
|
44
|
+
case let (.double(lhs), .double(rhs)):
|
|
45
|
+
return lhs == rhs
|
|
46
|
+
case let (.text(lhs), .text(rhs)):
|
|
47
|
+
return lhs == rhs
|
|
48
|
+
case let (.blob(lhs), .blob(rhs)):
|
|
49
|
+
return lhs.withUnsafeBytes { lhs in rhs.withUnsafeBytes { rhs in lhs.elementsEqual(rhs) } }
|
|
50
|
+
default:
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|