expo-sqlite 58.0.10 → 58.0.12

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 (88) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/android/build.gradle +6 -2
  3. package/android/src/main/cpp/NativeDatabaseBinding.cpp +11 -8
  4. package/android/src/main/cpp/NativeStatementBinding.cpp +43 -2
  5. package/android/src/main/cpp/NativeStatementBinding.h +3 -0
  6. package/android/src/main/cpp/SQLiteError.h +34 -0
  7. package/android/src/main/java/expo/modules/sqlite/DatabaseConnection.kt +68 -0
  8. package/android/src/main/java/expo/modules/sqlite/NativeDatabase.kt +135 -22
  9. package/android/src/main/java/expo/modules/sqlite/NativeSession.kt +126 -6
  10. package/android/src/main/java/expo/modules/sqlite/NativeStatement.kt +199 -14
  11. package/android/src/main/java/expo/modules/sqlite/NativeStatementBinding.kt +1 -0
  12. package/android/src/main/java/expo/modules/sqlite/SQLRecords.kt +19 -13
  13. package/android/src/main/java/expo/modules/sqlite/SQLiteModule.kt +176 -577
  14. package/android/src/main/java/expo/modules/sqlite/SQLiteOptions.kt +3 -10
  15. package/android/src/main/java/expo/modules/sqlite/SQLiteValues.kt +47 -0
  16. package/build/NativeDatabase.d.ts +2 -2
  17. package/build/SQLiteDatabase.js +9 -3
  18. package/build/SQLiteDatabase.js.map +1 -1
  19. package/build/WebConfiguration.d.ts +31 -0
  20. package/build/WebConfiguration.js +21 -0
  21. package/build/WebConfiguration.js.map +1 -0
  22. package/build/index.d.ts +1 -0
  23. package/build/index.js +1 -0
  24. package/build/index.js.map +1 -1
  25. package/dev-plugin-dist/+not-found.html +2 -4
  26. package/dev-plugin-dist/_expo/static/js/web/{__common-9b94dd2fbdbb825a662f42cf8d2077ee.js → __common-3e95e247f8ee6d975454b07047f39148.js} +52 -48
  27. package/dev-plugin-dist/_expo/static/js/web/{_layout-b2894030c1dfbd95b717a85901b6c271.js → _layout-0bc906c56b363d38baf86696db19957e.js} +23 -23
  28. package/dev-plugin-dist/_expo/static/js/web/{entry-04fdb7d857a65c0ed638ef2d774a4b6e.js → entry-293efbd088ab66af1b3334c3eeafa61f.js} +697 -696
  29. package/dev-plugin-dist/_expo/static/js/web/{index-4bf2da199f699eb47a42074d5e183c07.js → index-454a4f95007576080f4f47d70441d41a.js} +1740 -1740
  30. package/dev-plugin-dist/_expo/static/js/web/{worker-d51aa6be89e271e571111274ab8e7f5c.js → worker-172ab4a50823f055f5bd777d90d1a36a.js} +17 -17
  31. package/dev-plugin-dist/_sitemap.html +2 -4
  32. package/dev-plugin-dist/index.html +2 -4
  33. package/expo-module.config.json +0 -4
  34. package/ios/DatabaseFileUtils.swift +101 -5
  35. package/ios/NativeDatabase.swift +5 -8
  36. package/ios/NativeStatement.swift +20 -13
  37. package/ios/SQLiteModule.swift +8 -2
  38. package/ios/SQLiteResult.swift +31 -0
  39. package/ios/Tests/DatabaseFileUtilsTests.swift +119 -0
  40. package/ios/Tests/test_ErrorReporting.swift +25 -0
  41. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.12/expo.modules.sqlite-58.0.12-sources.jar +0 -0
  42. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.12/expo.modules.sqlite-58.0.12-sources.jar.md5 +1 -0
  43. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.12/expo.modules.sqlite-58.0.12-sources.jar.sha1 +1 -0
  44. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.12/expo.modules.sqlite-58.0.12-sources.jar.sha256 +1 -0
  45. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.12/expo.modules.sqlite-58.0.12-sources.jar.sha512 +1 -0
  46. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.12/expo.modules.sqlite-58.0.12.aar +0 -0
  47. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.12/expo.modules.sqlite-58.0.12.aar.md5 +1 -0
  48. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.12/expo.modules.sqlite-58.0.12.aar.sha1 +1 -0
  49. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.12/expo.modules.sqlite-58.0.12.aar.sha256 +1 -0
  50. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.12/expo.modules.sqlite-58.0.12.aar.sha512 +1 -0
  51. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/{58.0.10/expo.modules.sqlite-58.0.10.module → 58.0.12/expo.modules.sqlite-58.0.12.module} +22 -22
  52. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.12/expo.modules.sqlite-58.0.12.module.md5 +1 -0
  53. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.12/expo.modules.sqlite-58.0.12.module.sha1 +1 -0
  54. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.12/expo.modules.sqlite-58.0.12.module.sha256 +1 -0
  55. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.12/expo.modules.sqlite-58.0.12.module.sha512 +1 -0
  56. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/{58.0.10/expo.modules.sqlite-58.0.10.pom → 58.0.12/expo.modules.sqlite-58.0.12.pom} +1 -1
  57. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.12/expo.modules.sqlite-58.0.12.pom.md5 +1 -0
  58. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.12/expo.modules.sqlite-58.0.12.pom.sha1 +1 -0
  59. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.12/expo.modules.sqlite-58.0.12.pom.sha256 +1 -0
  60. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.12/expo.modules.sqlite-58.0.12.pom.sha512 +1 -0
  61. package/package.json +4 -4
  62. package/src/NativeDatabase.ts +2 -2
  63. package/src/SQLiteDatabase.ts +11 -3
  64. package/src/WebConfiguration.ts +36 -0
  65. package/src/index.ts +1 -0
  66. package/web/SQLiteModule.ts +16 -0
  67. package/web/WorkerChannel.ts +17 -9
  68. package/web/wa-sqlite/sqlite-api.js +4 -1
  69. package/web/web.types.ts +6 -0
  70. package/web/worker.ts +50 -38
  71. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10-sources.jar +0 -0
  72. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10-sources.jar.md5 +0 -1
  73. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10-sources.jar.sha1 +0 -1
  74. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10-sources.jar.sha256 +0 -1
  75. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10-sources.jar.sha512 +0 -1
  76. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.aar +0 -0
  77. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.aar.md5 +0 -1
  78. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.aar.sha1 +0 -1
  79. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.aar.sha256 +0 -1
  80. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.aar.sha512 +0 -1
  81. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.module.md5 +0 -1
  82. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.module.sha1 +0 -1
  83. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.module.sha256 +0 -1
  84. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.module.sha512 +0 -1
  85. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.pom.md5 +0 -1
  86. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.pom.sha1 +0 -1
  87. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.pom.sha256 +0 -1
  88. package/local-maven-repo/host/exp/exponent/expo.modules.sqlite/58.0.10/expo.modules.sqlite-58.0.10.pom.sha512 +0 -1
