From d6b99d4daeb4ff9726a8081edd557b0ebeab699c Mon Sep 17 00:00:00 2001 From: "J.C. Zhong" Date: Mon, 9 Dec 2024 22:59:06 +0000 Subject: [PATCH] comments --- .../CodeMirrorTooltip/TableColumnTooltip.tsx | 13 +-- .../extensions/useAutoCompleteExtension.ts | 94 +++++++++++-------- querybook/webapp/lib/sql-helper/sql-parser.ts | 36 +++---- 3 files changed, 76 insertions(+), 67 deletions(-) diff --git a/querybook/webapp/components/CodeMirrorTooltip/TableColumnTooltip.tsx b/querybook/webapp/components/CodeMirrorTooltip/TableColumnTooltip.tsx index 34ccda465..1d336dbcc 100644 --- a/querybook/webapp/components/CodeMirrorTooltip/TableColumnTooltip.tsx +++ b/querybook/webapp/components/CodeMirrorTooltip/TableColumnTooltip.tsx @@ -50,18 +50,13 @@ export const TableColumnTooltip: React.FunctionComponent = ({
{column.name}
{column.type && ( -
-
Type
+
+
Type:
{column.type}
)} {tagsDOM.length > 0 && ( -
-
Tags
-
-
{tagsDOM}
-
-
+
{tagsDOM}
)} {column.comment && (
@@ -75,7 +70,7 @@ export const TableColumnTooltip: React.FunctionComponent = ({
{description}
)} - {!!column?.stats?.length && ( + {statsDOM.length && (
Stats
{statsDOM}
diff --git a/querybook/webapp/hooks/queryEditor/extensions/useAutoCompleteExtension.ts b/querybook/webapp/hooks/queryEditor/extensions/useAutoCompleteExtension.ts index 478c8e0bc..80064ae10 100644 --- a/querybook/webapp/hooks/queryEditor/extensions/useAutoCompleteExtension.ts +++ b/querybook/webapp/hooks/queryEditor/extensions/useAutoCompleteExtension.ts @@ -2,11 +2,14 @@ import { autocompletion, Completion, CompletionContext, + CompletionResult, startCompletion, } from '@codemirror/autocomplete'; import { EditorView } from '@uiw/react-codemirror'; import { useCallback, useEffect, useMemo, useState } from 'react'; +import { CodeMirrorToken } from 'lib/codemirror/utils'; +import { IPosition } from 'lib/sql-helper/sql-lexer'; import { SqlParser } from 'lib/sql-helper/sql-parser'; export type AutoCompleteType = 'none' | 'schema' | 'all'; @@ -26,8 +29,7 @@ export const useAutoCompleteExtension = ({ const [typing, setTyping] = useState(false); const getColumnValueCompletions = useCallback( - (cursor, token) => { - console.log('getColumnValueCompletions', cursor, token); + (cursor: IPosition, token: CodeMirrorToken): CompletionResult => { const [textBeforeEqual, textAfterEqual] = token.text .split('=') .map((s) => s.trim()); @@ -49,40 +51,15 @@ export const useAutoCompleteExtension = ({ [sqlParserRef] ); - const getCompletions = useCallback( - async (context: CompletionContext) => { - if (type === 'none') { - return null; - } - - // Get the token before the cursor, token could be in below foramts - // - column: schema.table.column, table.column, column - // - table: schema.table, table - // - keyword: any keyword - // - column value: column = value, (value may be quoted) - const token = context.matchBefore( - /(\w+\.){0,2}\w*|(\w+.)?\s*=\s*'?\w*/ - ); - - // no token before the cursor, don't open completions. - if (!token?.text) return null; - - // Get the cursor position in codemirror v5 format - const cursorPos = context.pos; - const line = context.state.doc.lineAt(cursorPos); - const cursor = { line: line.number - 1, ch: cursorPos - line.from }; - + const getGeneralCompletions = useCallback( + async ( + context: string, + cursor: IPosition, + token: CodeMirrorToken + ): Promise => { const tokenText = token.text.toLowerCase(); - const sqlParserContext = - sqlParserRef.current.getContextAtPos(cursor); - - // handle the case where the token is a column and the user is trying to type a value in a where clause - if (sqlParserContext === 'column' && tokenText.includes('=')) { - return getColumnValueCompletions(cursor, token); - } - const options: Completion[] = []; - if (sqlParserContext === 'column') { + if (context === 'column') { const columns = sqlParserRef.current.getColumnMatches( cursor, token.text @@ -93,7 +70,7 @@ export const useAutoCompleteExtension = ({ detail: 'column', })) ); - } else if (sqlParserContext === 'table') { + } else if (context === 'table') { const tableNames = await sqlParserRef.current.getTableNameMatches(tokenText); options.push( @@ -118,13 +95,56 @@ export const useAutoCompleteExtension = ({ ); let from = token.from; - if (sqlParserContext === 'column') { + if (context === 'column') { from += token.text.lastIndexOf('.') + 1; } return { from, options }; }, - [sqlParserRef, type, getColumnValueCompletions] + [sqlParserRef, type] + ); + + const getCompletions = useCallback( + async (context: CompletionContext) => { + if (type === 'none') { + return null; + } + + // Get the token before the cursor, token could be in below foramts + // - column value: column = value (value may be quoted) + const columnValueRegex = /(\w+\.)?\w+\s*=\s*['"]?\w*/; + // - column: schema.table.column, table.column, column + // - table: schema.table, table + // - keyword: any keyword + const generalTokenRegex = /(\w+\.){0,2}\w*/; + + const columnValueToken = context.matchBefore(columnValueRegex); + const generalToken = + !columnValueToken && context.matchBefore(generalTokenRegex); + + // no token before the cursor, don't open completions. + if (!columnValueToken?.text && !generalToken.text) return null; + + // Get the cursor position in codemirror v5 format + const cursorPos = context.pos; + const line = context.state.doc.lineAt(cursorPos); + const cursor = { line: line.number - 1, ch: cursorPos - line.from }; + + const sqlParserContext = + sqlParserRef.current.getContextAtPos(cursor); + + // handle the case where the token is a column and the user is trying to type a value in a where clause + if (sqlParserContext === 'column' && columnValueToken?.text) { + return getColumnValueCompletions(cursor, columnValueToken); + } + + return getGeneralCompletions( + sqlParserContext, + cursor, + generalToken + ); + }, + [sqlParserRef, type, getColumnValueCompletions, getGeneralCompletions] ); const triggerCompletionOnType = () => { diff --git a/querybook/webapp/lib/sql-helper/sql-parser.ts b/querybook/webapp/lib/sql-helper/sql-parser.ts index ccaa8d6f4..b03fe5dfd 100644 --- a/querybook/webapp/lib/sql-helper/sql-parser.ts +++ b/querybook/webapp/lib/sql-helper/sql-parser.ts @@ -1,7 +1,7 @@ import { bind } from 'lodash-decorators'; import { IDataColumn } from 'const/metastore'; -import { ICodeAnalysis, TableToken } from 'lib/sql-helper/sql-lexer'; +import { ICodeAnalysis, IPosition, TableToken } from 'lib/sql-helper/sql-lexer'; import { getLanguageSetting, ILanguageSetting, @@ -59,7 +59,7 @@ export class SqlParser { * @param pos position of the cursor or mouse pointer * @returns string: 'table', 'column' or 'none' */ - public getContextAtPos(pos: { line: number; ch: number }): string { + public getContextAtPos(pos: IPosition): string { if (!this.codeAnalysis?.editorLines) { return 'none'; } @@ -77,7 +77,7 @@ export class SqlParser { * @param pos position of the cursor or mouse pointer * @returns TableToken if the cursor is on a table, otherwise null */ - public getTableAtPos(pos: { line: number; ch: number }): TableToken | null { + public getTableAtPos(pos: IPosition): TableToken | null { const { line, ch } = pos; if (this.codeAnalysis) { const tableReferences: TableToken[] = [].concat.apply( @@ -111,10 +111,7 @@ export class SqlParser { * @param text the token text before or at the cursor * @returns IDataColumn if the cursor is on a column, otherwise null */ - public getColumnAtPos( - pos: { line: number; ch: number }, - text: string - ): IDataColumn | null { + public getColumnAtPos(pos: IPosition, text: string): IDataColumn | null { const columns = this.getColumnMatches(pos, text, true); if (columns.length === 1) { return columns[0]; @@ -129,23 +126,20 @@ export class SqlParser { * @returns Array of column values if the cursor is on a column, otherwise empty array */ public getColumnValues( - cursor: { line: number; ch: number }, + cursor: IPosition, text: string ): Array { const columns = this.getColumnMatches(cursor, text, true); - if (columns.length === 1) { - const colStats = columns[0].stats ?? []; + if (columns.length !== 1) return []; - // find the stat with key="distinct_values" - const distinctValuesStat = colStats.find( - (stat) => stat.key === 'distinct_values' - ); - if (distinctValuesStat?.value instanceof Array) { - return distinctValuesStat?.value; - } - - return []; + const colStats = columns[0].stats ?? []; + // find the stat with key="distinct_values" + const distinctValuesStat = colStats.find( + (stat) => stat.key === 'distinct_values' + ); + if (distinctValuesStat?.value instanceof Array) { + return distinctValuesStat?.value; } return []; @@ -159,7 +153,7 @@ export class SqlParser { * @param exactMatch whether to do exact match or prefix match */ public getColumnMatches( - cursor: { line: number; ch: number }, + cursor: IPosition, text: string, exactMatch: boolean = false ): IDataColumn[] { @@ -255,7 +249,7 @@ export class SqlParser { }); } - private getLineAnalysis(cursor: { line: number; ch: number }) { + private getLineAnalysis(cursor: IPosition): ILineAnalysis { const lineAnalysis: ILineAnalysis = { context: 'none', alias: {},