Files
ChatLab/electron/main/worker/query/sql.ts
2026-01-08 01:18:03 +08:00

140 lines
2.9 KiB
TypeScript
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.
/**
* 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
}
}