修复bug
This commit is contained in:
1 parent
fc9bbc20e4
commit
e87c4ee6a8
90 files changed
+3828
-452
No files matched your search
@@ -21,7 +21,7 @@ function hasValidRows(body) {
|
||||
async function request(url, init, database) {
|
||||
const headers = new Headers(init && init.headers ? init.headers : undefined);
|
||||
if (database) headers.set('X-ChuSQL-Database', database);
|
||||
if (editing && editing.database === database && url.startsWith('/api/tables') && (!init || init.method === 'GET')) {
|
||||
if (editing && editing.database === database && ((url.startsWith('/api/tables') && (!init || init.method === 'GET')) || url === '/api/normalization/analyze')) {
|
||||
headers.set('X-ChuSQL-Edit', editing.id);
|
||||
}
|
||||
let response;
|
||||
@@ -326,3 +326,23 @@ export async function loadUiSettings() {
|
||||
export async function saveUiSettings(settings) {
|
||||
await request('/api/ui-settings', jsonInit('PUT', settings));
|
||||
}
|
||||
|
||||
// 在页面已有视图上请求只读范式分析。
|
||||
export async function analyzeNormalization(database, table, declaration, expected) {
|
||||
const payload = { api_version: 1, database, table };
|
||||
if (expected) {
|
||||
payload.expected_object = expected.object;
|
||||
payload.expected_schema_fingerprint = expected.schema_fingerprint;
|
||||
}
|
||||
const init = jsonInit('POST', payload);
|
||||
if (!editing && consoleTransaction?.database === database) {
|
||||
init.headers['X-ChuSQL-Console'] = consoleTransaction.console;
|
||||
}
|
||||
// 原始声明直接嵌入,重复字段交给服务端明确拒绝。
|
||||
init.body = `${JSON.stringify(payload).slice(0, -1)},"declaration":${typeof declaration === 'string' ? declaration : JSON.stringify(declaration)}}`;
|
||||
const result = await request('/api/normalization/analyze', init, database);
|
||||
if (!isObject(result) || result.api_version !== 1 || !Array.isArray(result.findings) || !Array.isArray(result.schema)) {
|
||||
throw new Error('服务器返回的范式报告无法识别。');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -64,6 +64,8 @@ const state = {
|
||||
dockHeight: '22vh',
|
||||
};
|
||||
|
||||
let normalizationTarget = null;
|
||||
|
||||
const root = document.getElementById('root');
|
||||
|
||||
// ---------------------------------------------------------------- 小工具
|
||||
@@ -210,6 +212,21 @@ function renderShell() {
|
||||
<button type="button" class="primary" data-action="create-index">创建</button>
|
||||
</footer>
|
||||
</div>
|
||||
</dialog>
|
||||
<dialog id="dlg-normalization" class="dialog">
|
||||
<div class="dialog-body">
|
||||
<header class="dialog-header"><h2 id="normalization-title">范式分析</h2></header>
|
||||
<div class="dialog-content">
|
||||
<p>只读分析,以声明为条件。PRIMARY KEY、UNIQUE 和样本不会自动成为完整业务依赖;NULL 与重复行需要另行验证。</p>
|
||||
<label class="field"><span>JSON 声明(完整性 false,原子性 null 表示未声明)</span><textarea id="normalization-declaration" rows="10"></textarea></label>
|
||||
<p class="alert" id="normalization-error"></p>
|
||||
<div id="normalization-result"></div>
|
||||
</div>
|
||||
<footer class="dialog-footer">
|
||||
<button type="button" data-action="close-dialog">关闭</button>
|
||||
<button type="button" class="primary" data-action="analyze-normalization" disabled>分析声明</button>
|
||||
</footer>
|
||||
</div>
|
||||
</dialog>`;
|
||||
renderAccount();
|
||||
applyLayout();
|
||||
@@ -282,7 +299,7 @@ function renderSidebar() {
|
||||
<button class="mini danger" data-action="drop-table" data-table="${escapeHtml(table.name)}" title="删除表">✕</button>
|
||||
</div>
|
||||
${open ? `<ul class="column-list">${columns}</ul>${indexList}
|
||||
<div class="tree-actions"><button class="mini" data-action="open-index-dialog" data-table="${escapeHtml(table.name)}">+ 索引</button></div>` : ''}
|
||||
<div class="tree-actions">${state.database !== 'system' && !table.name.startsWith('__system_') ? `<button class="mini" data-action="open-normalization" data-table="${escapeHtml(table.name)}">范式分析</button>` : ''}<button class="mini" data-action="open-index-dialog" data-table="${escapeHtml(table.name)}">+ 索引</button></div>` : ''}
|
||||
</li>`;
|
||||
}).join('');
|
||||
|
||||
@@ -1383,7 +1400,7 @@ function startSplitterDrag(kind, event) {
|
||||
|
||||
// 这些动作都要求已经选中一个库(服务没有默认库)
|
||||
const DATABASE_ACTIONS = new Set([
|
||||
'open-table', 'demo-data', 'open-table-dialog', 'create-table',
|
||||
'open-normalization', 'analyze-normalization', 'open-table', 'demo-data', 'open-table-dialog', 'create-table',
|
||||
'open-index-dialog', 'create-index', 'drop-table', 'drop-column', 'drop-index',
|
||||
'drop-database', 'commit', 'rollback', 'copy-sql',
|
||||
]);
|
||||
@@ -1413,7 +1430,14 @@ function onClick(event) {
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case 'open-normalization':
|
||||
void openNormalization(target.dataset.table);
|
||||
break;
|
||||
case 'analyze-normalization':
|
||||
void analyzeNormalizationDeclaration();
|
||||
break;
|
||||
case 'logout':
|
||||
normalizationTarget = null;
|
||||
void (async () => {
|
||||
try {
|
||||
await api.logout();
|
||||
@@ -1790,3 +1814,68 @@ async function boot() {
|
||||
}
|
||||
|
||||
void boot();
|
||||
|
||||
// 打开结构入口并绑定当前 schema 身份。
|
||||
async function openNormalization(table) {
|
||||
const declaration = { declared_keys: [], dependencies: [], dependencies_complete: false, atomic_attributes: null };
|
||||
const target = { database: state.database, table, report: null };
|
||||
normalizationTarget = target;
|
||||
region('dlg-normalization').querySelector('[data-action="analyze-normalization"]').disabled = true;
|
||||
region('normalization-title').textContent = `范式分析 · ${target.database}.${table}`;
|
||||
region('normalization-declaration').value = JSON.stringify(declaration, null, 2);
|
||||
region('normalization-result').textContent = '正在读取结构…';
|
||||
region('normalization-error').textContent = '';
|
||||
region('dlg-normalization').onclose = () => { normalizationTarget = null; };
|
||||
region('dlg-normalization').showModal();
|
||||
try {
|
||||
target.report = await api.analyzeNormalization(target.database, table, declaration);
|
||||
if (normalizationTarget === target) {
|
||||
renderNormalization(target.report);
|
||||
region('dlg-normalization').querySelector('[data-action="analyze-normalization"]').disabled = false;
|
||||
}
|
||||
} catch (error) {
|
||||
if (normalizationTarget === target) {
|
||||
region('normalization-result').textContent = '';
|
||||
region('normalization-error').textContent = error.message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 提交显式声明并复核绑定的对象结构。
|
||||
async function analyzeNormalizationDeclaration() {
|
||||
const target = normalizationTarget;
|
||||
if (!target?.report) return;
|
||||
const button = region('dlg-normalization').querySelector('[data-action="analyze-normalization"]');
|
||||
button.disabled = true;
|
||||
region('normalization-error').textContent = '';
|
||||
try {
|
||||
const declaration = region('normalization-declaration').value;
|
||||
const report = await api.analyzeNormalization(target.database, target.table, declaration, target.report);
|
||||
if (normalizationTarget === target) {
|
||||
target.report = report;
|
||||
renderNormalization(report);
|
||||
}
|
||||
} catch (error) {
|
||||
if (normalizationTarget === target) {
|
||||
region('normalization-error').textContent = error.message;
|
||||
if (error.code === 'conflict') {
|
||||
target.report = null;
|
||||
region('normalization-result').textContent = '对象或视图已变化,请关闭后重新打开范式分析。';
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
button.disabled = !target.report;
|
||||
}
|
||||
}
|
||||
|
||||
// 展示条件结论、违反证据与一次分解。
|
||||
function renderNormalization(report) {
|
||||
const labels = { satisfied: '满足', violated: '违反', insufficient: '信息不足' };
|
||||
const schema = report.schema.map((column) => `${column.name}: ${column.ty}${column.nullable ? '(可空)' : '(非空)'}`).join(',');
|
||||
const findings = report.findings.map((finding) => `<li><strong>${escapeHtml(finding.normal_form.toUpperCase())}: ${escapeHtml(labels[finding.verdict] ?? finding.verdict)}</strong>
|
||||
<p>${escapeHtml(finding.condition)}</p><pre>${escapeHtml(JSON.stringify(finding.witnesses, null, 2))}</pre></li>`).join('');
|
||||
region('normalization-result').innerHTML = `<p>${escapeHtml(schema)}</p><p>视图:${escapeHtml(report.view)} · 结论以声明为条件</p>
|
||||
<details><summary>结构与约束</summary><pre>${escapeHtml(JSON.stringify(report.schema, null, 2))}</pre></details>
|
||||
<p>相对于声明的候选键:${escapeHtml(JSON.stringify(report.candidate_keys))}</p><ul>${findings}</ul>
|
||||
<p>一次分解建议(无损标签不证明现有 SQL 数据):</p><pre>${escapeHtml(JSON.stringify(report.suggestions, null, 2))}</pre>`;
|
||||
}
|
||||
Reference in new issue
Block a user