This is an automated email from the ASF dual-hosted git repository.

abhishek pushed a commit to branch 29.0.0
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/29.0.0 by this push:
     new 8f20c01082a Web console: Make table driven query modification actions 
work with slices. (#15779) (#15785)
8f20c01082a is described below

commit 8f20c01082aac04dea5d754984e1ae2a6a15d213
Author: Laksh Singla <[email protected]>
AuthorDate: Tue Jan 30 16:21:54 2024 +0530

    Web console: Make table driven query modification actions work with slices. 
(#15779) (#15785)
    
    * Make table driven query modification actions work with slices.
    
    * cleanup found query prefix
    
    * fix regex complexity
    
    Co-authored-by: Vadim Ogievetsky <[email protected]>
---
 web-console/src/utils/sql.spec.ts                  | 70 ++++++++++++----
 web-console/src/utils/sql.ts                       | 17 +++-
 .../views/workbench-view/query-tab/query-tab.tsx   | 97 ++++++++++++++--------
 .../result-table-pane/result-table-pane.tsx        | 41 +++++----
 4 files changed, 154 insertions(+), 71 deletions(-)

diff --git a/web-console/src/utils/sql.spec.ts 
b/web-console/src/utils/sql.spec.ts
index 9f107b1c45f..cdb6fabd800 100644
--- a/web-console/src/utils/sql.spec.ts
+++ b/web-console/src/utils/sql.spec.ts
@@ -85,6 +85,7 @@ describe('sql', () => {
               "column": 14,
               "row": 1,
             },
+            "index": 0,
             "sql": "SELECT *
         FROM wikipedia",
             "startOffset": 0,
@@ -99,6 +100,7 @@ describe('sql', () => {
               "column": 7,
               "row": 5,
             },
+            "index": 1,
             "sql": "SELECT *
         FROM w2
         LIMIT 5",
@@ -132,6 +134,7 @@ describe('sql', () => {
               "column": 15,
               "row": 5,
             },
+            "index": 0,
             "sql": "SELECT
           \\"channel\\",
           COUNT(*) AS \\"Count\\"
@@ -150,6 +153,7 @@ describe('sql', () => {
               "column": 31,
               "row": 3,
             },
+            "index": 1,
             "sql": "SELECT * FROM \\"wikipedia\\"",
             "startOffset": 48,
             "startRowColumn": Object {
@@ -184,6 +188,7 @@ describe('sql', () => {
               "column": 15,
               "row": 8,
             },
+            "index": 0,
             "sql": "WITH w1 AS (
           SELECT channel, page FROM \\"wikipedia\\"
         )
@@ -205,6 +210,7 @@ describe('sql', () => {
               "column": 39,
               "row": 1,
             },
+            "index": 1,
             "sql": "SELECT channel, page FROM \\"wikipedia\\"",
             "startOffset": 15,
             "startRowColumn": Object {
@@ -218,6 +224,7 @@ describe('sql', () => {
               "column": 15,
               "row": 8,
             },
+            "index": 2,
             "sql": "SELECT
           page,
           COUNT(*) AS \\"cnt\\"
@@ -234,8 +241,10 @@ describe('sql', () => {
       `);
     });
 
-    it('works with replace query', () => {
+    it('works with select query followed by a replace query', () => {
       const text = sane`
+        SELECT * FROM "wiki"
+
         REPLACE INTO "wikipedia" OVERWRITE ALL
         WITH "ext" AS (
           SELECT *
@@ -259,11 +268,26 @@ describe('sql', () => {
       expect(found).toMatchInlineSnapshot(`
         Array [
           Object {
-            "endOffset": 379,
+            "endOffset": 29,
+            "endRowColumn": Object {
+              "column": 7,
+              "row": 2,
+            },
+            "index": 0,
+            "sql": "SELECT * FROM \\"wiki\\"",
+            "startOffset": 0,
+            "startRowColumn": Object {
+              "column": 0,
+              "row": 0,
+            },
+          },
+          Object {
+            "endOffset": 401,
             "endRowColumn": Object {
               "column": 18,
-              "row": 15,
+              "row": 17,
             },
+            "index": 1,
             "sql": "REPLACE INTO \\"wikipedia\\" OVERWRITE ALL
         WITH \\"ext\\" AS (
           SELECT *
@@ -280,18 +304,19 @@ describe('sql', () => {
           \\"channel\\"
         FROM \\"ext\\"
         PARTITIONED BY DAY",
-            "startOffset": 0,
+            "startOffset": 22,
             "startRowColumn": Object {
               "column": 0,
-              "row": 0,
+              "row": 2,
             },
           },
           Object {
-            "endOffset": 360,
+            "endOffset": 382,
             "endRowColumn": Object {
               "column": 10,
-              "row": 14,
+              "row": 16,
             },
+            "index": 2,
             "sql": "WITH \\"ext\\" AS (
           SELECT *
           FROM TABLE(
@@ -306,18 +331,19 @@ describe('sql', () => {
           \\"isRobot\\",
           \\"channel\\"
         FROM \\"ext\\"",
-            "startOffset": 39,
+            "startOffset": 61,
             "startRowColumn": Object {
               "column": 0,
-              "row": 1,
+              "row": 3,
             },
           },
           Object {
-            "endOffset": 276,
+            "endOffset": 298,
             "endRowColumn": Object {
               "column": 70,
-              "row": 8,
+              "row": 10,
             },
+            "index": 3,
             "sql": "SELECT *
           FROM TABLE(
             EXTERN(
@@ -325,27 +351,28 @@ describe('sql', () => {
               '{\\"type\\":\\"json\\"}'
             )
           ) EXTEND (\\"isRobot\\" VARCHAR, \\"channel\\" VARCHAR, 
\\"timestamp\\" VARCHAR)",
-            "startOffset": 57,
+            "startOffset": 79,
             "startRowColumn": Object {
               "column": 2,
-              "row": 2,
+              "row": 4,
             },
           },
           Object {
-            "endOffset": 360,
+            "endOffset": 382,
             "endRowColumn": Object {
               "column": 10,
-              "row": 14,
+              "row": 16,
             },
+            "index": 4,
             "sql": "SELECT
           TIME_PARSE(\\"timestamp\\") AS \\"__time\\",
           \\"isRobot\\",
           \\"channel\\"
         FROM \\"ext\\"",
-            "startOffset": 279,
+            "startOffset": 301,
             "startRowColumn": Object {
               "column": 0,
-              "row": 10,
+              "row": 12,
             },
           },
         ]
@@ -384,6 +411,7 @@ describe('sql', () => {
               "column": 22,
               "row": 17,
             },
+            "index": 0,
             "sql": "EXPLAIN PLAN FOR
         INSERT INTO \\"wikipedia\\"
         WITH \\"ext\\" AS (
@@ -414,6 +442,7 @@ describe('sql', () => {
               "column": 22,
               "row": 17,
             },
+            "index": 1,
             "sql": "INSERT INTO \\"wikipedia\\"
         WITH \\"ext\\" AS (
           SELECT *
@@ -443,6 +472,7 @@ describe('sql', () => {
               "column": 10,
               "row": 15,
             },
+            "index": 2,
             "sql": "WITH \\"ext\\" AS (
           SELECT *
           FROM TABLE(
@@ -469,6 +499,7 @@ describe('sql', () => {
               "column": 70,
               "row": 9,
             },
+            "index": 3,
             "sql": "SELECT *
           FROM TABLE(
             EXTERN(
@@ -488,6 +519,7 @@ describe('sql', () => {
               "column": 10,
               "row": 15,
             },
+            "index": 4,
             "sql": "SELECT
           TIME_PARSE(\\"timestamp\\") AS \\"__time\\",
           \\"isRobot\\",
@@ -526,6 +558,7 @@ describe('sql', () => {
               "column": 14,
               "row": 2,
             },
+            "index": 0,
             "sql": "EXPLAIN PLAN FOR
         SELECT *
         FROM wikipedia",
@@ -541,6 +574,7 @@ describe('sql', () => {
               "column": 14,
               "row": 2,
             },
+            "index": 1,
             "sql": "SELECT *
         FROM wikipedia",
             "startOffset": 17,
@@ -555,6 +589,7 @@ describe('sql', () => {
               "column": 7,
               "row": 7,
             },
+            "index": 2,
             "sql": "EXPLAIN PLAN FOR
         SELECT *
         FROM w2
@@ -571,6 +606,7 @@ describe('sql', () => {
               "column": 7,
               "row": 7,
             },
+            "index": 3,
             "sql": "SELECT *
         FROM w2
         LIMIT 5",
diff --git a/web-console/src/utils/sql.ts b/web-console/src/utils/sql.ts
index b80c5ea457d..78e69a75a18 100644
--- a/web-console/src/utils/sql.ts
+++ b/web-console/src/utils/sql.ts
@@ -108,7 +108,21 @@ export function findSqlQueryPrefix(text: string): string | 
undefined {
   }
 }
 
+export function cleanSqlQueryPrefix(text: string): string {
+  const matchReplace = text.match(/\sREPLACE$/i);
+  if (matchReplace) {
+    // This query likely grabbed a "REPLACE" (which is not a reserved keyword) 
from the next query over, see if we can delete it
+    const textWithoutReplace = text.slice(0, 
-matchReplace[0].length).trimEnd();
+    if (SqlQuery.maybeParse(textWithoutReplace)) {
+      return textWithoutReplace;
+    }
+  }
+
+  return text;
+}
+
 export interface QuerySlice {
+  index: number;
   startOffset: number;
   startRowColumn: RowColumn;
   endOffset: number;
@@ -130,11 +144,12 @@ export function findAllSqlQueriesInText(text: string): 
QuerySlice[] {
       if (sql) {
         const endIndex = m.index + sql.length;
         found.push({
+          index: found.length,
           startOffset: offset + m.index,
           startRowColumn: offsetToRowColumn(text, offset + m.index)!,
           endOffset: offset + endIndex,
           endRowColumn: offsetToRowColumn(text, offset + endIndex)!,
-          sql,
+          sql: cleanSqlQueryPrefix(sql),
         });
       }
       remainingText = remainingText.slice(advanceBy);
diff --git a/web-console/src/views/workbench-view/query-tab/query-tab.tsx 
b/web-console/src/views/workbench-view/query-tab/query-tab.tsx
index 8a4129fc67b..8c7db042736 100644
--- a/web-console/src/views/workbench-view/query-tab/query-tab.tsx
+++ b/web-console/src/views/workbench-view/query-tab/query-tab.tsx
@@ -44,6 +44,7 @@ import { WorkbenchRunningPromises } from 
'../../../singletons/workbench-running-
 import type { ColumnMetadata, QueryAction, QuerySlice, RowColumn } from 
'../../../utils';
 import {
   DruidError,
+  findAllSqlQueriesInText,
   localStorageGet,
   LocalStorageKeys,
   localStorageSet,
@@ -122,15 +123,40 @@ export const QueryTab = React.memo(function 
QueryTab(props: QueryTabProps) {
     onQueryChange(query.changeQueryString(queryString));
   });
 
-  const parsedQuery = query.getParsedQuery();
-  const handleQueryAction = usePermanentCallback((queryAction: QueryAction) => 
{
-    if (!(parsedQuery instanceof SqlQuery)) return;
-    
onQueryChange(query.changeQueryString(parsedQuery.apply(queryAction).toString()));
-
-    if (shouldAutoRun()) {
-      setTimeout(() => void handleRun(false), 20);
-    }
-  });
+  const handleQueryAction = usePermanentCallback(
+    (queryAction: QueryAction, sliceIndex: number | undefined) => {
+      let newQueryString: string;
+      if (typeof sliceIndex === 'number') {
+        const { queryString } = query;
+        const foundQuery = findAllSqlQueriesInText(queryString)[sliceIndex];
+        if (!foundQuery) return;
+        const parsedQuery = SqlQuery.maybeParse(foundQuery.sql);
+        if (!parsedQuery) return;
+        newQueryString =
+          queryString.slice(0, foundQuery.startOffset) +
+          parsedQuery.apply(queryAction) +
+          queryString.slice(foundQuery.endOffset);
+      } else {
+        const parsedQuery = query.getParsedQuery();
+        if (!(parsedQuery instanceof SqlQuery)) return;
+        newQueryString = parsedQuery.apply(queryAction).toString();
+      }
+      onQueryChange(query.changeQueryString(newQueryString));
+
+      if (shouldAutoRun()) {
+        setTimeout(() => {
+          if (typeof sliceIndex === 'number') {
+            const slice = findAllSqlQueriesInText(newQueryString)[sliceIndex];
+            if (slice) {
+              void handleRun(false, slice);
+            }
+          } else {
+            void handleRun(false);
+          }
+        }, 20);
+      }
+    },
+  );
 
   function shouldAutoRun(): boolean {
     if (query.getEffectiveEngine() !== 'sql-native') return false;
@@ -296,38 +322,37 @@ export const QueryTab = React.memo(function 
QueryTab(props: QueryTabProps) {
     if (querySlice) {
       effectiveQuery = effectiveQuery
         .changeQueryString(querySlice.sql)
+        .changeQueryContext({ ...effectiveQuery.queryContext, sliceIndex: 
querySlice.index })
         .changePrefixLines(querySlice.startRowColumn.row);
     }
 
-    if (effectiveQuery.getEffectiveEngine() !== 'sql-msq-task') {
-      WorkbenchHistory.addQueryToHistory(effectiveQuery);
-      queryManager.runQuery(effectiveQuery);
-      return;
-    }
-
-    effectiveQuery = preview
-      ? effectiveQuery.makePreview()
-      : effectiveQuery.setMaxNumTasksIfUnset(clusterCapacity);
-
-    const capacityInfo = await maybeGetClusterCapacity();
-
-    const effectiveMaxNumTasks = effectiveQuery.queryContext.maxNumTasks ?? 2;
-    if (capacityInfo && capacityInfo.availableTaskSlots < 
effectiveMaxNumTasks) {
-      setAlertElement(
-        <CapacityAlert
-          maxNumTasks={effectiveMaxNumTasks}
-          capacityInfo={capacityInfo}
-          onRun={() => {
-            queryManager.runQuery(effectiveQuery);
-          }}
-          onClose={() => {
-            setAlertElement(undefined);
-          }}
-        />,
-      );
+    if (effectiveQuery.getEffectiveEngine() === 'sql-msq-task') {
+      effectiveQuery = preview
+        ? effectiveQuery.makePreview()
+        : effectiveQuery.setMaxNumTasksIfUnset(clusterCapacity);
+
+      const capacityInfo = await maybeGetClusterCapacity();
+
+      const effectiveMaxNumTasks = effectiveQuery.queryContext.maxNumTasks ?? 
2;
+      if (capacityInfo && capacityInfo.availableTaskSlots < 
effectiveMaxNumTasks) {
+        setAlertElement(
+          <CapacityAlert
+            maxNumTasks={effectiveMaxNumTasks}
+            capacityInfo={capacityInfo}
+            onRun={() => {
+              queryManager.runQuery(effectiveQuery);
+            }}
+            onClose={() => {
+              setAlertElement(undefined);
+            }}
+          />,
+        );
+        return;
+      }
     } else {
-      queryManager.runQuery(effectiveQuery);
+      WorkbenchHistory.addQueryToHistory(effectiveQuery);
     }
+    queryManager.runQuery(effectiveQuery);
   });
 
   const statsTaskId: string | undefined = execution?.id;
diff --git 
a/web-console/src/views/workbench-view/result-table-pane/result-table-pane.tsx 
b/web-console/src/views/workbench-view/result-table-pane/result-table-pane.tsx
index cb38f6e0ca6..492767b3eaf 100644
--- 
a/web-console/src/views/workbench-view/result-table-pane/result-table-pane.tsx
+++ 
b/web-console/src/views/workbench-view/result-table-pane/result-table-pane.tsx
@@ -44,6 +44,7 @@ import {
   columnToWidth,
   convertToGroupByExpression,
   copyAndAlert,
+  deepGet,
   filterMap,
   formatNumber,
   getNumericColumnBraces,
@@ -80,7 +81,7 @@ function getExpressionIfAlias(query: SqlQuery, selectIndex: 
number): string {
 
 export interface ResultTablePaneProps {
   queryResult: QueryResult;
-  onQueryAction(action: QueryAction): void;
+  onQueryAction(action: QueryAction, sliceIndex?: number): void;
   onExport?(): void;
   runeMode: boolean;
   initPageSize?: number;
@@ -103,6 +104,10 @@ export const ResultTablePane = React.memo(function 
ResultTablePane(props: Result
     });
   }, [queryResult.rows.length]);
 
+  function handleQueryAction(action: QueryAction) {
+    onQueryAction(action, deepGet(queryResult, 'query.context.sliceIndex'));
+  }
+
   function hasFilterOnHeader(header: string, headerIndex: number): boolean {
     if (!parsedQuery || 
!parsedQuery.isRealOutputColumnAtSelectIndex(headerIndex)) return false;
 
@@ -139,7 +144,7 @@ export const ResultTablePane = React.memo(function 
ResultTablePane(props: Result
             icon={reverseOrderByDirection === 'ASC' ? IconNames.SORT_ASC : 
IconNames.SORT_DESC}
             text={`Order ${reverseOrderByDirection === 'ASC' ? 'ascending' : 
'descending'}`}
             onClick={() => {
-              onQueryAction(q => q.changeOrderByExpressions([reverseOrderBy]));
+              handleQueryAction(q => 
q.changeOrderByExpressions([reverseOrderBy]));
             }}
           />,
         );
@@ -150,7 +155,7 @@ export const ResultTablePane = React.memo(function 
ResultTablePane(props: Result
             icon={IconNames.SORT_DESC}
             text="Order descending"
             onClick={() => {
-              onQueryAction(q => q.changeOrderByExpressions([descOrderBy]));
+              handleQueryAction(q => 
q.changeOrderByExpressions([descOrderBy]));
             }}
           />,
           <MenuItem
@@ -158,7 +163,7 @@ export const ResultTablePane = React.memo(function 
ResultTablePane(props: Result
             icon={IconNames.SORT_ASC}
             text="Order ascending"
             onClick={() => {
-              onQueryAction(q => q.changeOrderByExpressions([ascOrderBy]));
+              handleQueryAction(q => q.changeOrderByExpressions([ascOrderBy]));
             }}
           />,
         );
@@ -175,7 +180,7 @@ export const ResultTablePane = React.memo(function 
ResultTablePane(props: Result
               text="Remove cast"
               onClick={() => {
                 if (!selectExpression || !underlyingExpression) return;
-                onQueryAction(q =>
+                handleQueryAction(q =>
                   q.changeSelect(
                     headerIndex,
                     
underlyingExpression.getArg(0)!.as(selectExpression.getOutputName()),
@@ -196,7 +201,7 @@ export const ResultTablePane = React.memo(function 
ResultTablePane(props: Result
                   text={asType}
                   onClick={() => {
                     if (!selectExpression) return;
-                    onQueryAction(q =>
+                    handleQueryAction(q =>
                       q.changeSelect(
                         headerIndex,
                         selectExpression
@@ -238,7 +243,7 @@ export const ResultTablePane = React.memo(function 
ResultTablePane(props: Result
                     text={path}
                     onClick={() => {
                       if (!selectExpression) return;
-                      onQueryAction(q =>
+                      handleQueryAction(q =>
                         q.addSelect(
                           F('JSON_VALUE', 
selectExpression.getUnderlyingExpression(), path).as(
                             selectExpression.getOutputName() + 
path.replace(/^\$/, ''),
@@ -264,7 +269,7 @@ export const ResultTablePane = React.memo(function 
ResultTablePane(props: Result
               icon={IconNames.FILTER_REMOVE}
               text="Remove from WHERE clause"
               onClick={() => {
-                onQueryAction(q =>
+                handleQueryAction(q =>
                   
q.changeWhereExpression(whereExpression.removeColumnFromAnd(header)),
                 );
               }}
@@ -280,7 +285,7 @@ export const ResultTablePane = React.memo(function 
ResultTablePane(props: Result
               icon={IconNames.FILTER_REMOVE}
               text="Remove from HAVING clause"
               onClick={() => {
-                onQueryAction(q =>
+                handleQueryAction(q =>
                   
q.changeHavingExpression(havingExpression.removeColumnFromAnd(header)),
                 );
               }}
@@ -309,7 +314,7 @@ export const ResultTablePane = React.memo(function 
ResultTablePane(props: Result
               key="time_floor"
               expression={selectExpression}
               onChange={expression => {
-                onQueryAction(q => q.changeSelect(headerIndex, expression));
+                handleQueryAction(q => q.changeSelect(headerIndex, 
expression));
               }}
             />,
           );
@@ -320,7 +325,9 @@ export const ResultTablePane = React.memo(function 
ResultTablePane(props: Result
               icon={IconNames.TIME}
               text="Use as the primary time column"
               onClick={() => {
-                onQueryAction(q => q.changeSelect(headerIndex, 
selectExpression.as(TIME_COLUMN)));
+                handleQueryAction(q =>
+                  q.changeSelect(headerIndex, 
selectExpression.as(TIME_COLUMN)),
+                );
               }}
             />,
           );
@@ -341,7 +348,7 @@ export const ResultTablePane = React.memo(function 
ResultTablePane(props: Result
                 icon={IconNames.TIME}
                 text={`Time parse as '${possibleDruidFormat}' and use as the 
primary time column`}
                 onClick={() => {
-                  onQueryAction(q =>
+                  handleQueryAction(q =>
                     q.changeSelect(headerIndex, 
newSelectExpression.as(TIME_COLUMN)),
                   );
                 }}
@@ -352,7 +359,7 @@ export const ResultTablePane = React.memo(function 
ResultTablePane(props: Result
           if (parsedQuery.hasGroupBy()) {
             if (parsedQuery.isGroupedOutputColumn(header)) {
               const convertToAggregate = (aggregate: SqlExpression) => {
-                onQueryAction(q =>
+                handleQueryAction(q =>
                   q.removeOutputColumn(header).addSelect(aggregate, {
                     insertIndex: 'last',
                   }),
@@ -428,7 +435,7 @@ export const ResultTablePane = React.memo(function 
ResultTablePane(props: Result
                     icon={IconNames.EXCHANGE}
                     text="Convert to group by"
                     onClick={() => {
-                      onQueryAction(q =>
+                      handleQueryAction(q =>
                         
q.removeOutputColumn(header).addSelect(groupByExpression, {
                           insertIndex: 'last-grouping',
                           addToGroupBy: 'end',
@@ -450,7 +457,7 @@ export const ResultTablePane = React.memo(function 
ResultTablePane(props: Result
             icon={IconNames.CROSS}
             text="Remove column"
             onClick={() => {
-              onQueryAction(q => q.removeOutputColumn(header));
+              handleQueryAction(q => q.removeOutputColumn(header));
             }}
           />,
         );
@@ -506,7 +513,7 @@ export const ResultTablePane = React.memo(function 
ResultTablePane(props: Result
         headerIndex={headerIndex}
         runeMode={runeMode}
         query={parsedQuery}
-        onQueryAction={onQueryAction}
+        onQueryAction={handleQueryAction}
         onShowFullValue={setShowValue}
       />
     );
@@ -640,7 +647,7 @@ export const ResultTablePane = React.memo(function 
ResultTablePane(props: Result
           expression={editingExpression}
           onSave={newExpression => {
             if (!parsedQuery) return;
-            onQueryAction(q => q.changeSelect(editingColumn, newExpression));
+            handleQueryAction(q => q.changeSelect(editingColumn, 
newExpression));
           }}
           onClose={() => setEditingColumn(-1)}
         />


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to