fix: make schema bootstrap mysql safe
This commit is contained in:
@@ -46,6 +46,84 @@ export function createDbPool() {
|
||||
});
|
||||
}
|
||||
|
||||
export function splitSqlStatements(sql) {
|
||||
const statements = [];
|
||||
let current = '';
|
||||
let quote = null;
|
||||
let lineComment = false;
|
||||
let blockComment = false;
|
||||
|
||||
for (let i = 0; i < sql.length; i += 1) {
|
||||
const char = sql[i];
|
||||
const next = sql[i + 1];
|
||||
|
||||
if (lineComment) {
|
||||
if (char === '\n') {
|
||||
lineComment = false;
|
||||
current += '\n';
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (blockComment) {
|
||||
if (char === '*' && next === '/') {
|
||||
blockComment = false;
|
||||
i += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (quote) {
|
||||
current += char;
|
||||
if (char === '\\' && (quote === '\'' || quote === '"') && next) {
|
||||
current += next;
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if (char === quote) {
|
||||
if (next === quote && quote !== '`') {
|
||||
current += next;
|
||||
i += 1;
|
||||
} else {
|
||||
quote = null;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '-' && next === '-') {
|
||||
lineComment = true;
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '/' && next === '*') {
|
||||
blockComment = true;
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '\'' || char === '"' || char === '`') {
|
||||
quote = char;
|
||||
current += char;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === ';') {
|
||||
const trimmed = current.trim();
|
||||
if (trimmed) statements.push(trimmed);
|
||||
current = '';
|
||||
continue;
|
||||
}
|
||||
|
||||
current += char;
|
||||
}
|
||||
|
||||
const trimmed = current.trim();
|
||||
if (trimmed) statements.push(trimmed);
|
||||
return statements;
|
||||
}
|
||||
|
||||
async function columnExists(pool, table, column) {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT 1 FROM information_schema.COLUMNS
|
||||
@@ -589,11 +667,8 @@ export async function migrateSchema(pool) {
|
||||
export async function initSchema(pool) {
|
||||
const schemaPath = path.join(__dirname, 'schema.sql');
|
||||
const sql = fs.readFileSync(schemaPath, 'utf8');
|
||||
for (const statement of sql.split(';')) {
|
||||
const trimmed = statement.trim();
|
||||
if (trimmed) {
|
||||
await pool.query(trimmed);
|
||||
}
|
||||
for (const statement of splitSqlStatements(sql)) {
|
||||
await pool.query(statement);
|
||||
}
|
||||
await migrateSchema(pool);
|
||||
await ensureDefaultSpaces(pool, {
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import { splitSqlStatements } from './db.mjs';
|
||||
|
||||
test('splitSqlStatements ignores semicolons in comments and strings', () => {
|
||||
const statements = splitSqlStatements(`
|
||||
-- comment with a semicolon; should not split
|
||||
CREATE TABLE one (
|
||||
label VARCHAR(64) DEFAULT 'semi;colon'
|
||||
);
|
||||
/* block; comment */
|
||||
CREATE TABLE two (
|
||||
id BIGINT PRIMARY KEY,
|
||||
name VARCHAR(64) DEFAULT "two;name"
|
||||
);
|
||||
`);
|
||||
|
||||
assert.equal(statements.length, 2);
|
||||
assert.match(statements[0], /^CREATE TABLE one/);
|
||||
assert.match(statements[1], /^CREATE TABLE two/);
|
||||
});
|
||||
+2
-2
@@ -233,7 +233,7 @@ CREATE TABLE IF NOT EXISTS h5_publish_records (
|
||||
updated_at BIGINT NOT NULL,
|
||||
KEY idx_h5_publish_route (url_slug, status, published_at),
|
||||
KEY idx_h5_publish_page (user_id, page_id, published_at),
|
||||
KEY idx_h5_publish_auto_private (expires_at, user_confirmed_at) WHERE access_mode = 'public' AND user_confirmed_at IS NULL,
|
||||
KEY idx_h5_publish_auto_private (expires_at, user_confirmed_at),
|
||||
UNIQUE KEY uq_h5_publish_token_hash (token_hash),
|
||||
CONSTRAINT fk_h5_publish_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_h5_publish_page FOREIGN KEY (page_id) REFERENCES h5_page_records(id) ON DELETE CASCADE,
|
||||
@@ -1013,7 +1013,7 @@ CREATE TABLE IF NOT EXISTS h5_blocked_words (
|
||||
|
||||
-- Shared experience store (etat C): all goosed Worker instances read/write the
|
||||
-- same learned experience so capability is not siloed per instance. Built on
|
||||
-- MySQL first with a keyword + recency retrieval; `embedding` is reserved for a
|
||||
-- MySQL first with a keyword + recency retrieval. `embedding` is reserved for a
|
||||
-- later move to PostgreSQL + pgvector without changing the calling contract.
|
||||
CREATE TABLE IF NOT EXISTS h5_experience (
|
||||
id CHAR(36) PRIMARY KEY,
|
||||
|
||||
Reference in New Issue
Block a user