Files
MetonaSqlark/site/demo.html
T
thzxx d544501e1c
CI / test (18.x) (push) Failing after 5m11s
CI / test (20.x) (push) Failing after 5m8s
CI / test (22.x) (push) Successful in 9m58s
CI / test (24.x) (push) Successful in 9m56s
release: v0.3.2 — 质量加固 + SQL扩展 + 表达式 + 并发同步
v0.2.6 质量加固:
- 修复 AriaEngine 二级索引 SSTable 互相覆盖(命名空间隔离)
- 修复 LSM 多版本读取顺序错误 + MergeIterator 取最新来源
- 重写 LZ4 压缩器(往返一致性 + 缓冲区溢出)
- sstableCache LRU 上限 + 预加载兜底(BufferPool 配置生效)
- 修复 React/Vue 集成 import type 运行时 bug + exports 子路径
- 新增 38 个测试(LZ4往返/Crypto/集成), 删除伪测试

v0.3.0 SQL 功能扩展:
- 多语句 parseAll + 事务语句 BEGIN/COMMIT/ROLLBACK
- INSERT INTO ... SELECT + UNION/UNION ALL + EXISTS 关联子查询
- CREATE/DROP INDEX 五引擎实现 + 别名 WHERE 修复
- benchmark 页面 + 36 个新测试

v0.3.1 表达式与性能:
- CASE WHEN 表达式(SELECT 列/WHERE/聚合)
- JOIN + 关联子查询逐行绑定
- WAL 批量组提交(写放大 O(N)→O(1))
- 修复 pending frozen 可见性 + flush 缓存竞争

v0.3.2 并发:
- CASE WHEN 用于 WHERE/聚合 + JOIN 哈希连接
- 多标签页同步(multiTabSync + BroadcastChannel)
- IndexedDB schema 持久化(reopen 后表结构恢复)
- 修复 where-matcher 顶层 $not
- 修复 CJS 产物 .js 被 ESM 解析(exports 空) — .cjs 后缀 + exports 修正
- 836 测试 / 44 套件 / 81.0% 覆盖率
2026-08-08 10:41:30 +08:00

