v26.10.2 修复bug,简化项目
This commit is contained in:
1 parent
ff3dbddd13
commit
fc9bbc20e4
26 files changed
+557
-299
No files matched your search
@@ -11,6 +11,12 @@ function isObject(value) {
|
||||
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
|
||||
// 校验结果列名及每行的列数。
|
||||
function hasValidRows(body) {
|
||||
return isObject(body) && Array.isArray(body.columns) && body.columns.every((column) => typeof column === 'string')
|
||||
&& Array.isArray(body.rows) && body.rows.every((row) => Array.isArray(row) && row.length === body.columns.length);
|
||||
}
|
||||
|
||||
// 发一次请求并处理错误。
|
||||
async function request(url, init, database) {
|
||||
const headers = new Headers(init && init.headers ? init.headers : undefined);
|
||||
@@ -114,21 +120,13 @@ export async function useDatabase(name, database) {
|
||||
|
||||
// ---------------------------------------------------------------- 表结构
|
||||
|
||||
// 取类型基名(去掉参数)。
|
||||
function columnKind(type) {
|
||||
return String(type).split('(')[0].trim().toLowerCase();
|
||||
}
|
||||
|
||||
const knownTypes = ['int', 'bigint', 'smallint', 'str', 'text', 'varchar', 'char', 'float', 'double', 'decimal', 'numeric', 'bool', 'date', 'timestamp', 'blob'];
|
||||
|
||||
// 校验并规整列描述。
|
||||
function asColumns(value) {
|
||||
if (!Array.isArray(value)) throw new Error('服务器返回的列清单无法识别。');
|
||||
return value.map((entry) => {
|
||||
if (!isObject(entry) || typeof entry.name !== 'string' || typeof entry.type !== 'string') {
|
||||
if (!isObject(entry) || typeof entry.name !== 'string' || typeof entry.type !== 'string' || !entry.type.trim()) {
|
||||
throw new Error('服务器返回的列描述无法识别。');
|
||||
}
|
||||
if (!knownTypes.includes(columnKind(entry.type))) throw new Error(`不支持的列类型:${entry.type}`);
|
||||
return { ...entry, name: entry.name, type: entry.type };
|
||||
});
|
||||
}
|
||||
@@ -197,13 +195,11 @@ export async function fetchRows({ database, table, limit, offset, sort, dir, fil
|
||||
params.set('filter', JSON.stringify(filters.map((f) => ({ column: f.column, value: f.expression }))));
|
||||
}
|
||||
const body = await request(`/api/tables/${encodeURIComponent(table)}/rows?${params}`, undefined, database);
|
||||
if (!isObject(body) || !Array.isArray(body.columns) || !body.columns.every((c) => typeof c === 'string')
|
||||
|| !Array.isArray(body.rows) || typeof body.total !== 'number') {
|
||||
if (!hasValidRows(body) || !Number.isSafeInteger(body.total) || body.total < 0) {
|
||||
throw new Error('服务器返回的分页数据无法识别。');
|
||||
}
|
||||
const columns = body.columns;
|
||||
const rows = body.rows.map((raw) => {
|
||||
if (!Array.isArray(raw)) throw new Error('服务器返回的行数据无法识别。');
|
||||
return Object.fromEntries(columns.map((column, index) => [column, raw[index]]));
|
||||
});
|
||||
return { columns, rows, total: body.total };
|
||||
@@ -281,7 +277,7 @@ export async function query(sql, database, tabId) {
|
||||
const init = jsonInit('POST', { sql });
|
||||
init.headers['X-ChuSQL-Console'] = console;
|
||||
const body = await request('/api/query', init, database);
|
||||
if (!isObject(body) || !Array.isArray(body.columns) || !Array.isArray(body.rows)) {
|
||||
if (!hasValidRows(body)) {
|
||||
throw new Error('服务器返回的查询结果无法识别。');
|
||||
}
|
||||
const durationMs = performance.now() - started;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
|
||||
// 检查前端复合值显示、SQL 生成和编码错误。
|
||||
// 检查复合值、SQL 生成、类型元数据和编码错误。
|
||||
|
||||
const source = await readFile(new URL('../static/core.js', import.meta.url), 'utf8');
|
||||
const { cellText, sqlLiteral, typeLabel, editText } = await import(`data:text/javascript;base64,${Buffer.from(source).toString('base64')}`);
|
||||
@@ -17,4 +17,38 @@ assert.throws(() => sqlLiteral({ __chusql_runtime_v1: [1, [7, [typeInt]], [1, 2]
|
||||
assert.equal(typeLabel('runtime([1,[5,[[1,[]]]]])'), '[Int]');
|
||||
assert.equal(editText({ __chusql_runtime_v1: [1, typeString, 'abc'] }), 'abc');
|
||||
assert.throws(() => sqlLiteral({ __chusql_runtime_v1: [1, typeInt, 1.5] }), /integer/);
|
||||
console.log('runtime values: 9 assertions passed');
|
||||
const apiSource = await readFile(new URL('../static/api.js', import.meta.url), 'utf8');
|
||||
const api = await import(`data:text/javascript;base64,${Buffer.from(apiSource).toString('base64')}`);
|
||||
const columns = ['int', 'runtime([1,[5,[[1,[]]]]])', 'runtime([1,[6,[[4,[]]]]])', 'runtime([1,[7,[[1,[]],[4,[]]]]])', 'domain(age,int)']
|
||||
.map((type, index) => ({ name: `column_${index}`, type }));
|
||||
const table = { table: 'typed', rowCount: 0, columns };
|
||||
const originalFetch = globalThis.fetch;
|
||||
try {
|
||||
globalThis.fetch = async (url) => new Response(JSON.stringify(url === '/api/tables' ? [table] : table));
|
||||
assert.deepEqual((await api.listTables('test'))[0].columns, columns);
|
||||
assert.deepEqual((await api.getTable('test', 'typed')).columns, columns);
|
||||
for (const type of [undefined, 42, ' ']) {
|
||||
globalThis.fetch = async () => new Response(JSON.stringify([{ ...table, columns: [{ name: 'broken', type }] }]));
|
||||
await assert.rejects(api.listTables('test'), /列描述/);
|
||||
}
|
||||
const page = { columns: ['id', 'value'], rows: [[1, null]], total: 1 };
|
||||
globalThis.fetch = async () => new Response(JSON.stringify(page));
|
||||
assert.deepEqual((await api.fetchRows({ database: 'test', table: 'typed', limit: 10, offset: 0 })).rows,
|
||||
[{ id: 1, value: null }]);
|
||||
assert.deepEqual((await api.query('SELECT id, value FROM typed', 'test')).rows, page.rows);
|
||||
for (const invalid of [
|
||||
{ ...page, rows: [[1]] }, { ...page, rows: [[1, null, 3]] },
|
||||
{ ...page, rows: [null] }, { ...page, columns: ['id', 42] },
|
||||
]) {
|
||||
globalThis.fetch = async () => new Response(JSON.stringify(invalid));
|
||||
await assert.rejects(api.fetchRows({ database: 'test', table: 'typed', limit: 10, offset: 0 }), /分页数据/);
|
||||
assert.equal((await api.query('SELECT id, value FROM typed', 'test')).type, 'error');
|
||||
}
|
||||
for (const total of [-1, 1.5, '1']) {
|
||||
globalThis.fetch = async () => new Response(JSON.stringify({ ...page, total }));
|
||||
await assert.rejects(api.fetchRows({ database: 'test', table: 'typed', limit: 10, offset: 0 }), /分页数据/);
|
||||
}
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
console.log('runtime values and schemas: 27 assertions passed');
|
||||
Reference in new issue
Block a user