// 快速诊断:batch_sql_check 对含 xxx_col 的 INSERT 返回什么 // 用法:node 临时脚本放在项目根,直接 require const fs = require('fs') const path = require('path') const os = require('os') const TMP_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'cal_diag_')) const BACKUP_DIR = path.join(process.cwd(), 'data', 'calendar_reminder_backups') const latest = fs.readdirSync(BACKUP_DIR).filter(f => f.startsWith('calendar_reminder_2') && f.endsWith('.db')).sort().slice(-1)[0] const SRC = path.join(BACKUP_DIR, latest) const DST_DIR = path.join(process.cwd(), 'data', 'calendar_reminder_backups', '_diag') fs.mkdirSync(DST_DIR, { recursive: true }) for (const suf of ['.db', '.db-wal', '.db-shm']) { fs.copyFileSync(SRC.replace(/\.db$/,'') + suf, path.join(DST_DIR, 'calendar_reminder' + suf)) } const modSrc = fs.readFileSync(path.join(process.cwd(), 'src','server','calendar_reminder.js'), 'utf8') const NEEDLE = "const DB_PATH = path.join(process.cwd(), 'data', 'calendar_reminder.db')" if (!modSrc.includes(NEEDLE)) { throw new Error('NEEDLE NOT FOUND') } const modTmp = path.join(process.cwd(), 'dev_test_scripts','unit','_calendar_reminder_diag.js') fs.writeFileSync(modTmp, modSrc.replace(NEEDLE, 'const DB_PATH = path.join(process.cwd(), "data/calendar_reminder_backups/_diag", "calendar_reminder.db")')) const express = require('express') const { createHash } = require('crypto') const cookie = require('cookie') const app = express() app.use((req, res, next) => { req.headers.cookie = (req.headers.cookie || '') + '; calendar_reminder_gate=1' next() }) const mod = require(modTmp) mod.bindRoutes(app) const PORT = 18795 const server = app.listen(PORT, '127.0.0.1', async () => { try { const badCol = `INSERT INTO events (title,xxx_col,start) VALUES ('t',1,'2026-09-02 09:00');` const http = require('http') const r = await new Promise((resolve, reject) => { const body = JSON.stringify({ sql_text: badCol }) const req = http.request({ hostname: '127.0.0.1', port: PORT, path: '/api/calendar_reminder/batch_sql_check', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } }, res => { let buf = ''; res.on('data', d => buf += d); res.on('end', () => resolve({ status: res.statusCode, body: buf })) }) req.on('error', reject); req.write(body); req.end() }) console.log('STATUS', r.status) console.log('BODY', r.body) let j try { j = JSON.parse(r.body) } catch (e) { j = null } if (j) { console.log('col_errors length:', j.col_errors && j.col_errors.length) console.log('col_errors:', JSON.stringify(j.col_errors, null, 2)) console.log('syntax_errors:', JSON.stringify(j.syntax_errors, null, 2)) console.log('forbidden:', JSON.stringify(j.forbidden, null, 2)) console.log('ok?', j.ok) } server.close(() => { fs.rmSync(DST_DIR, { recursive: true, force: true }) fs.unlinkSync(modTmp) }) } catch (e) { console.error(e) try { server.close() } catch {} try { fs.rmSync(DST_DIR, { recursive: true, force: true }) } catch {} try { fs.unlinkSync(modTmp) } catch {} } })