Files
ChatLab/electron/main/worker/query/sql.ts
T
2026-02-01 01:11:34 +08:00

137 lines
2.9 KiB
TypeScript

/**
* SQL 实验室查询模块
* 提供用户自定义 SQL 查询功能
*/
import { openDatabase } from '../core'
// 查询超时时间(毫秒)
const QUERY_TIMEOUT_MS = 10000
/**
* SQL 执行结果
*/
export interface SQLResult {
columns: string[]
rows: any[][]
rowCount: number
duration: number
limited: boolean // 是否被截断
}
/**
* 表结构信息
*/
export interface TableSchema {
name: string
columns: {
name: string
type: string
notnull: boolean
pk: boolean
}[]
}
/**
* 获取数据库 Schema
*/
export function getSchema(sessionId: string): TableSchema[] {
const db = openDatabase(sessionId)
if (!db) {
throw new Error('数据库不存在')
}
// 获取所有表名
const tables = db
.prepare(
`SELECT name FROM sqlite_master
WHERE type='table' AND name NOT LIKE 'sqlite_%'
ORDER BY name`
)
.all() as { name: string }[]
const schema: TableSchema[] = []
for (const table of tables) {
// 获取表的列信息
const columns = db.prepare(`PRAGMA table_info('${table.name}')`).all() as {
cid: number
name: string
type: string
notnull: number
dflt_value: any
pk: number
}[]
schema.push({
name: table.name,
columns: columns.map((col) => ({
name: col.name,
type: col.type,
notnull: col.notnull === 1,
pk: col.pk === 1,
})),
})
}
return schema
}
/**
* 检查 SQL 是否包含 LIMIT 子句
*/
function hasLimit(sql: string): boolean {
return /\bLIMIT\s+\d+/i.test(sql)
}
/**
* 执行用户 SQL 查询
* - 只支持 SELECT 语句
* - 不强制 LIMIT,由用户自行控制
* - 带超时控制(由 Worker 管理器控制)
*/
export function executeRawSQL(sessionId: string, sql: string): SQLResult {
const db = openDatabase(sessionId)
if (!db) {
throw new Error('数据库不存在')
}
const trimmedSQL = sql.trim()
// 只允许 SELECT 语句
if (!trimmedSQL.toUpperCase().startsWith('SELECT')) {
throw new Error('只支持 SELECT 查询语句')
}
// 执行查询
const startTime = Date.now()
try {
// better-sqlite3 是同步的,超时由 Worker 管理器控制
const stmt = db.prepare(trimmedSQL)
const rows = stmt.all()
const duration = Date.now() - startTime
// 获取列名
const columns = stmt.columns().map((col) => col.name)
// 将结果转换为二维数组
const rowData = rows.map((row: any) => columns.map((col) => row[col]))
return {
columns,
rows: rowData,
rowCount: rows.length,
duration,
limited: false, // 不再强制限制
}
} catch (error) {
if (error instanceof Error) {
// 美化错误信息
const message = error.message.replace(/^SQLITE_ERROR: /, '').replace(/^SQLITE_READONLY: /, '只读模式:')
throw new Error(message)
}
throw error
}
}