@@ -1,4 +1,4 @@
1
- <!DOCTYPE html><html lang="en"><head><title data-rh="true"></title><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><style id="expo-reset">#root,body,html{height:100%}body{overflow:hidden}#root{display:flex}</style><style id="react-native-stylesheet">[stylesheet-group="0"]{}
1
+ <!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><link rel="preload" href="/_expo/plugins/expo-sqlite/_expo/static/css/global-b1bdf830cdbd0ec4238cc5f230f99460.css" as="style"/><link rel="preload" href="/_expo/plugins/expo-sqlite/_expo/static/js/web/__expo-metro-runtime-8c088df87a31e4e6accd81c554ec290d.js" as="script"/><link rel="preload" href="/_expo/plugins/expo-sqlite/_expo/static/js/web/_layout-0bc906c56b363d38baf86696db19957e.js" as="script"/><link rel="preload" href="/_expo/plugins/expo-sqlite/_expo/static/js/web/__common-3e95e247f8ee6d975454b07047f39148.js" as="script"/><link rel="preload" href="/_expo/plugins/expo-sqlite/_expo/static/js/web/entry-293efbd088ab66af1b3334c3eeafa61f.js" as="script"/><style id="expo-reset">#root,body,html{height:100%}body{overflow:hidden}#root{display:flex}</style><style id="react-native-stylesheet">[stylesheet-group="0"]{}
2
2
  body{margin:0;}
3
3
  button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}
4
4
  html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0);}
@@ -313,6 +313,4 @@ input::-webkit-search-cancel-button,input::-webkit-search-decoration,input::-web
313
313
  @keyframes r-1pzkwqh{0%{transform:translateY(100%);}100%{transform:translateY(0%);}}
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
- @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-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
- </body></html>
316
+ @keyframes r-t2lo5v{0%{opacity:1;}100%{opacity:0;}}</style><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 defer="" src="/_expo/plugins/expo-sqlite/_expo/static/js/web/__expo-metro-runtime-8c088df87a31e4e6accd81c554ec290d.js"></script><script defer="" src="/_expo/plugins/expo-sqlite/_expo/static/js/web/_layout-0bc906c56b363d38baf86696db19957e.js"></script><script defer="" src="/_expo/plugins/expo-sqlite/_expo/static/js/web/__common-3e95e247f8ee6d975454b07047f39148.js"></script><script defer="" src="/_expo/plugins/expo-sqlite/_expo/static/js/web/entry-293efbd088ab66af1b3334c3eeafa61f.js"></script><script id="_R_">globalThis.__EXPO_ROUTER_HYDRATE__=true;</script></body></html>
@@ -1,4 +1,4 @@
1
- <!DOCTYPE html><html lang="en"><head><title data-rh="true"></title><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><style id="expo-reset">#root,body,html{height:100%}body{overflow:hidden}#root{display:flex}</style><style id="react-native-stylesheet">[stylesheet-group="0"]{}
1
+ <!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><link rel="preload" href="/_expo/plugins/expo-sqlite/_expo/static/css/global-b1bdf830cdbd0ec4238cc5f230f99460.css" as="style"/><link rel="preload" href="/_expo/plugins/expo-sqlite/_expo/static/js/web/__expo-metro-runtime-8c088df87a31e4e6accd81c554ec290d.js" as="script"/><link rel="preload" href="/_expo/plugins/expo-sqlite/_expo/static/js/web/_layout-0bc906c56b363d38baf86696db19957e.js" as="script"/><link rel="preload" href="/_expo/plugins/expo-sqlite/_expo/static/js/web/index-454a4f95007576080f4f47d70441d41a.js" as="script"/><link rel="preload" href="/_expo/plugins/expo-sqlite/_expo/static/js/web/__common-3e95e247f8ee6d975454b07047f39148.js" as="script"/><link rel="preload" href="/_expo/plugins/expo-sqlite/_expo/static/js/web/entry-293efbd088ab66af1b3334c3eeafa61f.js" as="script"/><style id="expo-reset">#root,body,html{height:100%}body{overflow:hidden}#root{display:flex}</style><style id="react-native-stylesheet">[stylesheet-group="0"]{}
2
2
  body{margin:0;}
