From d0f001288b45d6eaf85e58d6be7bf763825859f3 Mon Sep 17 00:00:00 2001
From: Shenoy Pratik <sgguruda@amazon.com>
Date: Mon, 9 Dec 2024 13:41:24 -0800
Subject: [PATCH] [Bug] Fix SQL/PPL crash with incorrect query (#2284)

* fix SQL/PPL crash with incorrect query

Signed-off-by: Shenoy Pratik <sgguruda@amazon.com>

* add constants

Signed-off-by: Shenoy Pratik <sgguruda@amazon.com>

---------

Signed-off-by: Shenoy Pratik <sgguruda@amazon.com>
---
 .../notebooks_test/notebooks.spec.js          | 64 +++++++++++++++----
 .cypress/support/constants.js                 |  1 -
 .cypress/utils/constants.js                   | 11 +++-
 server/services/queryService.ts               |  2 +-
 4 files changed, 62 insertions(+), 16 deletions(-)

diff --git a/.cypress/integration/notebooks_test/notebooks.spec.js b/.cypress/integration/notebooks_test/notebooks.spec.js
index e9e75a4b9..1c1afac31 100644
--- a/.cypress/integration/notebooks_test/notebooks.spec.js
+++ b/.cypress/integration/notebooks_test/notebooks.spec.js
@@ -13,6 +13,8 @@ import {
   PPL_QUERY_TEXT,
   NOTEBOOK_TEXT,
   OPENSEARCH_URL,
+  PPL_INCORRECT_QUERY_TEXT,
+  SQL_INCORRECT_QUERY_TEXT,
 } from '../../utils/constants';
 
 import { v4 as uuid4 } from 'uuid';
@@ -74,7 +76,9 @@ describe('Testing notebooks table', () => {
     cy.get('h3[data-test-subj="notebookTableTitle"]').contains('Notebooks (0)').should('exist');
     cy.get('div[data-test-subj="notebookEmptyTableText"]').contains('No notebooks');
     cy.get('a[data-test-subj="notebookEmptyTableCreateBtn"]').contains('Create notebook');
-    cy.get('button[data-test-subj="notebookEmptyTableAddSamplesBtn"]').contains('Add sample notebooks');
+    cy.get('button[data-test-subj="notebookEmptyTableAddSamplesBtn"]').contains(
+      'Add sample notebooks'
+    );
   });
 
   it('Displays error toast for invalid notebook name', () => {
@@ -96,13 +100,9 @@ describe('Testing notebooks table', () => {
     cy.get('input.euiFieldSearch').focus().type('this notebook should not exist');
     cy.get('.euiTableCellContent__text').contains('No items found').should('exist');
     cy.get('.euiFormControlLayoutClearButton').click();
-    cy.get('input.euiFieldSearch')
-      .focus()
-      .type(TEST_NOTEBOOK);
+    cy.get('input.euiFieldSearch').focus().type(TEST_NOTEBOOK);
 
-    cy.get('a.euiLink')
-      .contains(TEST_NOTEBOOK)
-      .should('exist');
+    cy.get('a.euiLink').contains(TEST_NOTEBOOK).should('exist');
   });
 
   it('Notebooks table columns headers and pagination', () => {
@@ -295,6 +295,24 @@ describe('Testing paragraphs', () => {
       });
   });
 
+  it('Adds an incorrect SQL query paragraph', () => {
+    cy.get('button[data-test-subj="AddParagraphButton"]').click();
+    cy.get('button[data-test-subj="AddCodeBlockBtn"]').click();
+
+    cy.get('textarea[data-test-subj="editorArea-6"]').clear();
+    cy.get('textarea[data-test-subj="editorArea-6"]').focus();
+    cy.get('textarea[data-test-subj="editorArea-6"]').type(SQL_INCORRECT_QUERY_TEXT);
+    cy.get('button[data-test-subj="runRefreshBtn-6"]').click();
+
+    cy.get('textarea[data-test-subj="editorArea-6"]').should('exist');
+    cy.get('div[id$="-error-0"]')
+      .should('exist')
+      .and('have.class', 'euiFormErrorText')
+      .and('contain.text', 'Invalid SQL query');
+
+    cy.get('.euiDataGrid__overflow').should('exist');
+  });
+
   it('Adds an observability visualization paragraph', () => {
     cy.get('h3[data-test-subj="notebookTitle"]').contains(TEST_NOTEBOOK).should('exist');
     cy.get('button[data-test-subj="notebook-paragraph-actions-button"]').click();
@@ -309,7 +327,9 @@ describe('Testing paragraphs', () => {
     cy.get('input[data-test-subj="comboBoxSearchInput"]')
       .focus()
       .type('[Logs] Count total requests by t');
-    cy.get('.euiComboBoxOption__content').contains('[Logs] Count total requests by tags').click({ force: true });
+    cy.get('.euiComboBoxOption__content')
+      .contains('[Logs] Count total requests by tags')
+      .click({ force: true });
     cy.get('button[data-test-subj="runRefreshBtn-0"]').click();
     cy.get('h5').contains('[Logs] Count total requests by tags').should('exist');
   });
@@ -318,12 +338,12 @@ describe('Testing paragraphs', () => {
     cy.get('button[data-test-subj="AddParagraphButton"]').click();
     cy.get('button[data-test-subj="AddCodeBlockBtn"]').click();
 
-    cy.get('textarea[data-test-subj="editorArea-7"]').clear();
-    cy.get('textarea[data-test-subj="editorArea-7"]').focus();
-    cy.get('textarea[data-test-subj="editorArea-7"]').type(PPL_QUERY_TEXT);
-    cy.get('button[data-test-subj="runRefreshBtn-7"]').click();
+    cy.get('textarea[data-test-subj="editorArea-8"]').clear();
+    cy.get('textarea[data-test-subj="editorArea-8"]').focus();
+    cy.get('textarea[data-test-subj="editorArea-8"]').type(PPL_QUERY_TEXT);
+    cy.get('button[data-test-subj="runRefreshBtn-8"]').click();
 
-    cy.get('textarea[data-test-subj="editorArea-7"]').should('not.exist');
+    cy.get('textarea[data-test-subj="editorArea-8"]').should('not.exist');
     cy.get('div[data-test-subj="queryOutputText"]')
       .contains('source=opensearch_dashboards_sample_data_flights')
       .should('exist');
@@ -331,6 +351,24 @@ describe('Testing paragraphs', () => {
     cy.get('.euiDataGrid__overflow').should('exist');
   });
 
+  it('Adds an incorrect PPL query paragraph', () => {
+    cy.get('button[data-test-subj="AddParagraphButton"]').click();
+    cy.get('button[data-test-subj="AddCodeBlockBtn"]').click();
+
+    cy.get('textarea[data-test-subj="editorArea-9"]').clear();
+    cy.get('textarea[data-test-subj="editorArea-9"]').focus();
+    cy.get('textarea[data-test-subj="editorArea-9"]').type(PPL_INCORRECT_QUERY_TEXT);
+    cy.get('button[data-test-subj="runRefreshBtn-9"]').click();
+
+    cy.get('textarea[data-test-subj="editorArea-9"]').should('exist');
+    cy.get('div[id$="-error-0"]')
+      .should('exist')
+      .and('have.class', 'euiFormErrorText')
+      .and('contain.text', 'Error occurred in OpenSearch engine: no such index');
+
+    cy.get('.euiDataGrid__overflow').should('exist');
+  });
+
   it('Clears outputs', () => {
     cy.get('h3[data-test-subj="notebookTitle"]').contains(TEST_NOTEBOOK).should('exist');
     cy.get('.euiButton__text').contains('Clear all outputs').click();
diff --git a/.cypress/support/constants.js b/.cypress/support/constants.js
index d7978c222..1001fd49d 100644
--- a/.cypress/support/constants.js
+++ b/.cypress/support/constants.js
@@ -7,4 +7,3 @@ export const ADMIN_AUTH = {
   username: 'admin',
   password: 'admin',
 };
-
diff --git a/.cypress/utils/constants.js b/.cypress/utils/constants.js
index e6f5f48a8..c1e211d44 100644
--- a/.cypress/utils/constants.js
+++ b/.cypress/utils/constants.js
@@ -74,7 +74,9 @@ export const setTimeFilter = (setEndTime = false, refresh = true) => {
     .focus()
     .type('{selectall}' + startTime, { force: true });
   if (setEndTime) {
-    cy.get('button.euiDatePopoverButton--end[data-test-subj="superDatePickerendDatePopoverButton"]').click();
+    cy.get(
+      'button.euiDatePopoverButton--end[data-test-subj="superDatePickerendDatePopoverButton"]'
+    ).click();
     cy.get('.euiTab__content').contains('Absolute').click();
     cy.get('input[data-test-subj="superDatePickerAbsoluteDateInput"]')
       .focus()
@@ -128,10 +130,17 @@ export const SQL_QUERY_TEXT = `%sql
 select * from opensearch_dashboards_sample_data_flights limit 20 {enter}
 `;
 
+export const SQL_INCORRECT_QUERY_TEXT = `%sql 
+selectaaaaa * from opensearch_dashboards_sample_data_flights limit 20 {enter}
+`;
+
 export const PPL_QUERY_TEXT = `%ppl
 source=opensearch_dashboards_sample_data_flights {enter}
 `;
 
+export const PPL_INCORRECT_QUERY_TEXT = `%ppl
+source=opensearch_dashboards_sample_data_flights__ {enter}
+`;
 
 export const suppressResizeObserverIssue = () => {
   // exception is thrown on loading EuiDataGrid in cypress only, ignore for now
diff --git a/server/services/queryService.ts b/server/services/queryService.ts
index 2c8a5d004..55e330252 100644
--- a/server/services/queryService.ts
+++ b/server/services/queryService.ts
@@ -51,7 +51,7 @@ export class QueryService {
       return {
         data: {
           ok: false,
-          resp: err.message,
+          resp: err.response,
           body: err.body,
         },
       };