mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-04-23 18:19:01 +08:00
140 lines
2.9 KiB
TypeScript
140 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
|
||
}
|
||
}
|
||
|