3
3
  button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}
4
4
  html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0);}
@@ -313,6 +313,4 @@ input::-webkit-search-cancel-button,input::-webkit-search-decoration,input::-web
313
313
  @keyframes r-1pzkwqh{0%{transform:translateY(100%);}100%{transform:translateY(0%);}}
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
- @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 [&amp;_svg]:pointer-events-none [&amp;_svg]:size-4 [&amp;_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
- </body></html>
316
+ @keyframes r-t2lo5v{0%{opacity:1;}100%{opacity:0;}}</style><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_jepm_-content-devtools" data-state="active" data-disabled="" disabled="" id="radix-_R_jepm_-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_jepm_-content-upload" data-state="inactive" id="radix-_R_jepm_-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_jepm_-trigger-upload" hidden="" id="radix-_R_jepm_-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_jepm_-trigger-devtools" id="radix-_R_jepm_-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 [&amp;_svg]:pointer-events-none [&amp;_svg]:size-4 [&amp;_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 defer="" src="/_expo/plugins/expo-sqlite/_expo/static/js/web/__expo-metro-runtime-8c088df87a31e4e6accd81c554ec290d.js"></script><script defer="" src="/_expo/plugins/expo-sqlite/_expo/static/js/web/_layout-0bc906c56b363d38baf86696db19957e.js"></script><script defer="" src="/_expo/plugins/expo-sqlite/_expo/static/js/web/index-454a4f95007576080f4f47d70441d41a.js"></script><script defer="" src="/_expo/plugins/expo-sqlite/_expo/static/js/web/__common-3e95e247f8ee6d975454b07047f39148.js"></script><script defer="" src="/_expo/plugins/expo-sqlite/_expo/static/js/web/entry-293efbd088ab66af1b3334c3eeafa61f.js"></script><script id="_R_">globalThis.__EXPO_ROUTER_HYDRATE__=true;</script></body></html>
@@ -1,10 +1,6 @@
1
1
  {
2
2
  "platforms": ["apple", "android", "devtools"],
3
- "apple": {
4
- "modules": ["SQLiteModule"]
5
- },
6
3
  "android": {
7
- "modules": ["expo.modules.sqlite.SQLiteModule"],
8
4
  "shouldUsePublicationScriptPath": "android/shouldUsePublication.groovy",
9
5
  "publication": {
10
6
  "groupId": "host.exp.exponent",
@@ -3,6 +3,92 @@
3
3
  import Foundation
4
4
 
5
5
  internal enum DatabaseFileUtils {
6
+ /// Files SQLite keeps next to the main database and that must travel with it.
7
+ static let sidecarSuffixes = ["-journal", "-wal", "-shm"]
8
+
9
+ private static let migrationLock = NSLock()
10
+
11
+ /**
12
+ Resolves the database path JavaScript sends into a `URL`. A `file:` URI is percent-encoded before
13
+ `URL(string:)` so spaces, non-ASCII characters, `#` and `?` survive on every supported OS, and
14
+ escapes that were already present stay as they are. A plain path goes through
15
+ `URL(fileURLWithPath:)`, which never parses the string as a URL.
16
+ */
17
+ static func fileURL(fromDatabasePath path: String) -> URL? {
18
+ guard path.hasPrefix("file:") else {
19
+ return URL(fileURLWithPath: path)
20
+ }
21
+ var allowed = CharacterSet.urlPathAllowed
22
+ allowed.insert(charactersIn: ":/")
23
+ guard let encoded = path.addingPercentEncoding(withAllowedCharacters: allowed) else {
24
+ return nil
25
+ }
26
+ // `%` is not path-allowed, so an input escape such as `%20` became `%2520`. Restore only escapes
27
+ // followed by two hex digits; a bare `%` in a file name keeps its new `%25`.
28
+ let restored = encoded.replacingOccurrences(
29
+ of: "%25([0-9A-Fa-f]{2})",
30
+ with: "%$1",
31
+ options: .regularExpression
32
+ )
33
+ return URL(string: restored)
34
+ }
35
+
36
+ /**
37
+ Returns the path earlier releases passed to SQLite, where `URL(string:)` percent-encoded the input
38
+ on iOS 17 and later. Returns `nil` when it equals `resolvedPath` or is not just its encoded form,
39
+ so a name truncated at `#` or `?` is left alone.
40
+ */
41
+ static func legacyEncodedPath(forDatabasePath path: String, resolvedPath: String) -> String? {
42
+ guard let oldPath = URL(string: path)?.toFilePath() else {
43
+ return nil
44
+ }
45
+ let legacyPath = URL(fileURLWithPath: oldPath).standardizedFileURL.path
46
+ guard legacyPath != resolvedPath, legacyPath.removingPercentEncoding == resolvedPath else {
47
+ return nil
48
+ }
49
+ return legacyPath
50
+ }
51
+
52
+ /**
53
+ Moves a database left under its legacy percent-encoded name, with its sidecar files, to `path`.
54
+ Sidecars move first so a crash leaves the database with its journal and the next open retries.
55
+ */
56
+ static func migrateLegacyDatabaseFiles(fromPath legacyPath: String, toPath path: String) throws {
57
+ migrationLock.lock()
58
+ defer { migrationLock.unlock() }
59
+
60
+ let fileManager = FileManager.default
61
+ let databaseExists = fileManager.fileExists(atPath: path)
62
+ let legacyDatabaseExists = fileManager.fileExists(atPath: legacyPath)
63
+ // With both databases present the legacy sidecars belong to the other one.
64
+ guard databaseExists != legacyDatabaseExists else {
65
+ return
66
+ }
67
+
68
+ for suffix in ["-journal", "-wal"] {
69
+ let legacySidecar = legacyPath + suffix
70
+ guard fileManager.fileExists(atPath: legacySidecar) else {
71
+ continue
72
+ }
73
+ let sidecar = path + suffix
74
+ if fileManager.fileExists(atPath: sidecar) {
75
+ // The moved database already wrote its own; the stale legacy one is kept, not deleted.
76
+ if databaseExists {
77
+ continue
78
+ }
79
+ try fileManager.removeItem(atPath: sidecar)
80
+ }
81
+ try fileManager.moveItem(atPath: legacySidecar, toPath: sidecar)
82
+ }
83
+
84
+ try? fileManager.removeItem(atPath: legacyPath + "-shm")
85
+ if databaseExists {
86
+ return
87
+ }
88
+ try? fileManager.removeItem(atPath: path + "-shm")
89
+ try fileManager.moveItem(atPath: legacyPath, toPath: path)
90
+ }
91
+
6
92
  /**
7
93
  Deletes the database file at the given path together with its `-journal`, `-wal` and `-shm`
8
94
  sidecar files, mirroring the behavior of Android's `SQLiteDatabase.deleteDatabase()`.
@@ -19,11 +105,21 @@ internal enum DatabaseFileUtils {
19
105
  throw DeleteDatabaseFileException(path)
20
106
  }
21
107
 
22
- for suffix in ["-journal", "-wal", "-shm"] {
23
- let sidecarPath = path + suffix
24
- if fileManager.fileExists(atPath: sidecarPath) {
25
- try? fileManager.removeItem(atPath: sidecarPath)
26
- }
108
+ removeSidecarFiles(atPath: path)
109
+ }
110
+
111
+ /// Removes the database file and its sidecar files when present. Missing files are not an error.
112
+ static func removeDatabaseFiles(atPath path: String) {
113
+ if FileManager.default.fileExists(atPath: path) {
114
+ try? FileManager.default.removeItem(atPath: path)
115
+ }
116
+ removeSidecarFiles(atPath: path)
117
+ }
118
+
119
+ private static func removeSidecarFiles(atPath path: String) {
120
+ let fileManager = FileManager.default
121
+ for suffix in sidecarSuffixes where fileManager.fileExists(atPath: path + suffix) {
122
+ try? fileManager.removeItem(atPath: path + suffix)
27
123
  }
28
124
  }
29
125
  }
@@ -144,8 +144,11 @@ final class NativeDatabase: SharedObject, @unchecked Sendable {
144
144
  try statement.ensureNotFinalized()
145
145
  try ensureOpen()
146
146
  let sourceString = source.cString(using: .utf8)
147
- if exsqlite3_prepare_v2(pointer, sourceString, -1, &statement.pointer, nil) != SQLITE_OK {
148
- throw SQLiteErrorException(lastErrorMessage())
147
+ let result = sqliteResult(for: pointer) {
148
+ exsqlite3_prepare_v2(pointer, sourceString, -1, &statement.pointer, nil)
149
+ }
150
+ if let message = result.message {
151
+ throw SQLiteErrorException(message)
149
152
  }
150
153
  statements.append(statement)
151
154
  }
@@ -172,12 +175,6 @@ final class NativeDatabase: SharedObject, @unchecked Sendable {
172
175
  }
173
176
  }
174
177
 
175
- internal func sqliteErrorMessage(for db: OpaquePointer?) -> String {
176
- let code = exsqlite3_errcode(db)
177
- let message = String(cString: exsqlite3_errmsg(db), encoding: .utf8) ?? ""
178
- return "Error code \(code): \(message)"
179
- }
180
-
181
178
  // `==` lives in an extension: an operator declared inside a type that carries a member-attribute macro
182
179
  // is seen twice by the compiler and fails the `Equatable` conformance check.
183
180
  // swiftlint:disable:next no_grouping_extension
@@ -104,8 +104,9 @@ final class NativeStatement: SharedObject, @unchecked Sendable {
104
104
  try ensureNotFinalized()
105
105
  try database.ensureOpen()
106
106
 
107
- if exsqlite3_reset(pointer) != SQLITE_OK {
108
- throw SQLiteErrorException(database.lastErrorMessage())
107
+ let result = sqliteResult(for: database.pointer) { exsqlite3_reset(pointer) }
108
+ if let message = result.message {
109
+ throw SQLiteErrorException(message)
109
110
  }
110
111
  }
111
112
  }
@@ -129,13 +130,13 @@ final class NativeStatement: SharedObject, @unchecked Sendable {
129
130
  try ensureNotFinalized()
130
131
  try database.ensureOpen()
131
132
 
132
- let ret = exsqlite3_finalize(pointer)
133
+ let result = sqliteResult(for: database.pointer) { exsqlite3_finalize(pointer) }
133
134
  // SQLite destroys the statement even when returning an earlier execution error.
134
135
  isFinalized = true
135
136
  pointer = nil
136
137
  database.statements.removeAll { $0 === self }
137
- if ret != SQLITE_OK {
138
- throw SQLiteErrorException(database.lastErrorMessage())
138
+ if let message = result.message {
139
+ throw SQLiteErrorException(message)
139
140
  }
140
141
  }
141
142
  }
@@ -170,9 +171,10 @@ final class NativeStatement: SharedObject, @unchecked Sendable {
170
171
  }
171
172
  }
172
173
 
173
- let ret = exsqlite3_step(pointer)
174
- if ret != SQLITE_ROW && ret != SQLITE_DONE {
175
- throw SQLiteErrorException(database.lastErrorMessage())
174
+ let result = sqliteResult(for: database.pointer) { exsqlite3_step(pointer) }
175
+ let ret = result.code
176
+ if let message = result.message {
177
+ throw SQLiteErrorException(message)
176
178
  }
177
179
  return SQLiteRunResult(
178
180
  lastInsertRowId: Double(exsqlite3_last_insert_rowid(database.pointer)),
@@ -187,12 +189,13 @@ final class NativeStatement: SharedObject, @unchecked Sendable {
187
189
  try ensureNotFinalized()
188
190
  try database.ensureOpen()
189
191
 
190
- let ret = exsqlite3_step(pointer)
192
+ let result = sqliteResult(for: database.pointer) { exsqlite3_step(pointer) }
193
+ let ret = result.code
191
194
  if ret == SQLITE_ROW {
192
195
  return try columnValues()
193
196
  }
194
- if ret != SQLITE_DONE {
195
- throw SQLiteErrorException(database.lastErrorMessage())
197
+ if let message = result.message {
198
+ throw SQLiteErrorException(message)
196
199
  }
197
200
  return nil
198
201
  }
@@ -205,7 +208,8 @@ final class NativeStatement: SharedObject, @unchecked Sendable {
205
208
 
206
209
  var rows: [[SQLiteColumnValue?]] = []
207
210
  while true {
208
- let ret = exsqlite3_step(pointer)
211
+ let result = sqliteResult(for: database.pointer) { exsqlite3_step(pointer) }
212
+ let ret = result.code
209
213
  if ret == SQLITE_ROW {
210
214
  rows.append(try columnValues())
211
215
  continue
@@ -213,7 +217,10 @@ final class NativeStatement: SharedObject, @unchecked Sendable {
213
217
  if ret == SQLITE_DONE {
214
218
  break
215
219
  }
216
- throw SQLiteErrorException(database.lastErrorMessage())
220
+ if let message = result.message {
221
+ throw SQLiteErrorException(message)
222
+ }
223
+ throw SQLiteErrorException("Error code \(result.code)")
217
224
  }
218
225
  return rows
219
226
  }
@@ -187,11 +187,17 @@ public final class SQLiteModule: Module, @unchecked Sendable {
187
187
  throw Exceptions.FileSystemModuleNotFound()
188
188
  }
189
189
 
190
- guard let pathUrl = URL(string: path) else {
190
+ guard let pathUrl = DatabaseFileUtils.fileURL(fromDatabasePath: path) else {
191
191
  throw DatabaseInvalidPathException(path)
192
192
  }
193
+ let filePath = pathUrl.toFilePath()
193
194
  fileSystem.ensureDirExists(withPath: pathUrl.deletingLastPathComponent().toFilePath())
194
195
 
196
+ // Installs made on iOS 17+ before this resolution opened a percent-encoded file name.
197
+ if let legacyPath = DatabaseFileUtils.legacyEncodedPath(forDatabasePath: path, resolvedPath: filePath) {
198
+ try DatabaseFileUtils.migrateLegacyDatabaseFiles(fromPath: legacyPath, toPath: filePath)
199
+ }
200
+
195
201
  return pathUrl
196
202
  }
197
203
 
@@ -285,7 +291,7 @@ public final class SQLiteModule: Module, @unchecked Sendable {
285
291
  fileManager.fileExists(atPath: assetPath) else {
286
292
  throw DatabaseNotFoundException(assetDatabasePath)
287
293
  }
288
- try? fileManager.removeItem(atPath: path.absoluteString)
294
+ DatabaseFileUtils.removeDatabaseFiles(atPath: path.toFilePath())
289
295
  try fileManager.copyItem(atPath: assetPath, toPath: path.toFilePath())
290
296
  }
291
297
 
@@ -0,0 +1,31 @@
1
+ // Copyright 2026-present 650 Industries. All rights reserved.
2
+
3
+ // Capture a statement's error while SQLite's recursive connection mutex still protects it.
4
+ // A different statement can otherwise replace both errcode and errmsg before we report the failure.
5
+ internal func sqliteErrorMessage(for db: OpaquePointer?, code: Int32) -> String {
6
+ let message = String(cString: exsqlite3_errmsg(db), encoding: .utf8) ?? ""
7
+ return "Error code \(code): \(message)"
8
+ }
9
+
10
+ internal func withSQLiteDatabaseLock<T>(for db: OpaquePointer?, _ operation: () throws -> T) rethrows -> T {
11
+ let mutex = exsqlite3_db_mutex(db)
12
+ exsqlite3_mutex_enter(mutex)
13
+ defer { exsqlite3_mutex_leave(mutex) }
14
+ return try operation()
15
+ }
16
+
17
+ internal func sqliteErrorMessage(for db: OpaquePointer?) -> String {
18
+ return withSQLiteDatabaseLock(for: db) {
19
+ return sqliteErrorMessage(for: db, code: exsqlite3_errcode(db))
20
+ }
21
+ }
22
+
23
+ internal func sqliteResult(for db: OpaquePointer?, _ operation: () -> Int32) -> (code: Int32, message: String?) {
24
+ return withSQLiteDatabaseLock(for: db) {
25
+ let code = operation()
26
+ guard code != SQLITE_OK && code != SQLITE_ROW && code != SQLITE_DONE else {
27
+ return (code, nil)
28
+ }
29
+ return (code, sqliteErrorMessage(for: db, code: code))
30
+ }
31
+ }
@@ -68,4 +68,123 @@ final class DatabaseFileUtilsTests {
68
68
  try DatabaseFileUtils.deleteDatabaseFiles(atPath: dbPath)
69
69
  }
70
70
  }
71
+
72
+ @Test
73
+ func `removes database files and sidecars and ignores missing ones`() {
74
+ let dbPath = createFile("test.db")
75
+ let walPath = createFile("test.db-wal")
76
+ let shmPath = createFile("test.db-shm")
77
+ let missingPath = tempDir.appendingPathComponent("missing.db").path
78
+
79
+ DatabaseFileUtils.removeDatabaseFiles(atPath: dbPath)
80
+ DatabaseFileUtils.removeDatabaseFiles(atPath: missingPath)
81
+
82
+ #expect(!FileManager.default.fileExists(atPath: dbPath))
83
+ #expect(!FileManager.default.fileExists(atPath: walPath))
84
+ #expect(!FileManager.default.fileExists(atPath: shmPath))
85
+ }
86
+
87
+ @Test
88
+ func `resolves a plain path with spaces and non-ASCII characters as is`() {
89
+ let path = "/tmp/My Dir/a#b?c データ.db"
90
+
91
+ #expect(DatabaseFileUtils.fileURL(fromDatabasePath: path)?.toFilePath() == path)
92
+ }
93
+
94
+ @Test
95
+ func `resolves a file URI with spaces, non-ASCII characters, hash and question mark`() {
96
+ let url = DatabaseFileUtils.fileURL(fromDatabasePath: "file:///tmp/My Dir/a#b?c データ.db")
97
+
98
+ #expect(url?.isFileURL == true)
99
+ #expect(url?.toFilePath() == "/tmp/My Dir/a#b?c データ.db")
100
+ }
101
+
102
+ @Test
103
+ func `keeps escapes of an already encoded file URI`() {
104
+ let url = DatabaseFileUtils.fileURL(fromDatabasePath: "file:///tmp/My%20Dir/%E3%83%87.db")
105
+
106
+ #expect(url?.toFilePath() == "/tmp/My Dir/デ.db")
107
+ }
108
+
109
+ @Test
110
+ func `reports the legacy percent-encoded path only when it decodes to the resolved path`() {
111
+ guard #available(iOS 17, tvOS 17, macOS 14, *) else {
112
+ return
113
+ }
114
+ let plainPath = "/tmp/My Dir/a.db"
115
+ let legacyPath = DatabaseFileUtils.legacyEncodedPath(forDatabasePath: plainPath, resolvedPath: plainPath)
116
+
117
+ #expect(legacyPath == "/tmp/My%20Dir/a.db")
118
+ #expect(DatabaseFileUtils.legacyEncodedPath(forDatabasePath: "/tmp/dir/a.db", resolvedPath: "/tmp/dir/a.db") == nil)
119
+ #expect(DatabaseFileUtils.legacyEncodedPath(forDatabasePath: "/tmp/dir/a#b.db", resolvedPath: "/tmp/dir/a#b.db") == nil)
120
+ #expect(DatabaseFileUtils.legacyEncodedPath(forDatabasePath: "file:///tmp/My Dir/a.db", resolvedPath: plainPath) == nil)
121
+ #expect(DatabaseFileUtils.legacyEncodedPath(forDatabasePath: "file:///tmp/My%20Dir/my data.db", resolvedPath: "/tmp/My Dir/my data.db") == "/tmp/My%20Dir/my data.db")
122
+ #expect(DatabaseFileUtils.legacyEncodedPath(forDatabasePath: "file:///tmp/dir/a#b.db", resolvedPath: "/tmp/dir/a#b.db") == nil)
123
+ #expect(DatabaseFileUtils.legacyEncodedPath(forDatabasePath: "/tmp/a/../My Dir/n.db", resolvedPath: "/tmp/My Dir/n.db") == "/tmp/My%20Dir/n.db")
124
+ }
125
+
126
+ @Test
127
+ func `moves legacy database files and sidecars to the resolved path`() throws {
128
+ let legacyPath = createFile("legacy.db")
129
+ let legacyWalPath = createFile("legacy.db-wal")
130
+ let path = tempDir.appendingPathComponent("resolved.db").path
131
+
132
+ try DatabaseFileUtils.migrateLegacyDatabaseFiles(fromPath: legacyPath, toPath: path)
133
+
134
+ #expect(FileManager.default.fileExists(atPath: path))
135
+ #expect(FileManager.default.fileExists(atPath: path + "-wal"))
136
+ #expect(!FileManager.default.fileExists(atPath: legacyPath))
137
+ #expect(!FileManager.default.fileExists(atPath: legacyWalPath))
138
+ }
139
+
140
+ @Test
141
+ func `does not migrate when the resolved database already exists`() throws {
142
+ let legacyPath = createFile("legacy.db")
143
+ let legacyWalPath = createFile("legacy.db-wal")
144
+ let path = createFile("resolved.db")
145
+
146
+ try DatabaseFileUtils.migrateLegacyDatabaseFiles(fromPath: legacyPath, toPath: path)
147
+
148
+ #expect(FileManager.default.fileExists(atPath: legacyPath))
149
+ #expect(FileManager.default.fileExists(atPath: legacyWalPath))
150
+ #expect(!FileManager.default.fileExists(atPath: path + "-wal"))
151
+ #expect(String(data: FileManager.default.contents(atPath: path) ?? Data(), encoding: .utf8) == "data")
152
+ }
153
+
154
+ @Test
155
+ func `replaces an orphan resolved wal when the database has not moved yet`() throws {
156
+ let legacyPath = createFile("legacy.db")
157
+ let legacyWalPath = tempDir.appendingPathComponent("legacy.db-wal").path
158
+ FileManager.default.createFile(atPath: legacyWalPath, contents: Data("current".utf8))
159
+ let path = tempDir.appendingPathComponent("resolved.db").path
160
+ FileManager.default.createFile(atPath: path + "-wal", contents: Data("orphan".utf8))
161
+ let shmPath = createFile("resolved.db-shm")
162
+
163
+ try DatabaseFileUtils.migrateLegacyDatabaseFiles(fromPath: legacyPath, toPath: path)
164
+
165
+ #expect(FileManager.default.fileExists(atPath: path))
166
+ #expect(String(data: FileManager.default.contents(atPath: path + "-wal") ?? Data(), encoding: .utf8) == "current")
167
+ #expect(!FileManager.default.fileExists(atPath: legacyPath))
168
+ #expect(!FileManager.default.fileExists(atPath: legacyWalPath))
169
+ #expect(!FileManager.default.fileExists(atPath: shmPath))
170
+ }
171
+
172
+ @Test
173
+ func `moves only the legacy sidecars the moved database is missing`() throws {
174
+ let legacyPath = tempDir.appendingPathComponent("legacy.db").path
175
+ let legacyWalPath = createFile("legacy.db-wal")
176
+ let legacyShmPath = createFile("legacy.db-shm")
177
+ let legacyJournalPath = tempDir.appendingPathComponent("legacy.db-journal").path
178
+ FileManager.default.createFile(atPath: legacyJournalPath, contents: Data("stale".utf8))
179
+ let path = createFile("resolved.db")
180
+ FileManager.default.createFile(atPath: path + "-journal", contents: Data("current".utf8))
181
+
182
+ try DatabaseFileUtils.migrateLegacyDatabaseFiles(fromPath: legacyPath, toPath: path)
183
+
184
+ #expect(FileManager.default.fileExists(atPath: path + "-wal"))
185
+ #expect(!FileManager.default.fileExists(atPath: legacyWalPath))
186
+ #expect(!FileManager.default.fileExists(atPath: legacyShmPath))
187
+ #expect(String(data: FileManager.default.contents(atPath: path + "-journal") ?? Data(), encoding: .utf8) == "current")
188
+ #expect(FileManager.default.fileExists(atPath: legacyJournalPath))
189
+ }
71
190
  }
@@ -0,0 +1,25 @@
1
+ // Copyright 2026-present 650 Industries. All rights reserved.
2
+
3
+ import Testing
4
+
5
+ @testable import ExpoSQLite
6
+
7
+ @Suite("SQLite error reporting")
8
+ struct SQLiteErrorReportingTests {
9
+ @Test
10
+ func `captures the operation code and detail before another statement changes the connection error`() throws {
11
+ var db: OpaquePointer?
12
+ #expect(exsqlite3_open(":memory:", &db) == SQLITE_OK)
13
+ defer { exsqlite3_close(db) }
14
+ #expect(exsqlite3_exec(db, "CREATE TABLE item(id INTEGER PRIMARY KEY); INSERT INTO item VALUES (1)", nil, nil, nil) == SQLITE_OK)
15
+ var statement: OpaquePointer?
16
+ #expect(exsqlite3_prepare_v2(db, "INSERT INTO item VALUES (1)", -1, &statement, nil) == SQLITE_OK)
17
+ let execution = sqliteResult(for: db) { exsqlite3_step(statement) }
18
+ let cleanup = sqliteResult(for: db) { exsqlite3_finalize(statement) }
19
+ #expect(exsqlite3_exec(db, "SELECT 1", nil, nil, nil) == SQLITE_OK)
20
+ #expect(execution.code == SQLITE_CONSTRAINT)
21
+ #expect(cleanup.code == SQLITE_CONSTRAINT)
22
+ #expect(execution.message == "Error code 19: UNIQUE constraint failed: item.id")
23
+ #expect(cleanup.message == execution.message)
24
+ }
25
+ }
@@ -0,0 +1 @@
1
+ e4feeb357b13c3da0d6690deff9aab2186a7874185369a06c55fa6da992fdc9c
@@ -0,0 +1 @@
1
+ 93ce18d06cc3125618ca50503fd5b7f6d7407b05e931ddf6381fa7e70d8b41c6719c65a433fafa28626a12335b6e9f9a5e4cd05036f715e7a5b01c94047fddf7
@@ -0,0 +1 @@
1
+ 5c4630886c040f5a6a78270d5ff691096ee1b45da666eea3d2ae3a7c7dd87559
@@ -0,0 +1 @@
1
+ 37b24f46590d18dce06d02014b873a8c7d4c87cf64d754969b798320ed3cc5bc8d61ff709be1d4d609224c4d5cbe928a94b020c195bffc6b2b7136e2680dabdc
@@ -3,7 +3,7 @@
3
3
  "component": {
4
4
  "group": "host.exp.exponent",
5
5
  "module": "expo.modules.sqlite",
6
- "version": "58.0.10",
6
+ "version": "58.0.12",
7
7
  "attributes": {
8
8
  "org.gradle.status": "release"
9
9
  }
@@ -24,13 +24,13 @@
24
24
  },
25
25
  "files": [
26
26
  {
27
- "name": "expo.modules.sqlite-58.0.10.aar",
28
- "url": "expo.modules.sqlite-58.0.10.aar",
29
- "size": 3958905,
30
- "sha512": "8da2bcca001991403ad15af7ae5fe40b6f02cf613801c3c109fc64b0a25980c07019c47fca22cbfef180ce826225442d307fc3b4416cc29127fda0c3880c666c",
31
- "sha256": "c4c90adee96c2308551babad7ac1fca8e3782ab14771b68933f1151a03436333",
32
- "sha1": "daaf8670dcbff2b9ab9410878e0a87e85eabad6f",
33
- "md5": "fad06d1b56a5b3ce76fff8218367c30e"
27
+ "name": "expo.modules.sqlite-58.0.12.aar",
28
+ "url": "expo.modules.sqlite-58.0.12.aar",
29
+ "size": 3845692,
30
+ "sha512": "37b24f46590d18dce06d02014b873a8c7d4c87cf64d754969b798320ed3cc5bc8d61ff709be1d4d609224c4d5cbe928a94b020c195bffc6b2b7136e2680dabdc",
31
+ "sha256": "5c4630886c040f5a6a78270d5ff691096ee1b45da666eea3d2ae3a7c7dd87559",
32
+ "sha1": "8094fc24b27657dd2b8e0f5304b7c806c8e5be15",
33
+ "md5": "6138be671e902727a2e7ef15521fb59c"
34
34
  }
35
35
  ]
36
36
  },
@@ -67,13 +67,13 @@
67
67
  ],
68
68
  "files": [
69
69
  {
70
- "name": "expo.modules.sqlite-58.0.10.aar",
71
- "url": "expo.modules.sqlite-58.0.10.aar",
72
- "size": 3958905,
73
- "sha512": "8da2bcca001991403ad15af7ae5fe40b6f02cf613801c3c109fc64b0a25980c07019c47fca22cbfef180ce826225442d307fc3b4416cc29127fda0c3880c666c",
74
- "sha256": "c4c90adee96c2308551babad7ac1fca8e3782ab14771b68933f1151a03436333",
75
- "sha1": "daaf8670dcbff2b9ab9410878e0a87e85eabad6f",
76
- "md5": "fad06d1b56a5b3ce76fff8218367c30e"
70
+ "name": "expo.modules.sqlite-58.0.12.aar",
71
+ "url": "expo.modules.sqlite-58.0.12.aar",
72
+ "size": 3845692,
73
+ "sha512": "37b24f46590d18dce06d02014b873a8c7d4c87cf64d754969b798320ed3cc5bc8d61ff709be1d4d609224c4d5cbe928a94b020c195bffc6b2b7136e2680dabdc",
74
+ "sha256": "5c4630886c040f5a6a78270d5ff691096ee1b45da666eea3d2ae3a7c7dd87559",
75
+ "sha1": "8094fc24b27657dd2b8e0f5304b7c806c8e5be15",
76
+ "md5": "6138be671e902727a2e7ef15521fb59c"
77
77
  }
78
78
  ]
79
79
  },
@@ -87,13 +87,13 @@
87
87
  },
88
88
  "files": [
89
89
  {
90
- "name": "expo.modules.sqlite-58.0.10-sources.jar",
91
- "url": "expo.modules.sqlite-58.0.10-sources.jar",
92
- "size": 12434,
93
- "sha512": "47d6b003042ef32b80e19b64fd015ffcfa159bb8f08971459211cd6ab971304da58dee6236c2be5319f606f9062d023530dcaa5c2e530bb17e42ab1465d5b95f",
94
- "sha256": "67dbdc88a1d7fc60c31092ad150a98a30d93d8ca82bbf869609890965425524d",
95
- "sha1": "afa88d36ec945ad0442cc3582e05eb84b07cb9f1",
96
- "md5": "f9c01cec01c62d6a619fdc69f237e033"
90
+ "name": "expo.modules.sqlite-58.0.12-sources.jar",
91
+ "url": "expo.modules.sqlite-58.0.12-sources.jar",
92
+ "size": 14875,
93
+ "sha512": "93ce18d06cc3125618ca50503fd5b7f6d7407b05e931ddf6381fa7e70d8b41c6719c65a433fafa28626a12335b6e9f9a5e4cd05036f715e7a5b01c94047fddf7",
94
+ "sha256": "e4feeb357b13c3da0d6690deff9aab2186a7874185369a06c55fa6da992fdc9c",
95
+ "sha1": "257a7ae7ca2f46a064622513d9ce08b515877773",
96
+ "md5": "5f6d9bf8e746dacc0378eead6be4ea33"
97
97
  }
98
98
  ]
99
99
  }
@@ -0,0 +1 @@
1
+ 62d1020b0f72c6339978f5d31305b61b77d28c6880f0af5501bbdd667fe6fa73