@@ -60,6 +60,8 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
6060 var NULL = 0 ;
6161 // SQLite enum
6262 var SQLITE_OK = 0 ;
63+ var SQLITE_BUSY = 5 ;
64+ var SQLITE_LOCKED = 6 ;
6365 var SQLITE_ROW = 100 ;
6466 var SQLITE_DONE = 101 ;
6567 var SQLITE_INTEGER = 1 ;
@@ -75,6 +77,17 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
7577 // var - cwrap function
7678 var sqlite3_open = cwrap ( "sqlite3_open" , "number" , [ "string" , "number" ] ) ;
7779 var sqlite3_close_v2 = cwrap ( "sqlite3_close_v2" , "number" , [ "number" ] ) ;
80+ var sqlite3_backup_init = cwrap (
81+ "sqlite3_backup_init" ,
82+ "number" ,
83+ [ "number" , "string" , "number" , "string" ]
84+ ) ;
85+ var sqlite3_backup_step = cwrap (
86+ "sqlite3_backup_step" , "number" , [ "number" , "number" ]
87+ ) ;
88+ var sqlite3_backup_finish = cwrap (
89+ "sqlite3_backup_finish" , "number" , [ "number" ]
90+ ) ;
7891 var sqlite3_exec = cwrap (
7992 "sqlite3_exec" ,
8093 "number" ,
@@ -1103,23 +1116,106 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
11031116 return new StatementIterator ( sql , this ) ;
11041117 } ;
11051118
1106- /** Exports the contents of the database to a binary array. This
1107- * operation will close and re-open the database which will cause
1108- * any pragmas to be set back to their default values.
1119+ /** Exports a snapshot of the main database to a binary array without
1120+ * closing the connection. Prepared statements, registered functions,
1121+ * update hooks and connection settings (including pragmas) are preserved.
1122+ * TEMP objects and attached databases remain available on this connection,
1123+ * but are not included in the exported file.
1124+ *
1125+ * Exporting during an active write transaction throws an error. Commit or
1126+ * roll back the transaction before exporting. Read transactions are
1127+ * allowed. Exporting never commits or rolls back a transaction or changes
1128+ * savepoints, even when the export fails.
1129+ *
1130+ * The exported file uses rollback-journal format, even if this connection
1131+ * uses WAL mode.
1132+ *
1133+ * The returned array is an independent copy. It can be used to create a
1134+ * new Database, but JavaScript functions, hooks and connection settings
1135+ * are not stored in the file and must be configured on the new connection.
11091136 @return {Uint8Array } An array of bytes of the SQLite3 database file
11101137 */
11111138 Database . prototype . export = function exportDatabase ( ) {
1112- Object . values ( this . statements ) . forEach ( function each ( stmt ) {
1113- stmt . free ( ) ;
1114- } ) ;
1115- this . functions . forEach ( removeFunctionPointers ) ;
1116- this . functions . clear ( ) ;
1117- this . handleError ( sqlite3_close_v2 ( this . db ) ) ;
1118- var binaryDb = FS . readFile ( this . filename , { encoding : "binary" } ) ;
1119- this . handleError ( sqlite3_open ( this . filename , apiTemp ) ) ;
1120- this . db = getValue ( apiTemp , "i32" ) ;
1121- registerExtensionFunctions ( this . db ) ;
1122- return binaryDb ;
1139+ if ( ! this . db ) {
1140+ throw new Error ( "Database closed" ) ;
1141+ }
1142+ var filename = this . filename + "_export" ;
1143+ FS . createDataFile ( "/" , filename , [ ] , true , true ) ;
1144+ var destination = NULL ;
1145+ var backup = NULL ;
1146+ try {
1147+ var file = FS . lookupPath ( filename ) . node ;
1148+ var binaryDb = null ;
1149+ var write = file . stream_ops . write ;
1150+ file . stream_ops = Object . create ( file . stream_ops ) ;
1151+ file . stream_ops . write = function writeExport (
1152+ stream , buffer , offset , length , position
1153+ ) {
1154+ if ( binaryDb === null ) {
1155+ return write ( stream , buffer , offset , length , position ) ;
1156+ }
1157+ // Write directly into the final JS-owned result buffer.
1158+ // MEMFS would otherwise replace it on the first write.
1159+ file . contents . set ( buffer . subarray ( offset , offset + length ) ,
1160+ position ) ;
1161+ file . usedBytes = Math . max ( file . usedBytes , position + length ) ;
1162+ return length ;
1163+ } ;
1164+ var rc = sqlite3_open ( filename , apiTemp ) ;
1165+ destination = getValue ( apiTemp , "i32" ) ;
1166+ if ( rc !== SQLITE_OK ) {
1167+ throw new Error ( sqlite3_errmsg ( destination ) ) ;
1168+ }
1169+ backup = sqlite3_backup_init ( destination , "main" , this . db , "main" ) ;
1170+ if ( backup === NULL ) {
1171+ throw new Error ( sqlite3_errmsg ( destination ) ) ;
1172+ }
1173+ // Initialize the backup and reject writers before allocating.
1174+ rc = sqlite3_backup_step ( backup , 0 ) ;
1175+ if ( rc === SQLITE_OK ) {
1176+ var sizes = this . exec (
1177+ "PRAGMA main.page_count; PRAGMA main.page_size"
1178+ ) ;
1179+ binaryDb = new Uint8Array (
1180+ sizes [ 0 ] . values [ 0 ] [ 0 ] * sizes [ 1 ] . values [ 0 ] [ 0 ]
1181+ ) ;
1182+ file . contents = binaryDb ;
1183+ rc = sqlite3_backup_step ( backup , - 1 ) ;
1184+ }
1185+ // Do not retry: this synchronous call cannot let a writer progress.
1186+ if ( rc === SQLITE_BUSY || rc === SQLITE_LOCKED ) {
1187+ throw new Error ( "Unable to export: SQLite is busy or locked. "
1188+ + "Finish active write transactions before exporting." ) ;
1189+ }
1190+ if ( rc !== SQLITE_DONE ) {
1191+ throw new Error ( sqlite3_errmsg ( destination ) ) ;
1192+ }
1193+ rc = sqlite3_backup_finish ( backup ) ;
1194+ backup = NULL ;
1195+ if ( rc !== SQLITE_OK ) {
1196+ throw new Error ( sqlite3_errmsg ( destination ) ) ;
1197+ }
1198+ rc = sqlite3_close_v2 ( destination ) ;
1199+ if ( rc !== SQLITE_OK ) {
1200+ throw new Error ( sqlite3_errmsg ( destination ) ) ;
1201+ }
1202+ destination = NULL ;
1203+ // The private file is closed and unlinked below; hand its buffer
1204+ // to the caller rather than making a full-size FS.readFile copy.
1205+ binaryDb = new Uint8Array ( file . contents . buffer ,
1206+ file . contents . byteOffset , file . usedBytes ) ;
1207+ if ( binaryDb . length > 0 ) {
1208+ // All pages, including WAL pages, are in the backup.
1209+ // Make the exported copy independent of WAL support.
1210+ binaryDb [ 18 ] = 1 ;
1211+ binaryDb [ 19 ] = 1 ;
1212+ }
1213+ return binaryDb ;
1214+ } finally {
1215+ if ( backup !== NULL ) sqlite3_backup_finish ( backup ) ;
1216+ if ( destination !== NULL ) sqlite3_close_v2 ( destination ) ;
1217+ FS . unlink ( "/" + filename ) ;
1218+ }
11231219 } ;
11241220
11251221 /** Close the database, and all associated prepared statements.
0 commit comments