import { describe, expect, it } from 'vitest'; import { buildBoundedQueryEditorCompletionSuggestions, buildQueryEditorAliasMap, buildQueryEditorResultSetMergeKey, collectQueryEditorReferencedDatabaseNames, createBoundedQueryEditorCompletionCandidateBatch, findCompletionTablesByDatabase, getCompletionTableSchemaCounts, isOracleBaseTableReference, materializeBoundedQueryEditorCompletionBatches, rankQueryEditorCompletionCandidate, resolveQueryEditorCompletionFilterText, resolveOracleLikeDefaultSchemaName, resolveOracleLikeExecutionSchemaName, resolveOracleLikeLookupSchemaCandidates, resolveQueryEditorMonacoLanguage, resolveQueryEditorNavigationTarget, selectUnqualifiedCompletionSynonyms, shouldHandleQueryEditorRunShortcutFallback, } from './QueryEditorHelpers'; describe('QueryEditor result merge identity', () => { it('keeps zero-based Elasticsearch request indexes distinct', () => { const first = buildQueryEditorResultSetMergeKey({ sql: 'GET /events/_count', sourceStatementIndex: 0, statementResultIndex: 0, }); const second = buildQueryEditorResultSetMergeKey({ sql: 'GET /events/_count', sourceStatementIndex: 1, statementResultIndex: 0, }); expect(first).not.toBe(second); expect(first).toContain('::0::0'); expect(second).toContain('::1::0'); }); }); describe('QueryEditor completion candidate budget', () => { it('builds at most the budget after ranking exact, prefix, and substring matches', () => { const candidates = [ ...Array.from({ length: 5_000 }, (_, index) => `archive_entity_${String(index).padStart(4, '0')}`), 'entity_primary', 'entity', ]; let materialized = 0; const suggestions = buildBoundedQueryEditorCompletionSuggestions({ candidates, prefix: 'entity', getMatchRank: (candidate, prefix) => rankQueryEditorCompletionCandidate(prefix, [candidate]), getSelectionKey: (candidate, _prefix, rank) => `${rank}${candidate}`, buildSuggestion: (candidate) => { materialized += 1; const rank = rankQueryEditorCompletionCandidate('entity', [candidate]); return { label: candidate, sortText: `${rank}${candidate}` }; }, }); expect(suggestions).toHaveLength(200); expect(materialized).toBe(200); expect(suggestions.slice(0, 2).map((item) => item.label)).toEqual(['entity', 'entity_primary']); }); it('stops scanning an empty-prefix source once the budget is full', () => { let inspected = 0; const candidates = Array.from({ length: 10_000 }, (_, index) => `table_${String(index).padStart(5, '0')}`); const suggestions = buildBoundedQueryEditorCompletionSuggestions({ candidates, prefix: '', getMatchRank: () => { inspected += 1; return 0; }, getSelectionKey: (candidate) => candidate, buildSuggestion: (candidate) => ({ label: candidate }), sourceAlreadySortedBySelection: true, }); expect(suggestions).toHaveLength(200); expect(inspected).toBe(200); }); it('keeps a late same-rank candidate when its final sort key is better', () => { const candidates = [ ...Array.from({ length: 200 }, (_, index) => ({ label: `other_${index}`, sortText: `10${String(index).padStart(3, '0')}`, })), { label: 'current_late', sortText: '00current_late' }, ]; let materialized = 0; const suggestions = buildBoundedQueryEditorCompletionSuggestions({ candidates, prefix: '', getMatchRank: () => 0, getSelectionKey: (candidate) => candidate.sortText, buildSuggestion: (candidate) => { materialized += 1; return candidate; }, }); expect(suggestions).toHaveLength(200); expect(materialized).toBe(200); expect(suggestions.map((item) => item.label)).toContain('current_late'); }); it('uses final sortText semantics when a late exact match ranks after current-database prefixes', () => { const candidates = [ ...Array.from({ length: 200 }, (_, index) => ({ label: `current_prefix_${String(index).padStart(3, '0')}`, matchRank: 1 as const, sortText: `00current_${String(index).padStart(3, '0')}`, })), { label: 'other_exact', matchRank: 0 as const, sortText: '01other_exact' }, ]; const suggestions = buildBoundedQueryEditorCompletionSuggestions({ candidates, prefix: 'target', getMatchRank: (candidate) => candidate.matchRank, getSelectionKey: (candidate) => candidate.sortText, buildSuggestion: (candidate) => candidate, }); expect(suggestions).toHaveLength(200); expect(suggestions.map((item) => item.label)).not.toContain('other_exact'); expect(suggestions[199]?.label).toBe('current_prefix_199'); }); it('materializes at most one global budget across nine completion categories', () => { let materialized = 0; const batches = Array.from({ length: 9 }, (_, groupIndex) => ( createBoundedQueryEditorCompletionCandidateBatch({ candidates: Array.from({ length: 500 }, (_, candidateIndex) => ({ label: `group_${groupIndex}_${candidateIndex}`, sortText: `${String(groupIndex).padStart(2, '0')}${String(candidateIndex).padStart(3, '0')}`, })), prefix: '', getMatchRank: () => 0, getSelectionKey: (candidate) => candidate.sortText, buildSuggestion: (candidate) => { materialized += 1; return candidate; }, }) )); expect(materialized).toBe(0); const suggestions = materializeBoundedQueryEditorCompletionBatches(batches); expect(suggestions).toHaveLength(200); expect(materialized).toBe(200); expect(suggestions[0]?.label).toBe('group_0_0'); expect(suggestions[199]?.label).toBe('group_0_199'); }); it('caches schema counts on the current-database partition without reading other table names', () => { let currentTableNameReads = 0; let otherTableNameReads = 0; const currentTables = Array.from({ length: 3 }, (_, index) => ({ dbName: 'main', get tableName() { currentTableNameReads += 1; return index < 2 ? `schema_${index}.users` : 'orders'; }, })); const otherTables = Array.from({ length: 5_000 }, (_, index) => ({ dbName: 'archive', get tableName() { otherTableNameReads += 1; return `archive_${index}`; }, })); const allTables = [...otherTables, ...currentTables]; const firstPartition = findCompletionTablesByDatabase(allTables, 'main'); const firstCounts = getCompletionTableSchemaCounts(firstPartition); const secondPartition = findCompletionTablesByDatabase(allTables, 'main'); const secondCounts = getCompletionTableSchemaCounts(secondPartition); expect(firstPartition).toBe(secondPartition); expect(firstCounts).toBe(secondCounts); expect(firstCounts.get('users')).toBe(2); expect(firstCounts.get('orders')).toBe(1); expect(currentTableNameReads).toBe(3); expect(otherTableNameReads).toBe(0); }); }); describe('QueryEditor completion filter text', () => { it('returns the matching suffix for Monaco substring filtering', () => { expect(resolveQueryEditorCompletionFilterText('title', ['short_title'])).toBe('title'); expect(resolveQueryEditorCompletionFilterText('title', ['subtitle'])).toBe('title'); }); it('does not override the filter text for an empty prefix or a non-match', () => { expect(resolveQueryEditorCompletionFilterText('', ['short_title'])).toBeUndefined(); expect(resolveQueryEditorCompletionFilterText('name', ['short_title'])).toBeUndefined(); }); }); describe('QueryEditor Monaco SQL grammar', () => { it.each([ [{ config: { type: 'mysql' } }, 'mysql'], [{ config: { type: 'mariadb' } }, 'mysql'], [{ config: { type: 'custom', driver: 'greatdb' } }, 'mysql'], [{ config: { type: 'oceanbase', oceanBaseProtocol: 'mysql' } }, 'mysql'], [{ config: { type: 'oceanbase', oceanBaseProtocol: 'oracle' } }, 'sql'], [{ config: { type: 'postgres' } }, 'sql'], [{ config: { type: 'elasticsearch' } }, 'elasticsearch-console'], ])('maps connection row %# to the expected Monaco grammar', (connection, expectedLanguage) => { expect(resolveQueryEditorMonacoLanguage(connection)).toBe(expectedLanguage); }); }); describe('QueryEditor run shortcut routing', () => { it('reserves editor-originated shortcuts for Monaco and keeps document targets as a fallback', () => { const editorTarget = {} as Node; const editorPane = { contains: (node: Node) => node === editorTarget, } as Pick; expect(shouldHandleQueryEditorRunShortcutFallback({ editorHasFocus: true, targetNode: editorTarget, editorPane, })).toBe(false); expect(shouldHandleQueryEditorRunShortcutFallback({ editorHasFocus: true, targetNode: null, editorPane, })).toBe(true); expect(shouldHandleQueryEditorRunShortcutFallback({ editorHasFocus: false, targetNode: null, editorPane, })).toBe(false); }); }); describe('QueryEditorHelpers Oracle-like execution schema', () => { it('uses the selected schema when it differs from the login user', () => { const config = { type: 'oceanbase', oceanBaseProtocol: 'oracle', user: 'SBDEVREAD', database: 'SBDEV', }; expect(resolveOracleLikeDefaultSchemaName(config)).toBe('SBDEVREAD'); expect(resolveOracleLikeExecutionSchemaName(config, 'SBDEV')).toBe('SBDEVREAD'); expect(resolveOracleLikeLookupSchemaCandidates(config, 'SBDEV')).toEqual(['SBDEVREAD', 'SBDEV']); }); it('keeps the login user schema when the selected schema is the same owner', () => { const config = { type: 'oracle', user: 'APP_OWNER', database: 'ORCLPDB1', }; expect(resolveOracleLikeExecutionSchemaName(config, 'APP_OWNER')).toBe('APP_OWNER'); expect(resolveOracleLikeLookupSchemaCandidates(config, 'APP_OWNER')).toEqual(['APP_OWNER']); }); it('recognizes base tables but not synonyms when deciding whether ROWID is safe', () => { const baseTables = [ { dbName: 'A', tableName: 'A.PERSON' }, { dbName: 'B', tableName: 'B.ORDERS' }, ]; expect(isOracleBaseTableReference('SELECT * FROM A.person', 'A', baseTables)).toBe(true); expect(isOracleBaseTableReference('SELECT * FROM person', 'B', baseTables)).toBe(false); expect(isOracleBaseTableReference('SELECT * FROM person_view', 'B', baseTables)).toBe(false); }); it('prefers login-owner synonyms, falls back to PUBLIC, and excludes other owners', () => { const otherOwner = { ownerName: 'IMP_BASICINFO', synonymName: 'PERSON', targetName: 'OTHER_PERSON' }; const publicOwner = { ownerName: 'PUBLIC', synonymName: 'PERSON', targetName: 'PUBLIC_PERSON' }; const loginOwner = { ownerName: 'B', synonymName: 'PERSON', targetName: 'LOGIN_PERSON' }; const otherOnly = { ownerName: 'IMP_BASICINFO', synonymName: 'AC02', targetName: 'AC02' }; expect(selectUnqualifiedCompletionSynonyms( [otherOwner, publicOwner, otherOnly, loginOwner], 'B', )).toEqual([loginOwner]); expect(selectUnqualifiedCompletionSynonyms([otherOwner, publicOwner, otherOnly], 'B')).toEqual([publicOwner]); }); }); describe('QueryEditorHelpers qualified navigation (MySQL db.table + PG schema.table)', () => { it('tracks an explicit two-part owner separately from the current database', () => { const qualified = buildQueryEditorAliasMap('SELECT p.* FROM IMP_BASICINFO.PERSON p', 'A'); expect(qualified.p).toEqual({ dbName: 'IMP_BASICINFO', tableName: 'PERSON', explicitOwnerName: 'IMP_BASICINFO', }); const unqualified = buildQueryEditorAliasMap('SELECT p.* FROM PERSON p', 'A'); expect(unqualified.p).toEqual({ dbName: 'A', tableName: 'PERSON' }); }); it('collects cross-db names from SQL without requiring an empty visible list', () => { const sql = ` SELECT * FROM uk_back_corp; SELECT * FROM front_end_sys_new.fs_mkefu_regist_record WHERE mobile = '1'; DELETE FROM front_end_sys_new.fs_mkefu_regist_record WHERE mobile = '1'; SELECT * FROM public.users; SELECT * FROM analytics.public.events; `; const names = collectQueryEditorReferencedDatabaseNames( sql, 'mkefu_test_new', ['mkefu_test_new', 'front_end_sys_new', 'analytics'], ); expect(names).toEqual(expect.arrayContaining([ 'mkefu_test_new', 'front_end_sys_new', 'analytics', ])); // public 是常见 schema,两段时不应当成库去拉取 expect(names.map((name) => name.toLowerCase())).not.toContain('public'); }); it('infers MySQL-style db.table even when the db is not yet in visibleDbs', () => { const names = collectQueryEditorReferencedDatabaseNames( "SELECT * FROM front_end_sys_new.fs_mkefu_regist_record", 'mkefu_test_new', ['mkefu_test_new'], ); expect(names).toEqual(expect.arrayContaining(['mkefu_test_new', 'front_end_sys_new'])); }); it('resolves MySQL db.table when the database is visible', () => { const tables = [ { dbName: 'mkefu_test_new', tableName: 'uk_back_corp' }, { dbName: 'front_end_sys_new', tableName: 'fs_mkefu_regist_record' }, ]; const sql = 'SELECT * FROM front_end_sys_new.fs_mkefu_regist_record'; expect(resolveQueryEditorNavigationTarget( sql, sql.length, 'mkefu_test_new', ['mkefu_test_new', 'front_end_sys_new'], tables, )).toEqual({ type: 'table', dbName: 'front_end_sys_new', tableName: 'fs_mkefu_regist_record', schemaName: undefined, }); }); it('resolves PostgreSQL schema.table under the current database', () => { const tables = [ { dbName: 'appdb', tableName: 'public.users' }, { dbName: 'appdb', tableName: 'billing.orders' }, ]; expect(resolveQueryEditorNavigationTarget( 'select * from public.users', 'select * from public.users'.length, 'appdb', ['appdb', 'otherdb'], tables, )).toEqual({ type: 'table', dbName: 'appdb', tableName: 'public.users', schemaName: 'public', }); expect(resolveQueryEditorNavigationTarget( 'select * from billing.orders', 'select * from billing.orders'.length, 'appdb', ['appdb'], tables, )).toEqual({ type: 'table', dbName: 'appdb', tableName: 'billing.orders', schemaName: 'billing', }); }); it('resolves database.schema.table three-part names for PostgreSQL-style metadata', () => { const tables = [ { dbName: 'analytics', tableName: 'public.events' }, { dbName: 'analytics', tableName: 'events' }, ]; expect(resolveQueryEditorNavigationTarget( 'select * from analytics.public.events', 'select * from analytics.public.events'.length, 'appdb', ['appdb', 'analytics'], tables, )).toEqual({ type: 'table', dbName: 'analytics', tableName: 'public.events', schemaName: 'public', }); }); it('still resolves schema-qualified objects when the first segment is also a visible database name but no table exists there', () => { // 边界:可见库列表里碰巧有 "billing" 这个库,但当前库下才有 billing.orders const tables = [ { dbName: 'main', tableName: 'billing.orders' }, ]; expect(resolveQueryEditorNavigationTarget( 'select * from billing.orders', 'select * from billing.orders'.length, 'main', ['main', 'billing'], tables, )).toEqual({ type: 'table', dbName: 'main', tableName: 'billing.orders', schemaName: 'billing', }); }); });