mirror of
https://github.com/apache/superset.git
synced 2026-04-24 10:35:01 +00:00
fix(sqllab): Replace autocomplete logic by a hook (#24677)
This commit is contained in:
@@ -16,38 +16,15 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import { css, styled, usePrevious, t } from '@superset-ui/core';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { css, styled, usePrevious } from '@superset-ui/core';
|
||||
|
||||
import { areArraysShallowEqual } from 'src/reduxUtils';
|
||||
import sqlKeywords from 'src/SqlLab/utils/sqlKeywords';
|
||||
import {
|
||||
queryEditorSetSelectedText,
|
||||
addTable,
|
||||
addDangerToast,
|
||||
} from 'src/SqlLab/actions/sqlLab';
|
||||
import {
|
||||
SCHEMA_AUTOCOMPLETE_SCORE,
|
||||
TABLE_AUTOCOMPLETE_SCORE,
|
||||
COLUMN_AUTOCOMPLETE_SCORE,
|
||||
SQL_FUNCTIONS_AUTOCOMPLETE_SCORE,
|
||||
} from 'src/SqlLab/constants';
|
||||
import {
|
||||
Editor,
|
||||
AceCompleterKeyword,
|
||||
FullSQLEditor as AceEditor,
|
||||
} from 'src/components/AsyncAceEditor';
|
||||
import { queryEditorSetSelectedText } from 'src/SqlLab/actions/sqlLab';
|
||||
import { FullSQLEditor as AceEditor } from 'src/components/AsyncAceEditor';
|
||||
import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor';
|
||||
import {
|
||||
useSchemas,
|
||||
useTables,
|
||||
tableEndpoints,
|
||||
skipToken,
|
||||
} from 'src/hooks/apiResources';
|
||||
import { useDatabaseFunctionsQuery } from 'src/hooks/apiResources/databaseFunctions';
|
||||
import { RootState } from 'src/views/store';
|
||||
import { useAnnotations } from './useAnnotations';
|
||||
import { useKeywords } from './useKeywords';
|
||||
|
||||
type HotKey = {
|
||||
key: string;
|
||||
@@ -101,68 +78,10 @@ const AceEditorWrapper = ({
|
||||
'schema',
|
||||
'templateParams',
|
||||
]);
|
||||
const { data: schemaOptions } = useSchemas({
|
||||
...(autocomplete && { dbId: queryEditor.dbId }),
|
||||
});
|
||||
const { data: tableData } = useTables({
|
||||
...(autocomplete && {
|
||||
dbId: queryEditor.dbId,
|
||||
schema: queryEditor.schema,
|
||||
}),
|
||||
});
|
||||
|
||||
const { data: functionNames, isError } = useDatabaseFunctionsQuery(
|
||||
{ dbId: queryEditor.dbId },
|
||||
{ skip: !autocomplete || !queryEditor.dbId },
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isError) {
|
||||
dispatch(
|
||||
addDangerToast(t('An error occurred while fetching function names.')),
|
||||
);
|
||||
}
|
||||
}, [dispatch, isError]);
|
||||
|
||||
const currentSql = queryEditor.sql ?? '';
|
||||
|
||||
// Loading schema, table and column names as auto-completable words
|
||||
const { schemas, schemaWords } = useMemo(
|
||||
() => ({
|
||||
schemas: schemaOptions ?? [],
|
||||
schemaWords: (schemaOptions ?? []).map(s => ({
|
||||
name: s.label,
|
||||
value: s.value,
|
||||
score: SCHEMA_AUTOCOMPLETE_SCORE,
|
||||
meta: 'schema',
|
||||
})),
|
||||
}),
|
||||
[schemaOptions],
|
||||
);
|
||||
const tables = tableData?.options ?? [];
|
||||
|
||||
const columns = useSelector<RootState, string[]>(state => {
|
||||
const columns = new Set<string>();
|
||||
tables.forEach(({ value }) => {
|
||||
tableEndpoints.tableMetadata
|
||||
.select(
|
||||
queryEditor.dbId && queryEditor.schema
|
||||
? {
|
||||
dbId: queryEditor.dbId,
|
||||
schema: queryEditor.schema,
|
||||
table: value,
|
||||
}
|
||||
: skipToken,
|
||||
)(state)
|
||||
.data?.columns?.forEach(({ name }) => {
|
||||
columns.add(name);
|
||||
});
|
||||
});
|
||||
return [...columns];
|
||||
}, shallowEqual);
|
||||
|
||||
const [sql, setSql] = useState(currentSql);
|
||||
const [words, setWords] = useState<AceCompleterKeyword[]>([]);
|
||||
|
||||
// The editor changeSelection is called multiple times in a row,
|
||||
// faster than React reconciliation process, so the selected text
|
||||
@@ -173,24 +92,10 @@ const AceEditorWrapper = ({
|
||||
useEffect(() => {
|
||||
// Making sure no text is selected from previous mount
|
||||
dispatch(queryEditorSetSelectedText(queryEditor, null));
|
||||
setAutoCompleter();
|
||||
}, []);
|
||||
|
||||
const prevTables = usePrevious(tables) ?? [];
|
||||
const prevSchemas = usePrevious(schemas) ?? [];
|
||||
const prevColumns = usePrevious(columns) ?? [];
|
||||
const prevSql = usePrevious(currentSql);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!areArraysShallowEqual(tables, prevTables) ||
|
||||
!areArraysShallowEqual(schemas, prevSchemas) ||
|
||||
!areArraysShallowEqual(columns, prevColumns)
|
||||
) {
|
||||
setAutoCompleter();
|
||||
}
|
||||
}, [tables, schemas, columns]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentSql !== prevSql) {
|
||||
setSql(currentSql);
|
||||
@@ -243,62 +148,6 @@ const AceEditorWrapper = ({
|
||||
onChange(text);
|
||||
};
|
||||
|
||||
function setAutoCompleter() {
|
||||
const tableWords = tables.map(t => {
|
||||
const tableName = t.value;
|
||||
|
||||
return {
|
||||
name: t.label,
|
||||
value: tableName,
|
||||
score: TABLE_AUTOCOMPLETE_SCORE,
|
||||
meta: 'table',
|
||||
};
|
||||
});
|
||||
|
||||
const columnWords = columns.map(col => ({
|
||||
name: col,
|
||||
value: col,
|
||||
score: COLUMN_AUTOCOMPLETE_SCORE,
|
||||
meta: 'column',
|
||||
}));
|
||||
|
||||
const functionWords = (functionNames ?? []).map(func => ({
|
||||
name: func,
|
||||
value: func,
|
||||
score: SQL_FUNCTIONS_AUTOCOMPLETE_SCORE,
|
||||
meta: 'function',
|
||||
}));
|
||||
|
||||
const completer = {
|
||||
insertMatch: (editor: Editor, data: any) => {
|
||||
if (data.meta === 'table') {
|
||||
dispatch(addTable(queryEditor, data.value, queryEditor.schema));
|
||||
}
|
||||
|
||||
let { caption } = data;
|
||||
if (data.meta === 'table' && caption.includes(' ')) {
|
||||
caption = `"${caption}"`;
|
||||
}
|
||||
|
||||
// executing https://github.com/thlorenz/brace/blob/3a00c5d59777f9d826841178e1eb36694177f5e6/ext/language_tools.js#L1448
|
||||
editor.completer.insertMatch(
|
||||
`${caption}${['function', 'schema'].includes(data.meta) ? '' : ' '}`,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const words = schemaWords
|
||||
.concat(tableWords)
|
||||
.concat(columnWords)
|
||||
.concat(functionWords)
|
||||
.concat(sqlKeywords)
|
||||
.map(word => ({
|
||||
...word,
|
||||
completer,
|
||||
}));
|
||||
|
||||
setWords(words);
|
||||
}
|
||||
const { data: annotations } = useAnnotations({
|
||||
dbId: queryEditor.dbId,
|
||||
schema: queryEditor.schema,
|
||||
@@ -306,9 +155,18 @@ const AceEditorWrapper = ({
|
||||
templateParams: queryEditor.templateParams,
|
||||
});
|
||||
|
||||
const keywords = useKeywords(
|
||||
{
|
||||
queryEditorId,
|
||||
dbId: queryEditor.dbId,
|
||||
schema: queryEditor.schema,
|
||||
},
|
||||
!autocomplete,
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledAceEditor
|
||||
keywords={words}
|
||||
keywords={keywords}
|
||||
onLoad={onEditorLoad}
|
||||
onBlur={onBlurSql}
|
||||
height={height}
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import {
|
||||
createWrapper,
|
||||
defaultStore as store,
|
||||
createStore,
|
||||
} from 'spec/helpers/testing-library';
|
||||
import { api } from 'src/hooks/apiResources/queryApi';
|
||||
import { schemaApiUtil } from 'src/hooks/apiResources/schemas';
|
||||
import { tableApiUtil } from 'src/hooks/apiResources/tables';
|
||||
import { addTable } from 'src/SqlLab/actions/sqlLab';
|
||||
import { initialState } from 'src/SqlLab/fixtures';
|
||||
import { reducers } from 'src/SqlLab/reducers';
|
||||
import {
|
||||
SCHEMA_AUTOCOMPLETE_SCORE,
|
||||
TABLE_AUTOCOMPLETE_SCORE,
|
||||
COLUMN_AUTOCOMPLETE_SCORE,
|
||||
SQL_FUNCTIONS_AUTOCOMPLETE_SCORE,
|
||||
} from 'src/SqlLab/constants';
|
||||
import { useKeywords } from './useKeywords';
|
||||
|
||||
const fakeSchemaApiResult = ['schema1', 'schema2'];
|
||||
const fakeTableApiResult = {
|
||||
count: 2,
|
||||
result: [
|
||||
{
|
||||
id: 1,
|
||||
value: 'fake api result1',
|
||||
label: 'fake api label1',
|
||||
type: 'table',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
value: 'fake api result2',
|
||||
label: 'fake api label2',
|
||||
type: 'table',
|
||||
},
|
||||
],
|
||||
};
|
||||
const fakeFunctionNamesApiResult = {
|
||||
function_names: ['abs', 'avg', 'sum'],
|
||||
};
|
||||
|
||||
const expectDbId = 1;
|
||||
const expectSchema = 'schema1';
|
||||
|
||||
beforeEach(() => {
|
||||
act(() => {
|
||||
store.dispatch(
|
||||
schemaApiUtil.upsertQueryData(
|
||||
'schemas',
|
||||
{
|
||||
dbId: expectDbId,
|
||||
forceRefresh: false,
|
||||
},
|
||||
fakeSchemaApiResult.map(value => ({
|
||||
value,
|
||||
label: value,
|
||||
title: value,
|
||||
})),
|
||||
),
|
||||
);
|
||||
store.dispatch(
|
||||
tableApiUtil.upsertQueryData(
|
||||
'tables',
|
||||
{ dbId: expectDbId, schema: expectSchema },
|
||||
{
|
||||
options: fakeTableApiResult.result,
|
||||
hasMore: false,
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
act(() => {
|
||||
store.dispatch(api.util.resetApiState());
|
||||
});
|
||||
});
|
||||
|
||||
test('returns keywords including fetched function_names data', async () => {
|
||||
const dbFunctionNamesApiRoute = `glob:*/api/v1/database/${expectDbId}/function_names/`;
|
||||
fetchMock.get(dbFunctionNamesApiRoute, fakeFunctionNamesApiResult);
|
||||
|
||||
const { result, waitFor } = renderHook(
|
||||
() =>
|
||||
useKeywords({
|
||||
queryEditorId: 'testqueryid',
|
||||
dbId: expectDbId,
|
||||
schema: expectSchema,
|
||||
}),
|
||||
{
|
||||
wrapper: createWrapper({
|
||||
useRedux: true,
|
||||
store,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(fetchMock.calls(dbFunctionNamesApiRoute).length).toBe(1),
|
||||
);
|
||||
fakeSchemaApiResult.forEach(schema => {
|
||||
expect(result.current).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: schema,
|
||||
score: SCHEMA_AUTOCOMPLETE_SCORE,
|
||||
meta: 'schema',
|
||||
}),
|
||||
);
|
||||
});
|
||||
fakeTableApiResult.result.forEach(({ value }) => {
|
||||
expect(result.current).toContainEqual(
|
||||
expect.objectContaining({
|
||||
value,
|
||||
score: TABLE_AUTOCOMPLETE_SCORE,
|
||||
meta: 'table',
|
||||
}),
|
||||
);
|
||||
});
|
||||
fakeFunctionNamesApiResult.function_names.forEach(func => {
|
||||
expect(result.current).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: func,
|
||||
value: func,
|
||||
meta: 'function',
|
||||
score: SQL_FUNCTIONS_AUTOCOMPLETE_SCORE,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('skip fetching if autocomplete skipped', () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useKeywords(
|
||||
{
|
||||
queryEditorId: 'testqueryid',
|
||||
dbId: expectDbId,
|
||||
schema: expectSchema,
|
||||
},
|
||||
true,
|
||||
),
|
||||
{
|
||||
wrapper: createWrapper({
|
||||
useRedux: true,
|
||||
store,
|
||||
}),
|
||||
},
|
||||
);
|
||||
expect(result.current).toEqual([]);
|
||||
expect(fetchMock.calls()).toEqual([]);
|
||||
});
|
||||
|
||||
test('returns column keywords among selected tables', async () => {
|
||||
const expectTable = 'table1';
|
||||
const expectColumn = 'column1';
|
||||
const expectQueryEditorId = 'testqueryid';
|
||||
|
||||
const unexpectedColumn = 'column2';
|
||||
const unexpectedTable = 'table2';
|
||||
|
||||
const dbFunctionNamesApiRoute = `glob:*/api/v1/database/${expectDbId}/function_names/`;
|
||||
const storeWithSqlLab = createStore(initialState, reducers);
|
||||
fetchMock.get(dbFunctionNamesApiRoute, fakeFunctionNamesApiResult);
|
||||
|
||||
act(() => {
|
||||
storeWithSqlLab.dispatch(
|
||||
tableApiUtil.upsertQueryData(
|
||||
'tableMetadata',
|
||||
{ dbId: expectDbId, schema: expectSchema, table: expectTable },
|
||||
{
|
||||
name: expectTable,
|
||||
columns: [
|
||||
{
|
||||
name: expectColumn,
|
||||
type: 'VARCHAR',
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
storeWithSqlLab.dispatch(
|
||||
tableApiUtil.upsertQueryData(
|
||||
'tableMetadata',
|
||||
{ dbId: expectDbId, schema: expectSchema, table: unexpectedTable },
|
||||
{
|
||||
name: unexpectedTable,
|
||||
columns: [
|
||||
{
|
||||
name: unexpectedColumn,
|
||||
type: 'VARCHAR',
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
);
|
||||
storeWithSqlLab.dispatch(
|
||||
addTable({ id: expectQueryEditorId }, expectTable, expectSchema),
|
||||
);
|
||||
});
|
||||
|
||||
const { result, waitFor } = renderHook(
|
||||
() =>
|
||||
useKeywords({
|
||||
queryEditorId: expectQueryEditorId,
|
||||
dbId: expectDbId,
|
||||
schema: expectSchema,
|
||||
}),
|
||||
{
|
||||
wrapper: createWrapper({
|
||||
useRedux: true,
|
||||
store: storeWithSqlLab,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(result.current).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: expectColumn,
|
||||
value: expectColumn,
|
||||
score: COLUMN_AUTOCOMPLETE_SCORE,
|
||||
meta: 'column',
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
expect(result.current).not.toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: unexpectedColumn,
|
||||
}),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
storeWithSqlLab.dispatch(
|
||||
addTable({ id: expectQueryEditorId }, unexpectedTable, expectSchema),
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(result.current).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: unexpectedColumn,
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
import { useSelector, useDispatch, shallowEqual, useStore } from 'react-redux';
|
||||
import { t } from '@superset-ui/core';
|
||||
|
||||
import { Editor } from 'src/components/AsyncAceEditor';
|
||||
import sqlKeywords from 'src/SqlLab/utils/sqlKeywords';
|
||||
import { addTable, addDangerToast } from 'src/SqlLab/actions/sqlLab';
|
||||
import {
|
||||
SCHEMA_AUTOCOMPLETE_SCORE,
|
||||
TABLE_AUTOCOMPLETE_SCORE,
|
||||
COLUMN_AUTOCOMPLETE_SCORE,
|
||||
SQL_FUNCTIONS_AUTOCOMPLETE_SCORE,
|
||||
} from 'src/SqlLab/constants';
|
||||
import {
|
||||
schemaEndpoints,
|
||||
tableEndpoints,
|
||||
skipToken,
|
||||
} from 'src/hooks/apiResources';
|
||||
import { api } from 'src/hooks/apiResources/queryApi';
|
||||
import { useDatabaseFunctionsQuery } from 'src/hooks/apiResources/databaseFunctions';
|
||||
import useEffectEvent from 'src/hooks/useEffectEvent';
|
||||
import { SqlLabRootState } from 'src/SqlLab/types';
|
||||
|
||||
type Params = {
|
||||
queryEditorId: string | number;
|
||||
dbId?: string | number;
|
||||
schema?: string;
|
||||
};
|
||||
|
||||
const EMPTY_LIST = [] as typeof sqlKeywords;
|
||||
|
||||
const { useQueryState: useSchemasQueryState } = schemaEndpoints.schemas;
|
||||
const { useQueryState: useTablesQueryState } = tableEndpoints.tables;
|
||||
|
||||
export function useKeywords(
|
||||
{ queryEditorId, dbId, schema }: Params,
|
||||
skip = false,
|
||||
) {
|
||||
const dispatch = useDispatch();
|
||||
const hasFetchedKeywords = useRef(false);
|
||||
// skipFetch is used to prevent re-evaluating memoized keywords
|
||||
// due to updated api results by skip flag
|
||||
const skipFetch = hasFetchedKeywords && skip;
|
||||
const { data: schemaOptions } = useSchemasQueryState(
|
||||
{
|
||||
dbId,
|
||||
forceRefresh: false,
|
||||
},
|
||||
{ skip: skipFetch || !dbId },
|
||||
);
|
||||
const { data: tableData } = useTablesQueryState(
|
||||
{
|
||||
dbId,
|
||||
schema,
|
||||
forceRefresh: false,
|
||||
},
|
||||
{ skip: skipFetch || !dbId || !schema },
|
||||
);
|
||||
|
||||
const { data: functionNames, isError } = useDatabaseFunctionsQuery(
|
||||
{ dbId },
|
||||
{ skip: skipFetch || !dbId },
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isError) {
|
||||
dispatch(
|
||||
addDangerToast(t('An error occurred while fetching function names.')),
|
||||
);
|
||||
}
|
||||
}, [dispatch, isError]);
|
||||
|
||||
const tablesForColumnMetadata = useSelector<SqlLabRootState, string[]>(
|
||||
({ sqlLab }) =>
|
||||
skip
|
||||
? []
|
||||
: (sqlLab?.tables ?? [])
|
||||
.filter(table => table.queryEditorId === queryEditorId)
|
||||
.map(table => table.name),
|
||||
shallowEqual,
|
||||
);
|
||||
|
||||
const store = useStore();
|
||||
const apiState = store.getState()[api.reducerPath];
|
||||
|
||||
const allColumns = useMemo(() => {
|
||||
const columns = new Set<string>();
|
||||
tablesForColumnMetadata.forEach(table => {
|
||||
tableEndpoints.tableMetadata
|
||||
.select(
|
||||
dbId && schema
|
||||
? {
|
||||
dbId,
|
||||
schema,
|
||||
table,
|
||||
}
|
||||
: skipToken,
|
||||
)({
|
||||
[api.reducerPath]: apiState,
|
||||
})
|
||||
.data?.columns?.forEach(({ name }) => {
|
||||
columns.add(name);
|
||||
});
|
||||
});
|
||||
return [...columns];
|
||||
}, [dbId, schema, apiState, tablesForColumnMetadata]);
|
||||
|
||||
const insertMatch = useEffectEvent((editor: Editor, data: any) => {
|
||||
if (data.meta === 'table') {
|
||||
dispatch(addTable({ id: queryEditorId, dbId }, data.value, schema));
|
||||
}
|
||||
|
||||
let { caption } = data;
|
||||
if (data.meta === 'table' && caption.includes(' ')) {
|
||||
caption = `"${caption}"`;
|
||||
}
|
||||
|
||||
// executing https://github.com/thlorenz/brace/blob/3a00c5d59777f9d826841178e1eb36694177f5e6/ext/language_tools.js#L1448
|
||||
editor.completer.insertMatch(
|
||||
`${caption}${['function', 'schema'].includes(data.meta) ? '' : ' '}`,
|
||||
);
|
||||
});
|
||||
|
||||
const schemaKeywords = useMemo(
|
||||
() =>
|
||||
(schemaOptions ?? []).map(s => ({
|
||||
name: s.label,
|
||||
value: s.value,
|
||||
score: SCHEMA_AUTOCOMPLETE_SCORE,
|
||||
meta: 'schema',
|
||||
completer: {
|
||||
insertMatch,
|
||||
},
|
||||
})),
|
||||
[schemaOptions, insertMatch],
|
||||
);
|
||||
|
||||
const tableKeywords = useMemo(
|
||||
() =>
|
||||
(tableData?.options ?? []).map(({ value, label }) => ({
|
||||
name: label,
|
||||
value,
|
||||
score: TABLE_AUTOCOMPLETE_SCORE,
|
||||
meta: 'table',
|
||||
completer: {
|
||||
insertMatch,
|
||||
},
|
||||
})),
|
||||
[tableData?.options, insertMatch],
|
||||
);
|
||||
|
||||
const columnKeywords = useMemo(
|
||||
() =>
|
||||
allColumns.map(col => ({
|
||||
name: col,
|
||||
value: col,
|
||||
score: COLUMN_AUTOCOMPLETE_SCORE,
|
||||
meta: 'column',
|
||||
})),
|
||||
[allColumns],
|
||||
);
|
||||
|
||||
const functionKeywords = useMemo(
|
||||
() =>
|
||||
(functionNames ?? []).map(func => ({
|
||||
name: func,
|
||||
value: func,
|
||||
score: SQL_FUNCTIONS_AUTOCOMPLETE_SCORE,
|
||||
meta: 'function',
|
||||
completer: {
|
||||
insertMatch,
|
||||
},
|
||||
})),
|
||||
[functionNames, insertMatch],
|
||||
);
|
||||
|
||||
const keywords = useMemo(
|
||||
() =>
|
||||
columnKeywords
|
||||
.concat(schemaKeywords)
|
||||
.concat(tableKeywords)
|
||||
.concat(functionKeywords)
|
||||
.concat(sqlKeywords),
|
||||
[schemaKeywords, tableKeywords, columnKeywords, functionKeywords],
|
||||
);
|
||||
|
||||
hasFetchedKeywords.current = !skip;
|
||||
|
||||
return skip ? EMPTY_LIST : keywords;
|
||||
}
|
||||
Reference in New Issue
Block a user