697 lines
25 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🧪 在线演示 — MetonaSqlark v0.3.2</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' rx='8' fill='%236366f1'/><text x='16' y='22' text-anchor='middle' font-size='20' fill='white'>◈</text></svg>">
<style>
:root {
--bg:#0a0a0f; --surface:#0d1117; --surface2:#161b22; --border:#30363d;
--primary:#6366f1; --accent:#06b6d4; --accent2:#ec4899; --green:#22c55e;
--text:#e2e8f0; --text2:#8b949e; --radius:12px;
--gradient:linear-gradient(135deg,#6366f1,#06b6d4,#ec4899);
}
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family:'Inter',-apple-system,system-ui,sans-serif; background:var(--bg); color:var(--text); height:100vh; overflow:hidden; }
header {
height:56px; display:flex; align-items:center; justify-content:space-between; padding:0 24px;
background:var(--surface); border-bottom:1px solid var(--border); z-index:10; position:relative;
}
.logo { font-weight:800; font-size:1.1rem; display:flex; align-items:center; gap:10px; }
.logo .icon { width:28px; height:28px; border-radius:7px; background:var(--gradient); display:flex; align-items:center; justify-content:center; font-size:0.85rem; font-weight:900; color:#fff; }
.logo span { background:var(--gradient); -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
nav { display:flex; gap:20px; }
nav a { color:var(--text2); text-decoration:none; font-size:0.88rem; transition:.2s; }
nav a:hover,.nav-active { color:var(--text); }
.status { display:flex; align-items:center; gap:8px; font-size:0.82rem; color:var(--text2); }
.status .dot { width:7px; height:7px; border-radius:50%; background:var(--green); animation:pulse 2s infinite; }
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.3} }
.main { display:flex; height:calc(100vh - 56px); }
.editor-panel { flex:1; display:flex; flex-direction:column; border-right:1px solid var(--border); min-width:400px; }
.result-panel { flex:1; display:flex; flex-direction:column; min-width:400px; }
.panel-header {
padding:12px 20px; font-size:0.78rem; font-weight:600; text-transform:uppercase;
letter-spacing:1px; color:var(--text2); border-bottom:1px solid var(--border);
display:flex; align-items:center; justify-content:space-between;
}
.panel-header .badge {
font-size:0.7rem; padding:3px 10px; border-radius:999px; background:var(--surface2);
border:1px solid var(--border); text-transform:none; letter-spacing:0;
}
.editor-area { flex:1; display:flex; flex-direction:column; }
#sql-input {
flex:1; background:var(--surface); color:var(--text); border:none; padding:20px;
font-family:'JetBrains Mono','Fira Code',monospace; font-size:0.9rem; line-height:1.8;
resize:none; outline:none; caret-color:var(--primary);
}
#sql-input::placeholder { color:var(--text2); opacity:0.5; }
.toolbar { display:flex; gap:8px; padding:12px 20px; border-top:1px solid var(--border); background:var(--surface2); flex-wrap:wrap; }
.btn {
display:inline-flex; align-items:center; gap:6px; padding:10px 20px; border-radius:8px;
font-weight:600; font-size:0.85rem; cursor:pointer; border:none; transition:.25s; font-family:inherit;
}
.btn-run { background:var(--gradient); color:#fff; box-shadow:0 4px 15px rgba(99,102,241,0.35); }
.btn-run:hover { transform:translateY(-1px); box-shadow:0 6px 25px rgba(99,102,241,0.5); }
.btn-run:active { transform:scale(0.97); }
.btn-clear { background:var(--surface2); color:var(--text2); border:1px solid var(--border); }
.btn-clear:hover { color:var(--text); border-color:var(--text2); }
.btn-preset { background:transparent; color:var(--accent); border:1px solid var(--accent); font-size:0.78rem; padding:6px 14px; }
.btn-preset:hover { background:rgba(6,182,212,0.12); }
.result-content { flex:1; overflow-y:auto; padding:20px; }
.result-table { width:100%; border-collapse:collapse; font-size:0.85rem; }
.result-table th { text-align:left; padding:10px 14px; background:var(--surface2); color:var(--accent); font-weight:600; border-bottom:2px solid var(--border); position:sticky; top:0; z-index:1; }
.result-table td { padding:8px 14px; border-bottom:1px solid var(--border); color:var(--text2); max-width:250px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.result-table tr:hover td { background:rgba(99,102,241,0.04); }
.empty-state { display:flex; flex-direction:column; align-items:center; justify-content:center; height:100%; color:var(--text2); gap:12px; }
.empty-state .icon { font-size:3rem; opacity:0.3; }
.error-box { background:rgba(239,68,68,0.1); border:1px solid rgba(239,68,68,0.3); border-radius:10px; padding:16px; color:#f87171; font-family:'JetBrains Mono',monospace; font-size:0.82rem; margin-bottom:16px; }
.success-box { background:rgba(34,197,94,0.1); border:1px solid rgba(34,197,94,0.3); border-radius:10px; padding:12px 16px; color:#4ade80; font-size:0.85rem; margin-bottom:16px; }
.sql-tag { display:inline-block; background:var(--surface2); border:1px solid var(--border); border-radius:6px; padding:4px 12px; font-family:'JetBrains Mono',monospace; font-size:0.75rem; color:var(--accent); margin-bottom:8px; }
::-webkit-scrollbar { width:6px; height:6px; }
::-webkit-scrollbar-track { background:transparent; }
::-webkit-scrollbar-thumb { background:var(--border); border-radius:3px; }
@media(max-width:900px){ .main { flex-direction:column; } .editor-panel,.result-panel { min-width:auto; } .editor-panel { height:50%; } }
</style>
</head>
<body>
<header>
<div class="logo"><div class="icon"></div><span>MetonaSqlark</span></div>
<nav>
<a href="index.html">首页</a>
<a href="docs.html">文档</a>
<a href="demo.html" class="nav-active">演示</a>
<a href="benchmark.html">基准</a>
</nav>
<div class="status"><span class="dot"></span> Memory 模式 — v0.3.2</div>
</header>
<div class="main">
<div class="editor-panel">
<div class="editor-area">
<textarea id="sql-input" placeholder="输入 SQL 语句...&#10;&#10;SELECT * FROM users;&#10;INSERT INTO users VALUES ('4', 'Diana', 'diana@test.com', 28);&#10;SELECT u.name, o.amount FROM users u INNER JOIN orders o ON u.id = o.user_id;">-- 🚀 MetonaSqlark v0.3.2 在线演示
-- 已预置 users / orders / products 表数据
-- 新特性: ALTER TABLE · TRUNCATE TABLE · WAL同步 · MVCC · SQL注入防护
-- 查看所有数据
SELECT * FROM users;
-- 条件查询 + 排序
SELECT name, age FROM users WHERE age > 25 ORDER BY age DESC;
-- JOIN 多表关联
SELECT u.name, o.product, o.amount
FROM users u INNER JOIN orders o ON u.id = o.user_id;
-- 聚合统计
SELECT COUNT(*) as total_users, AVG(age) as avg_age FROM users;</textarea>
<div class="toolbar">
<button class="btn btn-run" onclick="runQuery()">▶ 执行 (Ctrl+Enter)</button>
<button class="btn btn-clear" onclick="clearResults()">🗑 清空</button>
<span style="flex:1"></span>
<button class="btn btn-preset" onclick="loadPreset('all')">📋 全数据</button>
<button class="btn btn-preset" onclick="loadPreset('join')">🔗 JOIN</button>
<button class="btn btn-preset" onclick="loadPreset('agg')">📊 聚合</button>
<button class="btn btn-preset" onclick="loadPreset('ddl')">🏗 DDL</button>
<button class="btn btn-preset" onclick="loadPreset('crud')">✏️ CRUD</button>
<button class="btn btn-preset" onclick="loadPreset('subquery')">🔍 子查询</button>
<button class="btn btn-preset" onclick="loadPreset('tx')">🔒 事务</button>
<button class="btn btn-preset" onclick="loadPreset('scalar')">🎯 标量子查询</button>
<button class="btn btn-preset" onclick="loadPreset('cascade')">🔗 级联</button>
<button class="btn btn-preset" onclick="loadPreset('adv')">🧪 高级</button>
<button class="btn btn-preset" onclick="loadPreset('alter')">🏗 ALTER</button>
<button class="btn btn-preset" onclick="loadPreset('truncate')">🗑 TRUNCATE</button>
<button class="btn btn-preset" onclick="loadPreset('casewhen')">🎯 CASE WHEN</button>
<button class="btn btn-preset" onclick="loadPreset('union')">🔀 UNION</button>
<button class="btn btn-preset" onclick="loadPreset('insertselect')">📥 INSERT SELECT</button>
<button class="btn btn-preset" onclick="loadPreset('exists')">🔍 EXISTS</button>
<button class="btn btn-preset" onclick="loadPreset('index')">🗂 索引</button>
<button class="btn btn-preset" onclick="loadPreset('multistmt')">📜 多语句/事务</button>
<button class="btn btn-preset" onclick="loadPreset('aria')" style="color:#ec4899;border-color:#ec4899;">🌲 Aria</button>
</div>
</div>
</div>
<div class="result-panel">
<div class="panel-header">
<span>📊 查询结果</span>
<span class="badge" id="row-count">0 行</span>
</div>
<div class="result-content" id="result-area">
<div class="empty-state">
<div class="icon"></div>
<div>执行 SQL 查询以查看结果</div>
<div style="font-size:0.8rem;opacity:0.6">Ctrl + Enter 快捷执行 · 支持多语句</div>
</div>
</div>
</div>
</div>
<script src="../dist/metona-sqlark.js"></script>
<script>
// Resolve global: try both MetonaSqlark and MeSqlark
const globalNs = window.MetonaSqlark || window.MeSqlark || {};
const { MetonaSqlark, MeSqlark, create } = globalNs;
const DBClass = MetonaSqlark || MeSqlark;
let db = null;
const resultArea = document.getElementById('result-area');
const rowCount = document.getElementById('row-count');
const sqlInput = document.getElementById('sql-input');
// Init database
async function initDB() {
try {
db = new DBClass({ name: 'demo', mode: 'memory' });
await db.init();
// Create tables
await db.defineTable('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string', required: true },
email: { type: 'string' },
age: { type: 'number', default: 0 },
});
await db.defineTable('orders', {
id: { type: 'string', primaryKey: true },
user_id: { type: 'string' },
product: { type: 'string' },
amount: { type: 'number' },
quantity: { type: 'number', default: 1 },
});
await db.defineTable('products', {
id: { type: 'string', primaryKey: true },
name: { type: 'string', required: true },
price: { type: 'number', default: 0 },
category: { type: 'string' },
});
// Seed data
await db.table('users').insertMany([
{ id: '1', name: 'Alice', email: 'alice@demo.com', age: 30 },
{ id: '2', name: 'Bob', email: 'bob@demo.com', age: 25 },
{ id: '3', name: 'Charlie', email: 'charlie@demo.com', age: 35 },
{ id: '4', name: 'Diana', email: 'diana@demo.com', age: 28 },
{ id: '5', name: 'Eve', email: 'eve@demo.com', age: 22 },
]);
await db.table('orders').insertMany([
{ id: 'o1', user_id: '1', product: 'Laptop', amount: 1200, quantity: 1 },
{ id: 'o2', user_id: '1', product: 'Mouse', amount: 50, quantity: 2 },
{ id: 'o3', user_id: '2', product: 'Keyboard', amount: 150, quantity: 1 },
{ id: 'o4', user_id: '3', product: 'Monitor', amount: 400, quantity: 1 },
{ id: 'o5', user_id: '3', product: 'Cable', amount: 20, quantity: 3 },
]);
await db.table('products').insertMany([
{ id: 'p1', name: 'Laptop', price: 1200, category: 'Electronics' },
{ id: 'p2', name: 'Mouse', price: 50, category: 'Accessories' },
{ id: 'p3', name: 'Keyboard', price: 150, category: 'Accessories' },
]);
} catch (e) {
renderError('数据库初始化失败: ' + e.message);
}
}
// Run SQL
async function runQuery() {
const sql = sqlInput.value.trim();
if (!sql || !db) return;
const statements = sql.split(';').map(s => s.trim()).filter(s => {
// 过滤空语句和纯注释语句
if (s.length === 0) return false;
const nonComment = s.split('\n').filter(l => !l.trimStart().startsWith('--')).join('\n').trim();
return nonComment.length > 0;
});
try {
const allResults = [];
for (const stmt of statements) {
const result = await db.query(stmt);
// 深拷贝查询结果,防止后续语句通过引用修改已保存的结果
const resultCopy = Array.isArray(result) ? result.map(r => ({ ...r })) : result;
allResults.push({ sql: stmt, result: resultCopy });
}
if (allResults.length === 1) {
renderResult(allResults[0]);
} else {
renderAllResults(allResults);
}
} catch (e) {
renderError(e.message || String(e));
}
}
function renderResult({ sql, result }) {
if (Array.isArray(result)) {
renderTable(result);
} else {
renderSuccess(`✅ 执行成功 (影响 ${result ?? 0} 行)`);
}
}
function renderAllResults(allResults) {
let html = '';
for (const { sql, result } of allResults) {
html += `<div class="sql-tag">> ${escapeHTML(sql)}</div>`;
if (Array.isArray(result)) {
if (result.length === 0) {
html += '<div class="success-box">✅ 查询返回 0 行</div>';
} else {
html += buildTableHTML(result);
}
} else {
html += `<div class="success-box">✅ 执行成功 (影响 ${result ?? 0} 行)</div>`;
}
html += '<div style="height:12px"></div>';
}
resultArea.innerHTML = html;
updateRowCount(allResults.reduce((sum, r) => sum + (Array.isArray(r.result) ? r.result.length : 0), 0));
}
function renderTable(rows) {
if (!rows || rows.length === 0) {
resultArea.innerHTML = '<div class="success-box">✅ 查询返回 0 行</div>';
updateRowCount(0);
return;
}
resultArea.innerHTML = buildTableHTML(rows);
updateRowCount(rows.length);
}
function buildTableHTML(rows) {
const columns = Object.keys(rows[0]);
let html = '<table class="result-table"><thead><tr>';
for (const col of columns) {
html += `<th>${escapeHTML(col)}</th>`;
}
html += '</tr></thead><tbody>';
for (const row of rows) {
html += '<tr>';
for (const col of columns) {
const val = row[col];
html += `<td>${formatCell(val)}</td>`;
}
html += '</tr>';
}
html += '</tbody></table>';
return html;
}
function formatCell(val) {
if (val === null) return '<span style="color:#64748b;font-style:italic">NULL</span>';
if (val === undefined) return '<span style="color:#64748b">-</span>';
if (typeof val === 'boolean') return `<span style="color:#fbbf24">${val}</span>`;
if (typeof val === 'number') return `<span style="color:#fbbf24">${val}</span>`;
return escapeHTML(String(val));
}
function renderError(msg) {
resultArea.innerHTML = `<div class="error-box">❌ ${escapeHTML(msg)}</div>`;
updateRowCount(0);
}
function renderSuccess(msg) {
resultArea.innerHTML = `<div class="success-box">${msg}</div>`;
}
function clearResults() {
resultArea.innerHTML = `<div class="empty-state"><div class="icon">⚡</div><div>结果已清空</div></div>`;
updateRowCount(0);
}
function updateRowCount(n) { rowCount.textContent = `${n} 行`; }
function escapeHTML(str) { return String(str).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#039;'); }
// Presets
const presets = {
all: `-- 查看所有数据
SELECT * FROM users;
SELECT * FROM orders;
SELECT * FROM products;`,
join: `-- INNER JOIN:用户 + 订单
SELECT u.name, o.product, o.amount, o.quantity
FROM users u
INNER JOIN orders o ON u.id = o.user_id
ORDER BY o.amount DESC;
-- LEFT JOIN:包含没有订单的用户
SELECT u.name, o.product, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;`,
agg: `-- 聚合统计:每个用户的消费
SELECT u.name, COUNT(*) as orders, SUM(o.amount) as total_spent
FROM users u
INNER JOIN orders o ON u.id = o.user_id
GROUP BY u.name
ORDER BY total_spent DESC;
-- 全局聚合
SELECT COUNT(*) as total_users, AVG(age) as avg_age,
MIN(age) as min_age, MAX(age) as max_age
FROM users;
-- 分类统计
SELECT category, COUNT(*) as product_count, AVG(price) as avg_price
FROM products
GROUP BY category;`,
ddl: `-- 查看所有表
-- (使用 db.getTableNames() 替代)
-- 创建新表
CREATE TABLE tasks (
id STRING PRIMARY KEY,
title STRING NOT NULL,
done BOOLEAN DEFAULT false,
priority NUMBER DEFAULT 0
);
-- 插入数据
INSERT INTO tasks (id, title, done, priority) VALUES ('t1', 'Learn SQLark', false, 1);
INSERT INTO tasks VALUES ('t2', 'Write docs', true, 2);
-- 查询
SELECT * FROM tasks ORDER BY priority DESC;
-- 删除表
DROP TABLE tasks;`,
crud: `-- 增
INSERT INTO users VALUES ('9', 'Frank', 'frank@demo.com', 40);
-- 查
SELECT * FROM users WHERE name = 'Frank';
-- 改
UPDATE users SET age = 41, email = 'frank2@demo.com' WHERE id = '9';
-- 查(确认)
SELECT * FROM users WHERE id = '9';
-- 删
DELETE FROM users WHERE id = '9';
-- 确认
SELECT COUNT(*) as user_count FROM users;`,
subquery: `-- IN 子查询:有订单的用户
SELECT name, email FROM users
WHERE id IN (SELECT user_id FROM orders);
-- NOT IN 子查询:没有订单的用户
SELECT name, email FROM users
WHERE id NOT IN (SELECT user_id FROM orders);
-- 标量子查询:消费最高的用户
SELECT name, age FROM users
WHERE id IN (
SELECT user_id FROM orders
WHERE amount = (SELECT MAX(amount) FROM orders)
);`,
tx: `-- 事务演示:使用 db.transaction() API
-- 查询用户 Alice
SELECT * FROM users WHERE id = '1';
-- 为 Alice 插入新订单
INSERT INTO orders VALUES ('o99', '1', 'Headphones', 199, 1);
-- 查看 Alice 的订单汇总
SELECT u.name, COUNT(*) as order_count, SUM(o.amount) as total
FROM users u INNER JOIN orders o ON u.id = o.user_id
WHERE u.id = '1'
GROUP BY u.name;
-- 生产环境用 db.transaction() 包裹保证原子性`,
scalar: `-- 聚合无 GROUP BY 返回单行
SELECT COUNT(*) as total_users, AVG(age) as avg_age FROM users;
-- 年龄大于平均值的用户
SELECT name, age FROM users
WHERE age > (SELECT AVG(age) FROM users);
-- 消费超过平均值的用户
SELECT u.name, SUM(o.amount) as spent
FROM users u INNER JOIN orders o ON u.id = o.user_id
GROUP BY u.name
HAVING SUM(o.amount) > (SELECT AVG(amount) FROM orders);`,
cascade: `-- 外键级联:重建 orders 表带 CASCADE
DROP TABLE IF EXISTS orders;
CREATE TABLE orders (
id STRING PRIMARY KEY,
user_id STRING REFERENCES users(id) ON DELETE CASCADE,
product STRING,
amount NUMBER,
quantity NUMBER DEFAULT 1
);
-- 重新插入数据
INSERT INTO orders VALUES ('o1', '1', 'Laptop', 1200, 1);
INSERT INTO orders VALUES ('o2', '1', 'Mouse', 50, 2);
INSERT INTO orders VALUES ('o3', '2', 'Keyboard', 150, 1);
-- 查看关联数据
SELECT u.name, o.product, o.amount
FROM users u INNER JOIN orders o ON u.id = o.user_id;
-- 删除 Alice (id=1) 订单自动级联删除
DELETE FROM users WHERE id = '1';
-- 确认级联结果
SELECT u.name, o.product, o.amount
FROM users u LEFT JOIN orders o ON u.id = o.user_id;`,
adv: `-- BETWEEN 范围查询
SELECT * FROM users
WHERE age BETWEEN 20 AND 30 AND name != 'Bob'
ORDER BY age DESC;
-- DISTINCT 去重
SELECT DISTINCT age FROM users ORDER BY age;
-- 多表 JOIN + 过滤 + 排序 + 分页
SELECT u.name, o.product, o.amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE o.amount >= 50
ORDER BY o.amount DESC
LIMIT 5 OFFSET 0;
-- NOT LIKE 模糊排除
SELECT * FROM users
WHERE name NOT LIKE 'A%' AND age > 20;`,
alter: `-- 🏗 ALTER TABLE 动态修改表结构 (v0.2.5)
-- 添加新列
ALTER TABLE users ADD COLUMN phone STRING;
-- 插入数据(含新列)
INSERT INTO users (id, name, age, phone) VALUES ('6', 'Frank', 33, '123-4567');
-- 查询确认
SELECT * FROM users WHERE id = '6';
-- 删除列
ALTER TABLE users DROP COLUMN phone;
-- 查询确认(phone 列已删除)
SELECT * FROM users WHERE id = '6';`,
truncate: `-- 🗑 TRUNCATE TABLE 快速清空 (v0.2.5)
-- 创建临时表并插入数据
CREATE TABLE IF NOT EXISTS temp_logs (
id STRING PRIMARY KEY,
message STRING NOT NULL
);
INSERT INTO temp_logs VALUES ('1', 'log A');
INSERT INTO temp_logs VALUES ('2', 'log B');
INSERT INTO temp_logs VALUES ('3', 'log C');
-- 确认数据
SELECT COUNT(*) as total FROM temp_logs;
-- TRUNCATE 快速清空
TRUNCATE TABLE temp_logs;
-- 确认清空
SELECT COUNT(*) as total FROM temp_logs;
-- 清理
DROP TABLE temp_logs;`,
aria: `-- 🌲 AriaEngine 演示 (v0.2.5 → v0.3.2)
-- AriaEngine: LSM-Tree 自研存储引擎
-- 支持 LSM-Tree · WAL CRC同步 · MVCC版本链 · BloomFilter · 二级索引 · AES-GCM · 836测试
-- 基础 CRUD 完全兼容
CREATE TABLE IF NOT EXISTS tasks (
id STRING PRIMARY KEY,
title STRING NOT NULL,
done BOOLEAN DEFAULT false
);
-- 插入数据
INSERT INTO tasks VALUES ('t1', 'Implement AriaEngine', false);
INSERT INTO tasks VALUES ('t2', 'Write tests', false);
INSERT INTO tasks VALUES ('t3', 'Update docs', true);
-- 查询
SELECT * FROM tasks ORDER BY title;
-- 聚合统计
SELECT done, COUNT(*) as cnt FROM tasks GROUP BY done;
-- CASE WHEN 表达式
SELECT title,
CASE WHEN done THEN '✅ done' ELSE '⏳ pending' END AS status
FROM tasks;
-- EXISTS 关联子查询
SELECT u.name FROM users u
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);
-- AriaEngine 特性:
-- • LSM-Tree: MemTable (红黑树) → SSTable 多级索引
-- • WAL: Write-Ahead Log 保证崩溃恢复 + 批量组提交
-- • MVCC: 版本链 + 快照隔离
-- • Buffer Pool: SSTable LRU 缓存 (256页 ~ 1MB) ✅ 已生效
-- • Bloom Filter: FNV-1a + Murmur 双哈希
-- • 二级索引: 每列独立 LSM + 动态 CREATE INDEX
-- 生产环境: mode: 'aria' 激活自研引擎
-- const db = await MetonaSqlark.create({
-- name: 'my-app', mode: 'aria'
-- });`,
casewhen: `-- 🎯 CASE WHEN 条件表达式 (v0.3.1 / v0.3.2)
-- SELECT 列:多 WHEN + ELSE
SELECT name, age,
CASE WHEN age >= 30 THEN 'senior'
WHEN age >= 25 THEN 'mid'
ELSE 'junior' END AS age_group
FROM users ORDER BY age;
-- WHERE 条件中的 CASE WHEN
SELECT name FROM users
WHERE CASE WHEN age >= 25 THEN 'adult' ELSE 'young' END = 'adult';
-- 聚合中的 CASE WHEN
SELECT
SUM(CASE WHEN age >= 30 THEN 1 ELSE 0 END) AS seniors,
SUM(CASE WHEN age < 30 THEN 1 ELSE 0 END) AS juniors,
AVG(CASE WHEN age >= 25 THEN age END) AS avg_adult_age
FROM users;`,
union: `-- 🔀 UNION / UNION ALL (v0.3.0)
-- UNION 去重合并两表数据
SELECT name FROM users WHERE age >= 30
UNION
SELECT name FROM users WHERE age < 30;
-- UNION 对重复行去重(同查询两次 → 结果去重)
SELECT category FROM products
UNION
SELECT category FROM products;
-- UNION ALL 保留重复
SELECT category FROM products WHERE price >= 100
UNION ALL
SELECT category FROM products WHERE price >= 50;`,
insertselect: `-- 📥 INSERT INTO ... SELECT (v0.3.0)
-- 建备份表并复制全量数据
DROP TABLE IF EXISTS users_backup;
CREATE TABLE users_backup (
id STRING PRIMARY KEY, name STRING, email STRING, age NUMBER
);
INSERT INTO users_backup SELECT * FROM users;
-- 确认复制
SELECT COUNT(*) as backed_up FROM users_backup;
-- INSERT SELECT 带 WHERE 过滤
DROP TABLE IF EXISTS big_spenders;
CREATE TABLE big_spenders (
id STRING PRIMARY KEY, product STRING, amount NUMBER
);
INSERT INTO big_spenders (id, product, amount)
SELECT o.id, o.product, o.amount FROM orders o WHERE o.amount >= 150;
-- 确认过滤结果
SELECT * FROM big_spenders;`,
exists: `-- 🔍 EXISTS 关联子查询 (v0.3.0 / v0.3.1)
-- 有订单的用户(关联子查询)
SELECT u.name FROM users u
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);
-- 无订单的用户(NOT EXISTS
SELECT u.name FROM users u
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);
-- EXISTS + JOIN 组合
SELECT u.name, o.product, o.amount FROM users u
JOIN orders o ON u.id = o.user_id
WHERE EXISTS (SELECT 1 FROM orders o2 WHERE o2.user_id = u.id AND o2.amount > 100);`,
index: `-- 🗂 动态索引 CREATE / DROP INDEX (v0.3.0)
-- 为 orders.user_id 创建索引
CREATE INDEX idx_orders_user ON orders (user_id);
-- 索引查找(走二级索引)
SELECT u.name, o.product, o.amount
FROM orders o JOIN users u ON u.id = o.user_id
WHERE o.user_id = '1';
-- 删除索引
DROP INDEX idx_orders_user ON orders (user_id);
-- 删除后回退全表扫描(结果不变)
SELECT * FROM orders WHERE user_id = '3';`,
multistmt: `-- 📜 多语句 + 事务语句 (v0.3.0)
-- 分号分隔的多语句一次执行
DROP TABLE IF EXISTS audit;
CREATE TABLE audit (id STRING PRIMARY KEY, msg STRING);
INSERT INTO audit VALUES ('a1', 'first');
INSERT INTO audit VALUES ('a2', 'second');
SELECT * FROM audit ORDER BY id;
-- 事务语句:BEGIN → ROLLBACKa3 不会生效)
BEGIN;
INSERT INTO audit VALUES ('a3', 'will be rolled back');
ROLLBACK;
SELECT * FROM audit ORDER BY id;
-- 事务语句:BEGIN → COMMITa4 生效)
BEGIN;
INSERT INTO audit VALUES ('a4', 'will be committed');
COMMIT;
SELECT COUNT(*) as total FROM audit;`,
};
function loadPreset(name) {
if (presets[name]) {
sqlInput.value = presets[name];
runQuery();
}
}
// Keyboard shortcut
document.addEventListener('keydown', e => {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
runQuery();
}
});
// Boot
initDB().then(() => {
console.log('✅ MetonaSqlark v0.3.2 demo ready');
setTimeout(runQuery, 300);
}).catch(err => {
renderError('初始化失败: ' + err.message);
});
</script>
</body>
</html>