---
title: "SQL"
description: "Kasumi TypeScript SQL API、参数守卫和错误行为"
---


> 在 TypeScript 中查询数据库驱动、打开连接、执行参数化语句并关闭连接。

## 模块边界

`KSM.sql` 只封装 SQL 插件的前端调用，不实现数据库协议、SQL 方言转换或 ORM。实际可用
驱动由宿主返回；SQLite 在 Windows 和 Android 中内置。Windows 还会发现系统 MySQL /
MariaDB 和 PostgreSQL ODBC 驱动，MongoDB 与 Android 远程数据库需要宿主注册驱动。

实现入口是 `frontend/src/api/sql.ts` 和 `frontend/src/api/guards.ts`。所有语句都必须
经过参数守卫，宿主仍会执行 schema、权限和驱动级校验。

## 平台支持

| 能力 | Windows | Android |
| --- | --- | --- |
| SQL 五个命令 | 支持 | 支持 |
| SQLite | 内置 | 内置 |
| MySQL / PostgreSQL | 系统 ODBC 驱动存在时支持 | 注册驱动后支持 |
| MongoDB | 注册驱动后支持 | 注册驱动后支持 |

`sql.drivers` 返回的数组是唯一可靠的运行时能力来源。不要根据协议枚举假定驱动已安装。

## 方法

### `drivers()`

```ts
const { drivers } = await KSM.sql.drivers();
```

返回 `{ drivers: KSMSqlDriver[] }`。可能包含 `sqlite`、`mysql`、`postgres` 和
`mongodb` 中的任意子集。

### `open({ driver, url })`

```ts
const { connectionId } = await KSM.sql.open({
  driver: 'sqlite',
  url: ':memory:'
});
```

创建独立连接。`driver` 必须属于 `sqlite | mysql | postgres | mongodb`，`url` 必须是非
空字符串且不超过 4096 字符。URL 的具体格式由驱动定义。

### `query({ connectionId, statement, params? })`

```ts
const result = await KSM.sql.query({
  connectionId,
  statement: 'SELECT id, body FROM notes WHERE id = ?',
  params: [1]
});
```

返回 `{ columns, rows }`。`rows` 是列名到标量值的对象数组。语句必须使用目标驱动的占位符，
不得拼接未转义的用户输入。

### `execute({ connectionId, statement, params? })`

```ts
const result = await KSM.sql.execute({
  connectionId,
  statement: 'UPDATE notes SET body = ? WHERE id = ?',
  params: ['updated', 1]
});
```

返回 `{ rowsAffected, lastInsertId }`。`lastInsertId` 可能为 `null`。

### `close({ connectionId })`

```ts
await KSM.sql.close({ connectionId });
```

关闭后连接 ID 失效，再次使用返回 `NOT_FOUND`。

## 参数守卫

前端会在发送请求前拒绝：

- 空 `connectionId` 或超过 128 字符的连接 ID。
- 空语句或超过 1 MiB 的语句。
- 非数组的 `params`。
- 字符串、有限数值、布尔值和 `null` 之外的参数。
- 非协议枚举的驱动名。

这些失败通常是普通 `TypeError`。绕过前端封装直接调用 `KSM.invoke()` 时，宿主返回
`INVALID_ARGS`。

## 示例

```ts
import { KSM } from '@kasumi/bridge';

const { drivers } = await KSM.sql.drivers();
if (!drivers.includes('sqlite')) {
  throw new Error('SQLite driver missing');
}

const { connectionId } = await KSM.sql.open({
  driver: 'sqlite',
  url: 'app://data/app.db'
});

try {
  await KSM.sql.execute({
    connectionId,
    statement: 'CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT NOT NULL)'
  });

  await KSM.sql.execute({
    connectionId,
    statement: 'INSERT INTO notes (body) VALUES (?)',
    params: ['hello']
  });

  const result = await KSM.sql.query({
    connectionId,
    statement: 'SELECT id, body FROM notes ORDER BY id'
  });

  console.info(result.rows);
} finally {
  await KSM.sql.close({ connectionId });
}
```

## 错误行为

| 错误码 | 场景 |
| --- | --- |
| `INVALID_ARGS` | 参数结构、语句语法或驱动 URL 非法 |
| `PERMISSION_DENIED` | 路径、业务授权或驱动策略拒绝 |
| `NOT_FOUND` | 连接 ID 不存在或已关闭 |
| `IO_ERROR` | 打开、网络、磁盘或执行失败 |
| `NOT_SUPPORTED` | 驱动未注册或平台不支持 |
| `TIMEOUT` | 请求超过 10 秒默认超时 |
| `CANCELLED` | 请求被取消 |

SQL 请求复用 `KSM.invoke` 的超时与取消链路。长查询应在业务层控制语句范围；当前 API
不提供进度事件、事务对象或公开 AbortSignal。

## 相关

- [SQL 插件驱动与安全边界](/docs/kasumi/plugins/sql)
- [错误码](/docs/kasumi/api/types)
- [能力查询](/docs/kasumi/api/capabilities)

## 排查路径

1. 先调用 `KSM.sql.drivers()`，确认驱动存在。
2. 确认语句占位符与驱动方言一致。
3. 确认参数数量、类型和顺序与语句匹配。
4. SQLite 路径使用 `app://data`、`app://cache` 或 `app://tmp`。
5. 连接必须在 `finally` 中关闭，页面销毁后不得复用旧连接 ID。
