diff --git a/superset-frontend/packages/superset-ui-core/src/components/Icons/AntdEnhanced.tsx b/superset-frontend/packages/superset-ui-core/src/components/Icons/AntdEnhanced.tsx
index b1914d3e267..947a5ff3926 100644
--- a/superset-frontend/packages/superset-ui-core/src/components/Icons/AntdEnhanced.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/components/Icons/AntdEnhanced.tsx
@@ -115,6 +115,7 @@ import {
PlusSquareOutlined,
PlusOutlined,
ProfileOutlined,
+ PushpinFilled,
PushpinOutlined,
QuestionCircleOutlined,
ReloadOutlined,
@@ -270,6 +271,7 @@ const AntdIcons = {
PlusSquareOutlined,
PlusOutlined,
ProfileOutlined,
+ PushpinFilled,
PushpinOutlined,
ReloadOutlined,
QuestionCircleOutlined,
diff --git a/superset-frontend/src/SqlLab/components/ColumnElement/index.tsx b/superset-frontend/src/SqlLab/components/ColumnElement/index.tsx
index 59a029904f0..7606149e71b 100644
--- a/superset-frontend/src/SqlLab/components/ColumnElement/index.tsx
+++ b/superset-frontend/src/SqlLab/components/ColumnElement/index.tsx
@@ -74,13 +74,16 @@ interface ColumnElementProps {
keys?: { type: ColumnKeyTypeType }[];
type: string;
};
+ actions?: ReactNode;
}
-const NowrapDiv = styled.div`
+const ColumnType = styled.div`
white-space: nowrap;
+ color: ${({ theme }) => theme.colorTextDescription};
+ font-size: ${({ theme }) => theme.fontSizeSM}px;
`;
-const ColumnElement = ({ column }: ColumnElementProps) => {
+const ColumnElement = ({ column, actions }: ColumnElementProps) => {
let columnName: ReactNode = column.name;
let icons;
if (column.keys && column.keys.length > 0) {
@@ -110,10 +113,9 @@ const ColumnElement = ({ column }: ColumnElementProps) => {
{columnName}
{icons}
+ {actions}
-
- {column.type}
-
+ {column.type}
);
};
diff --git a/superset-frontend/src/SqlLab/components/EditorWrapper/useKeywords.test.ts b/superset-frontend/src/SqlLab/components/EditorWrapper/useKeywords.test.ts
index 60c6d5095f5..be285fa4d3d 100644
--- a/superset-frontend/src/SqlLab/components/EditorWrapper/useKeywords.test.ts
+++ b/superset-frontend/src/SqlLab/components/EditorWrapper/useKeywords.test.ts
@@ -257,6 +257,8 @@ test('returns column keywords among selected tables', async () => {
},
);
+ // Both columns should be present since all cached table metadata
+ // for this database is included in autocomplete
await waitFor(() =>
expect(result.current).toContainEqual(
expect.objectContaining({
@@ -268,31 +270,14 @@ test('returns column keywords among selected tables', async () => {
),
);
- expect(result.current).not.toContainEqual(
+ expect(result.current).toContainEqual(
expect.objectContaining({
name: unexpectedColumn,
+ value: unexpectedColumn,
+ score: COLUMN_AUTOCOMPLETE_SCORE,
+ meta: 'column',
}),
);
-
- act(() => {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- storeWithSqlLab.dispatch(
- addTable(
- { id: expectQueryEditorId } as any,
- unexpectedTable,
- expectCatalog,
- expectSchema,
- ) as any,
- );
- });
-
- await waitFor(() =>
- expect(result.current).toContainEqual(
- expect.objectContaining({
- name: unexpectedColumn,
- }),
- ),
- );
});
test('returns long keywords with detail', async () => {
diff --git a/superset-frontend/src/SqlLab/components/EditorWrapper/useKeywords.ts b/superset-frontend/src/SqlLab/components/EditorWrapper/useKeywords.ts
index ce1896e0886..fa1a1974be9 100644
--- a/superset-frontend/src/SqlLab/components/EditorWrapper/useKeywords.ts
+++ b/superset-frontend/src/SqlLab/components/EditorWrapper/useKeywords.ts
@@ -17,7 +17,7 @@
* under the License.
*/
import { useEffect, useMemo, useRef } from 'react';
-import { useSelector, useDispatch, shallowEqual, useStore } from 'react-redux';
+import { useDispatch, useStore } from 'react-redux';
import { t } from '@apache-superset/core/translation';
import { getExtensionsRegistry } from '@superset-ui/core';
@@ -30,15 +30,10 @@ import {
COLUMN_AUTOCOMPLETE_SCORE,
SQL_FUNCTIONS_AUTOCOMPLETE_SCORE,
} from 'src/SqlLab/constants';
-import {
- schemaEndpoints,
- tableEndpoints,
- skipToken,
-} from 'src/hooks/apiResources';
+import { schemaEndpoints } 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;
@@ -51,7 +46,6 @@ type Params = {
const EMPTY_LIST = [] as typeof sqlKeywords;
const { useQueryState: useSchemasQueryState } = schemaEndpoints.schemas;
-const { useQueryState: useTablesQueryState } = tableEndpoints.tables;
const getHelperText = (value: string) =>
value.length > 30 && {
@@ -87,16 +81,6 @@ export function useKeywords(
},
{ skip: skipFetch || !dbId },
);
- const { currentData: tableData } = useTablesQueryState(
- {
- dbId,
- catalog,
- schema,
- forceRefresh: false,
- },
- { skip: skipFetch || !dbId || !schema },
- );
-
const { currentData: functionNames, isError } = useDatabaseFunctionsQuery(
{ dbId },
{ skip: skipFetch || !dbId },
@@ -110,41 +94,64 @@ export function useKeywords(
}
}, [dispatch, isError]);
- const tablesForColumnMetadata = useSelector(
- ({ sqlLab }) =>
- skip
- ? []
- : (sqlLab?.tables ?? [])
- .filter(table => table.queryEditorId === queryEditorId)
- .map(table => table.name),
- shallowEqual,
- );
-
const store = useStore();
const apiState = store.getState()[api.reducerPath];
+ // Normalize catalog for comparison (null/undefined both mean "no catalog")
+ const normalizedCatalog = catalog ?? null;
+
+ // Collect all table names from all cached table-list queries for this database/catalog.
+ // This includes tables from any schema the user has expanded in the tree.
+ const allCachedTables = useMemo(() => {
+ if (skipFetch || !dbId || !apiState) return [];
+ const tables: { value: string; label: string; schema: string }[] = [];
+ const seen = new Set();
+ const queries = apiState.queries ?? {};
+ for (const entry of Object.values(queries) as any[]) {
+ const arg = entry?.originalArgs;
+ if (
+ arg?.dbId === dbId &&
+ (arg?.catalog ?? null) === normalizedCatalog &&
+ entry?.status === 'fulfilled' &&
+ entry?.data?.options
+ ) {
+ for (const table of entry.data.options) {
+ const key = `${arg.schema}.${table.value}`;
+ if (!seen.has(key)) {
+ seen.add(key);
+ tables.push({
+ value: table.value,
+ label: table.label ?? table.value,
+ schema: arg.schema,
+ });
+ }
+ }
+ }
+ }
+ return tables;
+ }, [dbId, normalizedCatalog, apiState, skipFetch]);
+
+ // Collect column names from all cached table-metadata queries for this database/catalog.
+ // This includes columns from any table the user has expanded in the tree.
const allColumns = useMemo(() => {
+ if (skipFetch || !dbId || !apiState) return [];
const columns = new Set();
- tablesForColumnMetadata.forEach(table => {
- tableEndpoints.tableMetadata
- .select(
- dbId && schema
- ? {
- dbId,
- catalog,
- schema,
- table,
- }
- : skipToken,
- )({
- [api.reducerPath]: apiState,
- })
- .data?.columns?.forEach(({ name }) => {
- columns.add(name);
- });
- });
+ const queries = apiState.queries ?? {};
+ for (const entry of Object.values(queries) as any[]) {
+ const arg = entry?.originalArgs;
+ if (
+ entry?.status === 'fulfilled' &&
+ entry?.data?.columns &&
+ arg?.dbId === dbId &&
+ (arg?.catalog ?? null) === normalizedCatalog
+ ) {
+ for (const col of entry.data.columns) {
+ columns.add(col.name);
+ }
+ }
+ }
return [...columns];
- }, [dbId, catalog, schema, apiState, tablesForColumnMetadata]);
+ }, [dbId, normalizedCatalog, apiState, skipFetch]);
const insertMatch = useEffectEvent((editor: Editor, data: any) => {
if (data.meta === 'table') {
@@ -153,7 +160,7 @@ export function useKeywords(
{ id: String(queryEditorId), dbId: dbId as number, tabViewId },
data.value,
catalog ?? null,
- schema ?? '',
+ data.schema ?? schema ?? '',
false, // Don't auto-expand/switch tabs when adding via autocomplete
),
);
@@ -187,9 +194,10 @@ export function useKeywords(
const tableKeywords = useMemo(
() =>
- (tableData?.options ?? []).map(({ value, label }) => ({
+ allCachedTables.map(({ value, label, schema: tableSchema }) => ({
name: label,
value,
+ schema: tableSchema,
score: TABLE_AUTOCOMPLETE_SCORE,
meta: 'table',
completer: {
@@ -197,7 +205,7 @@ export function useKeywords(
},
...getHelperText(value),
})),
- [tableData?.options, insertMatch],
+ [allCachedTables, insertMatch],
);
const columnKeywords = useMemo(
diff --git a/superset-frontend/src/SqlLab/components/QueryLimitSelect/index.tsx b/superset-frontend/src/SqlLab/components/QueryLimitSelect/index.tsx
index 9dadb22b091..4c2803b7753 100644
--- a/superset-frontend/src/SqlLab/components/QueryLimitSelect/index.tsx
+++ b/superset-frontend/src/SqlLab/components/QueryLimitSelect/index.tsx
@@ -89,7 +89,7 @@ const QueryLimitSelect = ({
>