blob: a81c32572b09806f33e557ad9aeefc593f4f9f9a [file] [log] [blame]
/**
* 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 { sqlLab as sqlLabType } from '@apache-superset/core';
import {
QUERY_FAILED,
QUERY_SUCCESS,
QUERY_EDITOR_SETDB,
querySuccess,
startQuery,
START_QUERY,
stopQuery,
STOP_QUERY,
createQueryFailedAction,
} from 'src/SqlLab/actions/sqlLab';
import { RootState, store } from 'src/views/store';
import { AnyListenerPredicate } from '@reduxjs/toolkit';
import memoizeOne from 'memoize-one';
import type { SqlLabRootState } from 'src/SqlLab/types';
import { Disposable } from '../models';
import { createActionListener } from '../utils';
import {
Panel,
Editor,
Tab,
QueryContext,
QueryResultContext,
QueryErrorResultContext,
} from './models';
const { CTASMethod } = sqlLabType;
const getSqlLabState = () => {
const { sqlLab }: { sqlLab: SqlLabRootState['sqlLab'] } = store.getState();
return sqlLab;
};
const activeEditorId = () => {
const { tabHistory } = getSqlLabState();
return tabHistory[tabHistory.length - 1];
};
const findQueryEditor = (editorId: string) => {
const { queryEditors } = getSqlLabState();
return queryEditors.find(editor => editor.id === editorId);
};
const createTab = (
id: string,
name: string,
sql: string,
dbId: number,
catalog?: string,
schema?: string,
table?: any,
) => {
const editor = new Editor(sql, dbId, catalog, schema, table);
const panels: Panel[] = []; // TODO: Populate panels
return new Tab(id, name, editor, panels);
};
const notImplemented = (): never => {
throw new Error('Not implemented yet');
};
function extractBaseData(action: any): {
baseParams: [string, Tab, boolean, number];
options: {
ctasMethod?: string;
tempTable?: string;
templateParams?: string;
requestedLimit?: number;
};
} {
const { query } = action;
const {
id,
sql,
startDttm,
runAsync,
dbId,
catalog,
schema,
sqlEditorId,
tab: tabName,
ctas_method: ctasMethod,
tempTable,
templateParams,
queryLimit,
} = query;
const tab = createTab(sqlEditorId, tabName, sql, dbId, catalog, schema);
return {
baseParams: [id, tab, runAsync ?? false, startDttm ?? 0],
options: {
ctasMethod,
tempTable,
templateParams,
requestedLimit: queryLimit,
},
};
}
function createQueryContext(
action: ReturnType<typeof startQuery> | ReturnType<typeof stopQuery>,
): QueryContext {
const { baseParams, options } = extractBaseData(action);
return new QueryContext(...baseParams, options);
}
function createQueryResultContext(
action: ReturnType<typeof querySuccess>,
): QueryResultContext {
const { baseParams, options } = extractBaseData(action);
const [_, tab] = baseParams;
const { results } = action;
const {
query_id: queryId,
columns,
data,
query: {
endDttm,
executedSql,
tempTable: resultTempTable,
limit,
limitingFactor,
},
} = results;
return new QueryResultContext(
...baseParams,
queryId,
executedSql ?? tab.editor.content,
columns,
data,
endDttm,
{
...options,
tempTable: resultTempTable || options.tempTable,
appliedLimit: limit,
appliedLimitingFactor: limitingFactor,
},
);
}
function createQueryErrorContext(
action: ReturnType<typeof createQueryFailedAction>,
): QueryErrorResultContext {
const { baseParams, options } = extractBaseData(action);
const { msg: errorMessage, errors, query } = action;
const { endDttm, executedSql, query_id: queryId } = query;
return new QueryErrorResultContext(...baseParams, errorMessage, errors, {
...options,
queryId,
executedSql: executedSql ?? null,
endDttm: endDttm ?? Date.now(),
});
}
const getCurrentTab: typeof sqlLabType.getCurrentTab = () => {
const queryEditor = findQueryEditor(activeEditorId());
if (queryEditor) {
const { id, name, sql, dbId, catalog, schema } = queryEditor;
return createTab(
id,
name,
sql,
dbId!,
catalog ?? undefined,
schema ?? undefined,
undefined,
);
}
return undefined;
};
const getActiveEditorImmutableId = () => {
const { tabHistory } = getSqlLabState();
const activeEditorId = tabHistory[tabHistory.length - 1];
const activeEditor = findQueryEditor(activeEditorId);
return activeEditor?.immutableId;
};
// Memoized version to avoid repeated store lookups when active editor hasn't changed
const getActiveEditorId = memoizeOne(getActiveEditorImmutableId);
const predicate = (actionType: string): AnyListenerPredicate<RootState> => {
// Capture the immutable ID of the active editor at the time the listener is created
// This ID never changes for a tab, ensuring stable event routing
const registrationImmutableId = getActiveEditorId();
return action => {
if (action.type !== actionType) return false;
// If we don't have a registration ID, don't filter events
if (!registrationImmutableId) return true;
// For query events, use the immutableId directly from the action payload
if (action.query?.immutableId) {
return action.query.immutableId === registrationImmutableId;
}
// For tab events, we need to find the immutable ID of the affected tab
if (action.queryEditor?.id) {
const queryEditor = findQueryEditor(action.queryEditor.id);
return queryEditor?.immutableId === registrationImmutableId;
}
// Fallback: do not allow the event if we can't determine the source
return false;
};
};
const onDidQueryRun: typeof sqlLabType.onDidQueryRun = (
listener: (queryContext: sqlLabType.QueryContext) => void,
thisArgs?: any,
): Disposable =>
createActionListener(
predicate(START_QUERY),
listener,
(action: ReturnType<typeof startQuery>) => createQueryContext(action),
thisArgs,
);
const onDidQuerySuccess: typeof sqlLabType.onDidQuerySuccess = (
listener: (queryResultContext: sqlLabType.QueryResultContext) => void,
thisArgs?: any,
): Disposable =>
createActionListener(
predicate(QUERY_SUCCESS),
listener,
(action: ReturnType<typeof querySuccess>) =>
createQueryResultContext(action),
thisArgs,
);
const onDidQueryStop: typeof sqlLabType.onDidQueryStop = (
listener: (queryContext: sqlLabType.QueryContext) => void,
thisArgs?: any,
): Disposable =>
createActionListener(
predicate(STOP_QUERY),
listener,
(action: ReturnType<typeof stopQuery>) => createQueryContext(action),
thisArgs,
);
const onDidQueryFail: typeof sqlLabType.onDidQueryFail = (
listener: (
queryErrorResultContext: sqlLabType.QueryErrorResultContext,
) => void,
thisArgs?: any,
): Disposable =>
createActionListener(
predicate(QUERY_FAILED),
listener,
(action: ReturnType<typeof createQueryFailedAction>) =>
createQueryErrorContext(action),
thisArgs,
);
const onDidChangeEditorDatabase: typeof sqlLabType.onDidChangeEditorDatabase = (
listener: (e: number) => void,
thisArgs?: any,
): Disposable =>
createActionListener(
predicate(QUERY_EDITOR_SETDB),
listener,
(action: { type: string; dbId?: number; queryEditor: { dbId: number } }) =>
action.dbId || action.queryEditor.dbId,
thisArgs,
);
const onDidChangeEditorContent: typeof sqlLabType.onDidChangeEditorContent =
notImplemented;
const onDidChangeEditorCatalog: typeof sqlLabType.onDidChangeEditorCatalog =
notImplemented;
const onDidChangeEditorSchema: typeof sqlLabType.onDidChangeEditorSchema =
notImplemented;
const onDidChangeEditorTable: typeof sqlLabType.onDidChangeEditorTable =
notImplemented;
const onDidClosePanel: typeof sqlLabType.onDidClosePanel = notImplemented;
const onDidChangeActivePanel: typeof sqlLabType.onDidChangeActivePanel =
notImplemented;
const onDidChangeTabTitle: typeof sqlLabType.onDidChangeTabTitle =
notImplemented;
const getDatabases: typeof sqlLabType.getDatabases = notImplemented;
const getTabs: typeof sqlLabType.getTabs = notImplemented;
const onDidCloseTab: typeof sqlLabType.onDidCloseTab = notImplemented;
const onDidChangeActiveTab: typeof sqlLabType.onDidChangeActiveTab =
notImplemented;
const onDidRefreshDatabases: typeof sqlLabType.onDidRefreshDatabases =
notImplemented;
const onDidRefreshCatalogs: typeof sqlLabType.onDidRefreshCatalogs =
notImplemented;
const onDidRefreshSchemas: typeof sqlLabType.onDidRefreshSchemas =
notImplemented;
const onDidRefreshTables: typeof sqlLabType.onDidRefreshTables = notImplemented;
export const sqlLab: typeof sqlLabType = {
CTASMethod,
getCurrentTab,
onDidChangeEditorContent,
onDidChangeEditorDatabase,
onDidChangeEditorCatalog,
onDidChangeEditorSchema,
onDidChangeEditorTable,
onDidClosePanel,
onDidChangeActivePanel,
onDidChangeTabTitle,
onDidQueryRun,
onDidQueryStop,
onDidQueryFail,
onDidQuerySuccess,
getDatabases,
getTabs,
onDidCloseTab,
onDidChangeActiveTab,
onDidRefreshDatabases,
onDidRefreshCatalogs,
onDidRefreshSchemas,
onDidRefreshTables,
};
// Export all models
export * from './models';