diff --git a/.gitignore b/.gitignore index a56201d..b7e6841 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /drupal-wasm-1.0.zip +*.sql diff --git a/playground/public/assets/export-db.php b/playground/public/assets/export-db.php new file mode 100644 index 0000000..5b15fe5 --- /dev/null +++ b/playground/public/assets/export-db.php @@ -0,0 +1,103 @@ +getAppRoot()); +$kernel->boot(); + +foreach (Cache::getBins() as $cache_backend) { + $cache_backend->deleteAll(); +} +\Drupal::service('plugin.cache_clearer')->clearCachedDefinitions(); +$kernel->invalidateContainer(); + +$sql = 'PRAGMA foreign_keys=OFF;' . PHP_EOL; +$sql .= 'BEGIN TRANSACTION;' . PHP_EOL; + +// adapted from https://github.com/ephestione/php-sqlite-dump/blob/master/sqlite_dump.php +$database = \Drupal::database(); +$tables = $database->query('SELECT [name], [sql] FROM {sqlite_master} WHERE [type] = "table" AND [name] NOT LIKE "sqlite_%" ORDER BY name'); +foreach ($tables as $table) { + $sql .= str_replace('CREATE TABLE ', 'CREATE TABLE IF NOT EXISTS ', $table->sql) . ';' . PHP_EOL; + + $columns = array_map( + static fn (object $row) => "`{$row->name}`", + $database->query("PRAGMA table_info({$table->name})")->fetchAll() + ); + $columns = implode(', ', $columns); + $sql .= PHP_EOL; + + $rows = $database->select($table->name)->fields($table->name)->execute()->fetchAll(\PDO::FETCH_ASSOC); + foreach ($rows as $row) { + $values = implode( + ', ', + array: array_map( + static function ($value) { + if ($value === NULL) { + return 'NULL'; + } + if ($value === '') { + return "''"; + } + if (is_numeric($value)) { + return $value; + } + if ($value === 'b:0;') { + return false; + } + $is_serialized = $value[1] === ':' && str_ends_with($value, '}'); + + $value = str_replace(["'"], ["''"], $value); + $value = "'$value'"; + + if ($is_serialized) { + $value = sprintf( + "replace(%s, char(1), char(0))", + str_replace(chr(0), chr(1), $value), + ); + } + return $value; + }, + array_values($row) + ) + ); + $sql .= "INSERT INTO {$table->name} VALUES($values);" . PHP_EOL; + } + + $sql .= PHP_EOL; +} + +$sql .= 'DELETE FROM sqlite_sequence;' . PHP_EOL; +$sequences = $database->query('SELECT [name], [seq] FROM sqlite_sequence'); +foreach ($sequences as $sequence) { + $sql .= "INSERT INTO sqlite_sequence VALUES('{$sequence->name}',{$sequence->seq});" . PHP_EOL; +} + +$indexes = $database->query('SELECT [sql] FROM {sqlite_master} WHERE [type] = "index" AND [name] NOT LIKE "sqlite_%" ORDER BY name'); +foreach ($indexes as $index) { + $sql .= $index->sql . ';' . PHP_EOL; +} + + +$sql .= 'COMMIT;' . PHP_EOL; + +// TODO support this collation somehow +// see https://www.drupal.org/project/drupal/issues/3036487 +$sql = str_replace('NOCASE_UTF8', 'NOCASE', $sql); + +file_put_contents('/persist/dump.sql', $sql); diff --git a/playground/public/index.html b/playground/public/index.html index 4a6de98..dcab014 100644 --- a/playground/public/index.html +++ b/playground/public/index.html @@ -75,6 +75,10 @@

Browser compatibility warningsLoading... + @@ -92,6 +96,10 @@

Browser compatibility warningsLoading... + @@ -144,10 +152,13 @@

Browser compatibility warnings { const flavorCleanup = document.querySelector(`[data-cleanup][data-flavor="${flavor}"]`) const flavorLaunch = document.querySelector(`[data-launch][data-flavor="${flavor}"]`) + const flavorExportDb = document.querySelector(`[data-export-db][data-flavor="${flavor}"]`) if (checkWww.exists) { flavorCleanup.hidden = false flavorCleanup.disabled = false + flavorExportDb.hidden = false + flavorExportDb.disabled = false flavorLaunch.innerHTML = 'Launch' } else { @@ -163,6 +174,9 @@

Browser compatibility warnings { el.addEventListener('click', cleanup) }) + document.querySelectorAll('[data-export-db]').forEach(el => { + el.addEventListener('click', exportDb) + }) }); async function cleanup(event ) { @@ -178,14 +192,14 @@

Browser compatibility warnings { + openDb.onsuccess = () => { const db = openDb.result; const transaction = db.transaction(["FILE_DATA"], "readwrite"); const objectStore = transaction.objectStore("FILE_DATA"); // IDBKeyRange.bound trick found at https://stackoverflow.com/a/76714057/1949744 const objectStoreRequest = objectStore.delete(IDBKeyRange.bound(`/persist/${flavor}`, `/persist/${flavor}/\uffff`)); - objectStoreRequest.onsuccess = async (event) => { + objectStoreRequest.onsuccess = async () => { db.close(); console.log('Reloading after purging data...'); await sendMessage('refresh', []); @@ -199,7 +213,6 @@

Browser compatibility warningsBrowser compatibility warnings console.log(event.detail)); php.addEventListener('error', event => console.log(event.detail)); @@ -252,6 +268,35 @@

Browser compatibility warnings console.log(event.detail)); + + await sendMessage('writeFile', ['/config/flavor.txt', flavor]); + + const exportDbPhpCode = await (await fetch('/assets/export-db.php')).text(); + await php.run(exportDbPhpCode) + + const dbContents = await sendMessage('readFile', ['/persist/dump.sql']); + const blob = new Blob([dbContents], {type: 'application/sql'}) + const link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + link.download = 'drupal.sql' + link.click(); + URL.revokeObjectURL(link.href); + event.target.disabled = false + setTimeout(() => sendMessage('unlink', ['/persist/dump.sql']), 0); + } diff --git a/playground/public/worker.mjs b/playground/public/worker.mjs index f278dd1..7698a5c 100644 --- a/playground/public/worker.mjs +++ b/playground/public/worker.mjs @@ -19,16 +19,16 @@ const notFound = (request) => { }; const sharedLibs = [ - `php\${PHP_VERSION}-zlib.so`, - `php\${PHP_VERSION}-zip.so`, - `php\${PHP_VERSION}-iconv.so`, - `php\${PHP_VERSION}-gd.so`, - `php\${PHP_VERSION}-dom.so`, - `php\${PHP_VERSION}-mbstring.so`, - `php\${PHP_VERSION}-sqlite.so`, - `php\${PHP_VERSION}-pdo-sqlite.so`, - `php\${PHP_VERSION}-xml.so`, - `php\${PHP_VERSION}-simplexml.so`, + `php\${PHP_VERSION}-zlib.so`, + `php\${PHP_VERSION}-zip.so`, + `php\${PHP_VERSION}-iconv.so`, + `php\${PHP_VERSION}-gd.so`, + `php\${PHP_VERSION}-dom.so`, + `php\${PHP_VERSION}-mbstring.so`, + `php\${PHP_VERSION}-sqlite.so`, + `php\${PHP_VERSION}-pdo-sqlite.so`, + `php\${PHP_VERSION}-xml.so`, + `php\${PHP_VERSION}-simplexml.so`, ]; const php = new PhpCgiWorker({