Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SQLite Database Integration</title>
<style>
:root { color-scheme: light; --bg: #f6f7f7; --fg: #1e1e1e; --muted: #50575e; --brand: #3858e9; --card: #fff; --border: #dcdcde; }
* { box-sizing: border-box; }
body { margin: 0; font: 16px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: var(--bg); color: var(--fg); }
main { width: min(980px, calc(100% - 32px)); margin: 0 auto; padding: 56px 0; }
.hero { background: linear-gradient(135deg, #fff, #eef2ff); border: 1px solid var(--border); border-radius: 18px; padding: 40px; }
h1 { font-size: clamp(2rem, 5vw, 4rem); line-height: 1.05; margin: 0 0 16px; }
h2 { margin-top: 40px; }
p { color: var(--muted); }
a { color: var(--brand); }
.button { display: inline-block; margin: 14px 12px 0 0; padding: 12px 18px; border-radius: 999px; background: var(--brand); color: #fff; text-decoration: none; font-weight: 700; }
.button.secondary { background: #fff; color: var(--brand); border: 1px solid var(--brand); }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px; margin-top: 24px; }
.card { background: var(--card); border: 1px solid var(--border); border-radius: 14px; padding: 20px; }
code, pre { background: #fff; border: 1px solid var(--border); border-radius: 8px; }
code { padding: 2px 5px; }
pre { padding: 16px; overflow: auto; }
table { border-collapse: collapse; width: 100%; background: #fff; border: 1px solid var(--border); }
th, td { border-bottom: 1px solid var(--border); padding: 10px; text-align: left; }
.badge { display: inline-block; border: 1px solid var(--border); border-radius: 999px; padding: 4px 10px; background: #fff; color: var(--muted); }
</style>
</head>
<body>
<main>
<section class="hero">
<p class="badge">WordPress + SQLite + Native parser extension</p>
<h1>SQLite Database Integration</h1>
<p>Run WordPress on SQLite and try the optional native MySQL parser extension in your browser with WordPress Playground.</p>
<a class="button" href="https://playground.wordpress.net/?php=8.4&php-extension=https%3A%2F%2Fwordpress.github.io%2Fsqlite-database-integration%2Fwp_mysql_parser-wasm-extension%2Flatest%2Fmanifest.json&blueprint-url=https%3A%2F%2Fwordpress.github.io%2Fsqlite-database-integration%2Fnative-extension%2Fblueprint.json">Test in Playground</a>
<a class="button secondary" href="native-extension/">Native extension details</a>
</section>
<section class="grid">
<div class="card"><h2>Native PHP extension</h2><p><code>wp_mysql_parser</code> accelerates the MySQL lexer/parser path while keeping the pure-PHP implementation as the fallback.</p></div>
<div class="card"><h2>WASM build</h2><p>The Playground build is published as PHP 8.0–8.5 JSPI side modules and loaded with the <code>php-extension</code> query parameter.</p></div>
<div class="card"><h2>Benchmarks</h2><p>The demo page verifies that the extension is loaded and runs a small in-browser benchmark.</p></div>
</section>
</main>
</body>
</html>
36 changes: 36 additions & 0 deletions native-extension/benchmark.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"updated": "2026-05-26",
"environment": "Apple Silicon macOS, PHP 8.4.5 CLI, Rust release build of wp_mysql_parser loaded with php -d extension",
"artifact_manifest": "https://wordpress.github.io/sqlite-database-integration/wp_mysql_parser-wasm-extension/latest/manifest.json",
"results": [
{
"name": "MySQL lexer",
"implementation": "Pure PHP",
"queries": 69577,
"duration": 0.9885420799255371,
"qps": 70383.44792084213,
"speedup": null,
"notes": "Measured by tests/tools/run-native-extension-benchmark.php --json."
},
{
"name": "MySQL lexer",
"implementation": "Native extension",
"queries": 69577,
"duration": 0.20811200141906738,
"qps": 334324.7843736575,
"speedup": 4.7500484027105525,
"notes": "Measured by tests/tools/run-native-extension-benchmark.php --json with wp_mysql_parser loaded."
},
{
"name": "MySQL parser",
"implementation": "Pure PHP",
"queries": 69577,
"duration": 9.678236961364746,
"qps": 7189.015962075475,
"speedup": null,
"failures": 9,
"exceptions": 0,
"notes": "Measured by tests/tools/run-parser-benchmark.php --json."
}
]
}
36 changes: 36 additions & 0 deletions native-extension/blueprint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"$schema": "https://playground.wordpress.net/blueprint-schema.json",
"landingPage": "/native-extension-demo.php",
"meta": {
"title": "SQLite Database Integration: Native Parser Extension",
"description": "Loads the wp_mysql_parser WASM extension and runs a small in-browser benchmark.",
"author": "WordPress"
},
"preferredVersions": {
"php": "8.4",
"wp": "latest"
},
"features": {
"networking": true
},
"login": true,
"steps": [
{
"step": "installPlugin",
"pluginData": {
"resource": "url",
"url": "https://github.com/WordPress/sqlite-database-integration/releases/download/v3.0.0-rc.3/plugin-sqlite-database-integration.zip"
},
"options": {
"activate": false,
"targetFolderName": "sqlite-database-integration"
},
"ifAlreadyInstalled": "overwrite"
},
{
"step": "writeFile",
"path": "/wordpress/native-extension-demo.php",
"data": "<?php\n$manifest_url = 'https://wordpress.github.io/sqlite-database-integration/wp_mysql_parser-wasm-extension/latest/manifest.json';\n$benchmark_url = 'https://wordpress.github.io/sqlite-database-integration/native-extension/benchmark.json';\n\nfunction esc($value) {\n return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');\n}\n\nfunction try_require($path) {\n if (file_exists($path)) {\n require_once $path;\n return true;\n }\n return false;\n}\n\n$plugin_base_candidates = array(\n '/wordpress/wp-content/plugins/sqlite-database-integration/wp-includes/database',\n '/wordpress/wp-content/plugins/plugin-sqlite-database-integration/wp-includes/database',\n '/wordpress/wp-content/plugins/sqlite-database-integration/wp-includes',\n '/wordpress/wp-content/plugins/plugin-sqlite-database-integration/wp-includes',\n);\n\n$loaded_php_lexer = false;\nforeach ($plugin_base_candidates as $base) {\n if (file_exists($base . '/mysql/class-wp-mysql-lexer.php')) {\n try_require($base . '/parser/class-wp-parser-token.php');\n try_require($base . '/mysql/class-wp-mysql-token.php');\n try_require($base . '/mysql/class-wp-mysql-lexer.php');\n $loaded_php_lexer = class_exists('WP_MySQL_Lexer', false);\n break;\n }\n}\n\n$queries = array(\n 'SELECT ID, post_title FROM wp_posts WHERE post_status = \"publish\" ORDER BY post_date DESC LIMIT 10',\n 'INSERT INTO wp_options (option_name, option_value, autoload) VALUES (\"demo\", \"1\", \"yes\")',\n 'UPDATE wp_posts SET post_title = CONCAT(post_title, \"!\") WHERE ID IN (1, 2, 3)',\n 'SELECT p.ID, pm.meta_value FROM wp_posts p LEFT JOIN wp_postmeta pm ON pm.post_id = p.ID WHERE pm.meta_key = \"_thumbnail_id\"',\n 'CREATE TABLE wp_demo (id bigint unsigned NOT NULL AUTO_INCREMENT, title varchar(255), PRIMARY KEY (id))',\n);\n$workload = array();\nfor ($i = 0; $i < 200; $i++) {\n foreach ($queries as $query) {\n $workload[] = $query;\n }\n}\n\nfunction bench_php_lexer($workload) {\n if (!class_exists('WP_MySQL_Lexer', false)) {\n return array('available' => false, 'reason' => 'WP_MySQL_Lexer was not found in the installed plugin.');\n }\n $start = microtime(true);\n foreach ($workload as $query) {\n $lexer = new WP_MySQL_Lexer($query);\n $tokens = $lexer->remaining_tokens();\n if (count($tokens) === 0) {\n throw new Exception('Pure PHP lexer returned no tokens.');\n }\n }\n $duration = microtime(true) - $start;\n return array('available' => true, 'duration' => $duration, 'qps' => count($workload) / $duration);\n}\n\nfunction bench_native_lexer($workload) {\n if (!class_exists('WP_MySQL_Native_Lexer', false)) {\n return array('available' => false, 'reason' => 'WP_MySQL_Native_Lexer is not available. Check that the php-extension URL loaded before PHP started.');\n }\n $start = microtime(true);\n foreach ($workload as $query) {\n $lexer = new WP_MySQL_Native_Lexer($query);\n $tokens = $lexer->native_token_stream();\n if ($tokens->count() === 0) {\n throw new Exception('Native lexer returned no tokens.');\n }\n }\n $duration = microtime(true) - $start;\n return array('available' => true, 'duration' => $duration, 'qps' => count($workload) / $duration);\n}\n\n$error = null;\ntry {\n $php = bench_php_lexer($workload);\n $native = bench_native_lexer($workload);\n} catch (Throwable $e) {\n $error = $e->getMessage();\n $php = array('available' => false, 'reason' => $error);\n $native = array('available' => false, 'reason' => $error);\n}\n$speedup = (!empty($php['available']) && !empty($native['available'])) ? $native['qps'] / $php['qps'] : null;\n?><!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>wp_mysql_parser Playground demo</title>\n<style>\nbody{font:16px/1.55 -apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;margin:0;background:#f6f7f7;color:#1e1e1e}main{width:min(960px,calc(100% - 32px));margin:0 auto;padding:40px 0}.card{background:#fff;border:1px solid #dcdcde;border-radius:14px;padding:20px;margin:16px 0}.ok{color:#008a20}.bad{color:#cc1818}table{border-collapse:collapse;width:100%}th,td{border-bottom:1px solid #dcdcde;padding:10px;text-align:left}code{background:#f6f7f7;padding:2px 5px;border-radius:4px}.button{display:inline-block;padding:10px 14px;border-radius:999px;background:#3858e9;color:#fff;text-decoration:none;font-weight:700}\n</style>\n</head>\n<body><main>\n<h1>Native MySQL Parser Extension</h1>\n<p>This Playground was opened with the published <code>wp_mysql_parser</code> WASM extension manifest.</p>\n<div class=\"card\">\n<h2>Extension status</h2>\n<ul>\n<li>Manifest: <a href=\"<?php echo esc($manifest_url); ?>\"><?php echo esc($manifest_url); ?></a></li>\n<li><code>extension_loaded('wp_mysql_parser')</code>: <strong class=\"<?php echo extension_loaded('wp_mysql_parser') ? 'ok' : 'bad'; ?>\"><?php echo extension_loaded('wp_mysql_parser') ? 'yes' : 'no'; ?></strong></li>\n<li><code>WP_MySQL_Native_Lexer</code>: <strong class=\"<?php echo class_exists('WP_MySQL_Native_Lexer', false) ? 'ok' : 'bad'; ?>\"><?php echo class_exists('WP_MySQL_Native_Lexer', false) ? 'available' : 'missing'; ?></strong></li>\n<li><code>WP_MySQL_Lexer</code> fallback: <strong class=\"<?php echo $loaded_php_lexer ? 'ok' : 'bad'; ?>\"><?php echo $loaded_php_lexer ? 'available' : 'missing'; ?></strong></li>\n</ul>\n</div>\n<div class=\"card\">\n<h2>In-browser lexer benchmark</h2>\n<p>Workload: <?php echo count($workload); ?> sample WordPress/MySQL queries.</p>\n<table><thead><tr><th>Implementation</th><th>Status</th><th>Duration</th><th>QPS</th></tr></thead><tbody>\n<tr><td>Pure PHP lexer</td><td><?php echo !empty($php['available']) ? 'available' : esc($php['reason']); ?></td><td><?php echo !empty($php['duration']) ? esc(number_format($php['duration'], 5)) . 's' : '\u2014'; ?></td><td><?php echo !empty($php['qps']) ? esc(number_format($php['qps'])) : '\u2014'; ?></td></tr>\n<tr><td>Native WASM lexer</td><td><?php echo !empty($native['available']) ? 'available' : esc($native['reason']); ?></td><td><?php echo !empty($native['duration']) ? esc(number_format($native['duration'], 5)) . 's' : '\u2014'; ?></td><td><?php echo !empty($native['qps']) ? esc(number_format($native['qps'])) : '\u2014'; ?></td></tr>\n</tbody></table>\n<?php if ($speedup): ?><p><strong>Speedup: <?php echo esc(number_format($speedup, 2)); ?>x</strong></p><?php endif; ?>\n</div>\n<div class=\"card\">\n<h2>Published benchmark data</h2>\n<div id=\"published-benchmark\">Loading <?php echo esc($benchmark_url); ?>\u2026</div>\n</div>\n<p><a class=\"button\" href=\"/wp-admin/\">Open wp-admin</a></p>\n<script>\nfetch('<?php echo esc($benchmark_url); ?>').then(r => r.json()).then(data => {\n const rows = data.results.map(row => `<tr><td>${row.name}</td><td>${row.implementation}</td><td>${row.queries}</td><td>${row.qps ? Math.round(row.qps).toLocaleString() : 'n/a'}</td><td>${row.speedup ? row.speedup.toFixed(2) + 'x' : '\u2014'}</td></tr>`).join('');\n document.getElementById('published-benchmark').innerHTML = `<table><thead><tr><th>Benchmark</th><th>Implementation</th><th>Queries</th><th>QPS</th><th>Speedup</th></tr></thead><tbody>${rows}</tbody></table><p>Environment: ${data.environment}. Updated: ${data.updated}.</p>`;\n}).catch(() => { document.getElementById('published-benchmark').textContent = 'Published benchmark data is not available yet.'; });\n</script>\n</main></body></html>\n"
}
]
}
60 changes: 60 additions & 0 deletions native-extension/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Native MySQL Parser Extension</title>
<style>
:root { color-scheme: light; --bg: #f6f7f7; --fg: #1e1e1e; --muted: #50575e; --brand: #3858e9; --card: #fff; --border: #dcdcde; }
* { box-sizing: border-box; }
body { margin: 0; font: 16px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: var(--bg); color: var(--fg); }
main { width: min(980px, calc(100% - 32px)); margin: 0 auto; padding: 56px 0; }
.hero { background: linear-gradient(135deg, #fff, #eef2ff); border: 1px solid var(--border); border-radius: 18px; padding: 40px; }
h1 { font-size: clamp(2rem, 5vw, 4rem); line-height: 1.05; margin: 0 0 16px; }
h2 { margin-top: 40px; }
p { color: var(--muted); }
a { color: var(--brand); }
.button { display: inline-block; margin: 14px 12px 0 0; padding: 12px 18px; border-radius: 999px; background: var(--brand); color: #fff; text-decoration: none; font-weight: 700; }
.button.secondary { background: #fff; color: var(--brand); border: 1px solid var(--brand); }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px; margin-top: 24px; }
.card { background: var(--card); border: 1px solid var(--border); border-radius: 14px; padding: 20px; }
code, pre { background: #fff; border: 1px solid var(--border); border-radius: 8px; }
code { padding: 2px 5px; }
pre { padding: 16px; overflow: auto; }
table { border-collapse: collapse; width: 100%; background: #fff; border: 1px solid var(--border); }
th, td { border-bottom: 1px solid var(--border); padding: 10px; text-align: left; }
.badge { display: inline-block; border: 1px solid var(--border); border-radius: 999px; padding: 4px 10px; background: #fff; color: var(--muted); }
</style>
</head>
<body>
<main>
<section class="hero">
<p class="badge">wp_mysql_parser</p>
<h1>Native MySQL Parser Extension</h1>
<p>Load the published WASM build in WordPress Playground, verify the extension, and run a lightweight benchmark without installing anything locally.</p>
<a class="button" href="https://playground.wordpress.net/?php=8.4&php-extension=https%3A%2F%2Fwordpress.github.io%2Fsqlite-database-integration%2Fwp_mysql_parser-wasm-extension%2Flatest%2Fmanifest.json&blueprint-url=https%3A%2F%2Fwordpress.github.io%2Fsqlite-database-integration%2Fnative-extension%2Fblueprint.json">Test in Playground</a>
<a class="button secondary" href="https://wordpress.github.io/sqlite-database-integration/wp_mysql_parser-wasm-extension/latest/manifest.json">View manifest</a>
</section>
<h2>Published artifacts</h2>
<div class="grid">
<div class="card"><h3>Manifest</h3><p><a href="https://wordpress.github.io/sqlite-database-integration/wp_mysql_parser-wasm-extension/latest/manifest.json">https://wordpress.github.io/sqlite-database-integration/wp_mysql_parser-wasm-extension/latest/manifest.json</a></p></div>
<div class="card"><h3>Checksums</h3><p><a href="https://wordpress.github.io/sqlite-database-integration/wp_mysql_parser-wasm-extension/latest/SHA256SUMS">https://wordpress.github.io/sqlite-database-integration/wp_mysql_parser-wasm-extension/latest/SHA256SUMS</a></p></div>
<div class="card"><h3>Supported PHP versions</h3><p>8.0, 8.1, 8.2, 8.3, 8.4, and 8.5.</p></div>
</div>
<h2>Latest published benchmark</h2>
<div id="benchmark">Loading benchmark.json…</div>
<h2>Reproduce locally</h2>
<pre><code>php tests/tools/run-lexer-benchmark.php
php tests/tools/run-parser-benchmark.php
php -d extension=/path/to/libwp_mysql_parser.so tests/tools/run-native-extension-benchmark.php</code></pre>
<script>
fetch('benchmark.json').then(r => r.json()).then(data => {
const rows = data.results.map(row => `<tr><td>${row.name}</td><td>${row.implementation}</td><td>${row.queries}</td><td>${row.qps ? Math.round(row.qps).toLocaleString() : 'n/a'}</td><td>${row.speedup ? row.speedup.toFixed(2) + 'x' : '—'}</td></tr>`).join('');
document.getElementById('benchmark').innerHTML = `<table><thead><tr><th>Benchmark</th><th>Implementation</th><th>Queries</th><th>QPS</th><th>Speedup</th></tr></thead><tbody>${rows}</tbody></table><p>Environment: ${data.environment}. Updated: ${data.updated}.</p>`;
}).catch(() => {
document.getElementById('benchmark').textContent = 'Benchmark data is not available yet.';
});
</script>
</main>
</body>
</html>