import {v4 as uuidv4} from 'uuid'; import type {Actions, ErrorResponse} from '../../../types/api/query'; import type {QueryAction, QueryMode, QuerySettings, QuerySyntax} from '../../../types/store/query'; import type { QueryResponseChunk, SessionChunk, StreamDataChunk, StreamingChunk, } from '../../../types/store/streaming'; import {valueIsDefined} from '../../../utils'; import {QUERY_ACTIONS, STATISTICS_MODES} from '../../../utils/query'; import type { PreparedQueryData, QueryExecutionStatusType, QueryInHistory, QueryTabState, RawQueryInHistory, } from './types'; const QUERY_ACTION_TO_BACKEND_BASE_ACTION: Record = { [QUERY_ACTIONS.execute]: 'execute', [QUERY_ACTIONS.explain]: 'explain', [QUERY_ACTIONS.explainAnalyze]: 'execute', }; export function getEffectiveQuerySettingsForAction( actionType: QueryAction, querySettings: Partial, ): Partial { if (actionType !== QUERY_ACTIONS.explainAnalyze) { return querySettings; } return { ...querySettings, statisticsMode: STATISTICS_MODES.full, }; } export function getEffectiveQueryDataForAction( actionType: QueryAction, data: PreparedQueryData, ): PreparedQueryData { if (actionType !== QUERY_ACTIONS.explainAnalyze) { return data; } return { ...data, resultSets: undefined, }; } export function getActionAndSyntaxFromQueryMode( baseAction: QueryAction = QUERY_ACTIONS.execute, queryMode: QueryMode = 'query', ) { const backendBaseAction = QUERY_ACTION_TO_BACKEND_BASE_ACTION[baseAction]; let action: Actions = backendBaseAction; const syntax: QuerySyntax = 'yql_v1'; if (queryMode) { action = `${backendBaseAction}-${queryMode}`; } return {action, syntax}; } const QUERY_EXECUTION_STATUS_VALUES: QueryExecutionStatusType[] = [ 'loading', 'completed', 'failed', 'stopped', 'aborted', ]; function isQueryExecutionStatusType(value: unknown): value is QueryExecutionStatusType { return ( typeof value === 'string' && QUERY_EXECUTION_STATUS_VALUES.includes(value as QueryExecutionStatusType) ); } export function getQueryInHistory(rawQuery: string | RawQueryInHistory): QueryInHistory { if (typeof rawQuery === 'string') { return { queryText: rawQuery, queryId: uuidv4(), }; } const enhancedQuery: QueryInHistory = { ...rawQuery, status: isQueryExecutionStatusType(rawQuery.status) ? rawQuery.status : undefined, }; if ( !valueIsDefined(enhancedQuery.startTime) && valueIsDefined(enhancedQuery.durationUs) && valueIsDefined(enhancedQuery.endTime) ) { enhancedQuery.startTime = Number(enhancedQuery.endTime) - Math.round(Number(enhancedQuery.durationUs) / 1000); } if (!enhancedQuery.queryId) { enhancedQuery.queryId = uuidv4(); } return enhancedQuery; } export function isSessionChunk(content: StreamingChunk): content is SessionChunk { return content?.meta?.event === 'SessionCreated'; } export function isStreamDataChunk(content: StreamingChunk): content is StreamDataChunk { return content?.meta?.event === 'StreamData'; } export function isQueryResponseChunk(content: StreamingChunk): content is QueryResponseChunk { return content?.meta?.event === 'QueryResponse'; } export function isKeepAliveChunk(content: StreamingChunk): content is SessionChunk { return content?.meta?.event === 'KeepAlive'; } export function isErrorChunk(content: unknown): content is ErrorResponse { return Boolean( content && typeof content === 'object' && ('error' in content || 'issues' in content), ); } interface PreparedQueryWithPragmasMetadata { query: string; preparedQueryPrefixLineCount: number; } export const prepareQueryWithPragmasMetadata = ( query: string, pragmas?: string, ): PreparedQueryWithPragmasMetadata => { if (!pragmas || !pragmas.trim()) { return {query, preparedQueryPrefixLineCount: 0}; } // Add pragmas at the beginning with proper line separation const trimmedPragmas = pragmas.trim(); const separator = trimmedPragmas.endsWith(';') ? '\n\n' : ';\n\n'; return { query: `${trimmedPragmas}${separator}${query}`, preparedQueryPrefixLineCount: trimmedPragmas.split('\n').length + 1, }; }; export const prepareQueryWithPragmas = (query: string, pragmas?: string): string => { return prepareQueryWithPragmasMetadata(query, pragmas).query; }; type UnknownRecord = Record; function isRecord(value: unknown): value is UnknownRecord { return typeof value === 'object' && value !== null && !Array.isArray(value); } function isStringArray(value: unknown): value is string[] { return Array.isArray(value) && value.every((item) => typeof item === 'string'); } export interface QueryTabPersistedState { id: string; title: string; isTitleUserDefined?: boolean; isTouched?: boolean; input: string; savedInput?: string; savedQueryName?: string; createdAt: number; updatedAt: number; } export interface QueryTabsPersistedState { activeTabId?: string; tabsOrder: string[]; tabsById: Record; newTabCounter?: number; } export type QueryTabsDirtyPersistedState = Record; function isQueryTabPersistedState(value: unknown): value is QueryTabPersistedState { if (!isRecord(value)) { return false; } if ('isTitleUserDefined' in value && typeof value.isTitleUserDefined !== 'boolean') { return false; } if ('isTouched' in value && typeof value.isTouched !== 'boolean') { return false; } if ('savedInput' in value && typeof value.savedInput !== 'string') { return false; } if ('savedQueryName' in value && typeof value.savedQueryName !== 'string') { return false; } return ( typeof value.id === 'string' && typeof value.title === 'string' && typeof value.input === 'string' && typeof value.createdAt === 'number' && typeof value.updatedAt === 'number' ); } export function isQueryTabsPersistedState(value: unknown): value is QueryTabsPersistedState { if (!isRecord(value)) { return false; } if ( ('activeTabId' in value && value.activeTabId !== undefined && typeof value.activeTabId !== 'string') || !isStringArray(value.tabsOrder) || !isRecord(value.tabsById) ) { return false; } return Object.values(value.tabsById).every(isQueryTabPersistedState); } export function isQueryTabsDirtyPersistedState( value: unknown, ): value is QueryTabsDirtyPersistedState { if (!isRecord(value)) { return false; } return Object.values(value).every((item) => typeof item === 'boolean'); } export function getUniqueTabTitle( tabsById: Record, baseTitle: string, ): string { const existingTitles = new Set(Object.values(tabsById).map((tab) => tab.title)); if (!existingTitles.has(baseTitle)) { return baseTitle; } let counter = 2; while (existingTitles.has(`${baseTitle} ${counter}`)) { counter++; } return `${baseTitle} ${counter}`; } export function createDefaultTabState({ tabId, title = '', input = '', pendingSnippet, savedQueryName, isTitleUserDefined = false, }: { tabId: string; title?: string; input?: string; pendingSnippet?: string; savedQueryName?: string; isTitleUserDefined?: boolean; }): QueryTabState { const now = Date.now(); return { id: tabId, title, isTitleUserDefined, input, savedInput: input, isDirty: false, createdAt: now, updatedAt: now, pendingSnippet, savedQueryName, }; } export function applyQueryContentToTab({ tab, title, input, pendingSnippet, savedQueryName, }: { tab: QueryTabState; title: string; input: string; pendingSnippet?: string; savedQueryName?: string; }): QueryTabState { return { ...tab, title, isTitleUserDefined: false, isTouched: undefined, input, savedInput: input, isDirty: false, result: undefined, lastExecutedQueryText: undefined, pendingSnippet, savedQueryName, updatedAt: Date.now(), }; }