From f24ecc2473b86b8f87f1e54e6d1b8a87383a1137 Mon Sep 17 00:00:00 2001 From: Sangeeth Sivan <74818788+berzerkeer@users.noreply.github.com> Date: Thu, 6 Jul 2023 13:50:58 +0530 Subject: [PATCH] feat: MySQL & MSSQL query generator (#24516) Co-authored-by: Aishwarya UR --- .../OneClickBinding/TableWidget/mysql_spec.ts | 108 ++++++++ .../Sanity/Datasources/MsSQL_Basic_Spec.ts | 108 +++++++- .../cypress/support/Pages/AggregateHelper.ts | 4 +- app/client/cypress/support/Pages/Table.ts | 2 + .../WidgetQueryGenerators/MSSQL/index.test.ts | 236 ++++++++++++++++++ .../src/WidgetQueryGenerators/MSSQL/index.ts | 227 +++++++++++++++++ .../WidgetQueryGenerators/MySQL/index.test.ts | 236 ++++++++++++++++++ .../src/WidgetQueryGenerators/MySQL/index.ts | 227 +++++++++++++++++ .../WidgetQueryGenerators/PostgreSQL/index.ts | 4 +- .../Snowflake/index.test.ts | 236 ++++++++++++++++++ .../WidgetQueryGenerators/Snowflake/index.ts | 231 +++++++++++++++++ app/client/src/WidgetQueryGenerators/index.ts | 6 + app/client/src/WidgetQueryGenerators/types.ts | 2 +- app/client/src/entities/Action/index.ts | 3 + 14 files changed, 1623 insertions(+), 7 deletions(-) create mode 100644 app/client/cypress/e2e/Regression/ClientSide/OneClickBinding/TableWidget/mysql_spec.ts create mode 100644 app/client/src/WidgetQueryGenerators/MSSQL/index.test.ts create mode 100644 app/client/src/WidgetQueryGenerators/MSSQL/index.ts create mode 100644 app/client/src/WidgetQueryGenerators/MySQL/index.test.ts create mode 100644 app/client/src/WidgetQueryGenerators/MySQL/index.ts create mode 100644 app/client/src/WidgetQueryGenerators/Snowflake/index.test.ts create mode 100644 app/client/src/WidgetQueryGenerators/Snowflake/index.ts diff --git a/app/client/cypress/e2e/Regression/ClientSide/OneClickBinding/TableWidget/mysql_spec.ts b/app/client/cypress/e2e/Regression/ClientSide/OneClickBinding/TableWidget/mysql_spec.ts new file mode 100644 index 000000000000..91de3394b3e9 --- /dev/null +++ b/app/client/cypress/e2e/Regression/ClientSide/OneClickBinding/TableWidget/mysql_spec.ts @@ -0,0 +1,108 @@ +import oneClickBindingLocator from "../../../../../locators/OneClickBindingLocator"; +import { + agHelper, + assertHelper, + dataSources, + draggableWidgets, + entityExplorer, + table, +} from "../../../../../support/Objects/ObjectsCore"; +import { OneClickBinding } from "../spec_utility"; + +const oneClickBinding = new OneClickBinding(); + +// TODO: Adds two rows on click of save row will debug and fix this in a different PR - Sangeeth +describe.skip("Table widget one click binding feature", () => { + it("1.should check that queries are created and bound to table widget properly", () => { + entityExplorer.DragDropWidgetNVerify(draggableWidgets.TABLE, 400); + + dataSources.CreateDataSource("MySql"); + + cy.get("@dsName").then((dsName) => { + entityExplorer.NavigateToSwitcher("Widgets"); + + entityExplorer.SelectEntityByName("Table1", "Widgets"); + + oneClickBinding.ChooseAndAssertForm( + `${dsName}`, + dsName, + "configs", + "configName", + ); + }); + + agHelper.GetNClick(oneClickBindingLocator.connectData); + + assertHelper.AssertNetworkStatus("@postExecute"); + + agHelper.Sleep(2000); + + [ + "id", + "configName", + "configJson", + "configVersion", + "updatedAt", + "updatedBy", + ].forEach((column) => { + agHelper.AssertElementExist(table._headerCell(column)); + }); + + agHelper.GetNClick(table._addNewRow, 0, true); + + table.EditTableCell(0, 1, "One Click Config", false); + + table.UpdateTableCell(0, 2, `{{}"key":"oneClick"}`); + table.UpdateTableCell(0, 3, 36); + table.UpdateTableCell(0, 4, "2023-07-03 15:30:00", false, true); + + agHelper.Sleep(2000); + + agHelper.GetNClick(table._saveNewRow, 0, true); + + assertHelper.AssertNetworkStatus("@postExecute"); + + agHelper.TypeText(table._searchInput, "One Click Config"); + + assertHelper.AssertNetworkStatus("@postExecute"); + + agHelper.AssertElementExist(table._bodyCell("One Click Config")); + + agHelper.Sleep(1000); + + table.EditTableCell(0, 1, "Bindings", false); + table.EditTableCell(0, 4, "2023-07-03 15:30:00", false); + + agHelper.Sleep(1000); + + (cy as any).AssertTableRowSavable(6, 0); + + (cy as any).saveTableRow(6, 0); + + assertHelper.AssertNetworkStatus("@postExecute"); + + assertHelper.AssertNetworkStatus("@postExecute"); + + agHelper.Sleep(500); + + agHelper.ClearTextField(table._searchInput); + + agHelper.TypeText(table._searchInput, "Bindings"); + + assertHelper.AssertNetworkStatus("@postExecute"); + + agHelper.Sleep(2000); + + agHelper.AssertElementExist(table._bodyCell("Bindings")); + + agHelper.ClearTextField(table._searchInput); + + agHelper.TypeText(table._searchInput, "One Click Config"); + + assertHelper.AssertNetworkStatus("@postExecute"); + + agHelper.Sleep(2000); + + agHelper.AssertElementAbsence(table._bodyCell("One Click Config")); + }); +}); diff --git a/app/client/cypress/e2e/Sanity/Datasources/MsSQL_Basic_Spec.ts b/app/client/cypress/e2e/Sanity/Datasources/MsSQL_Basic_Spec.ts index f93b5822b42e..4d8be29b27b8 100644 --- a/app/client/cypress/e2e/Sanity/Datasources/MsSQL_Basic_Spec.ts +++ b/app/client/cypress/e2e/Sanity/Datasources/MsSQL_Basic_Spec.ts @@ -1,11 +1,18 @@ import { agHelper, - entityExplorer, + assertHelper, propPane, dataSources, entityItems, + draggableWidgets, + entityExplorer, + table, } from "../../../support/Objects/ObjectsCore"; import { Widgets } from "../../../support/Pages/DataSources"; +import oneClickBindingLocator from "../../../locators/OneClickBindingLocator"; +import { OneClickBinding } from "../../Regression/ClientSide/OneClickBinding/spec_utility"; + +const oneClickBinding = new OneClickBinding(); describe("Validate MsSQL connection & basic querying with UI flows", () => { let dsName: any, @@ -54,7 +61,8 @@ describe("Validate MsSQL connection & basic querying with UI flows", () => { dataSources.RunQuery(); query = `CREATE TABLE Simpsons( - episode_id VARCHAR(7) NOT NULL PRIMARY KEY + id INT NOT NULL IDENTITY PRIMARY KEY + ,episode_id VARCHAR(7) ,season INTEGER NOT NULL ,episode INTEGER NOT NULL ,number_in_series INTEGER NOT NULL @@ -128,6 +136,102 @@ describe("Validate MsSQL connection & basic querying with UI flows", () => { }); }); + // TODO: This fails with `Invalid Object ` error. Looks like there needs to be a delay in query exectuion. Will debug and fix this in a different PR - Sangeeth + it.skip("3.One click binding - should check that queries are created and bound to table widget properly", () => { + entityExplorer.DragDropWidgetNVerify(draggableWidgets.TABLE, 450, 200); + + oneClickBinding.ChooseAndAssertForm(dsName, dsName, "Simpsons", "title"); + + agHelper.GetNClick(oneClickBindingLocator.connectData); + + assertHelper.AssertNetworkStatus("@postExecute"); + + agHelper.Sleep(2000); + + [ + "id", + "episode_id", + "season", + "episode", + "number_in_series", + "title", + "summary", + "air_date", + "episode_image", + "rating", + "votes", + ].forEach((column) => { + agHelper.AssertElementExist(table._headerCell(column)); + }); + + agHelper.GetNClick(table._addNewRow, 0, true); + + table.EditTableCell(0, 1, "S01E01", false); + + table.UpdateTableCell(0, 2, "1"); + + table.UpdateTableCell(0, 3, " 1"); + + table.UpdateTableCell(0, 4, " 10"); + + table.UpdateTableCell(0, 5, "Expanse"); + table.UpdateTableCell(0, 6, "Prime"); + + table.UpdateTableCell(0, 7, "2016-06-22 19:10:25-07", false, true); + agHelper.GetNClick(oneClickBindingLocator.dateInput, 0, true); + agHelper.GetNClick(oneClickBindingLocator.dayViewFromDate, 0, true); + table.UpdateTableCell(0, 8, "expanse.png", false, true); + table.UpdateTableCell(0, 9, "5"); + table.UpdateTableCell(0, 10, "20"); + + agHelper.GetNClick(table._saveNewRow, 0, true, 2000); + + assertHelper.AssertNetworkStatus("@postExecute"); + + agHelper.TypeText(table._searchInput, "Expanse"); + + assertHelper.AssertNetworkStatus("@postExecute"); + + agHelper.AssertElementExist(table._bodyCell("Expanse")); + + agHelper.Sleep(1000); + + table.EditTableCell(0, 5, "Westworld"); + + agHelper.Sleep(1000); + + (cy as any).AssertTableRowSavable(11, 0); + + (cy as any).saveTableRow(11, 0); + agHelper.Sleep(2000); + + assertHelper.AssertNetworkStatus("@postExecute"); + + assertHelper.AssertNetworkStatus("@postExecute"); + + agHelper.Sleep(500); + + agHelper.ClearTextField(table._searchInput); + + agHelper.TypeText(table._searchInput, "Westworld"); + + assertHelper.AssertNetworkStatus("@postExecute"); + + agHelper.Sleep(2000); + + agHelper.AssertElementExist(table._bodyCell("Westworld")); + + agHelper.ClearTextField(table._searchInput); + + agHelper.TypeText(table._searchInput, "Expanse"); + + assertHelper.AssertNetworkStatus("@postExecute"); + + agHelper.Sleep(2000); + + agHelper.AssertElementAbsence(table._bodyCell("Expanse")); + }); + after("Verify Deletion of the datasource", () => { entityExplorer.SelectEntityByName(dsName, "Datasources"); entityExplorer.ActionContextMenuByEntityName({ diff --git a/app/client/cypress/support/Pages/AggregateHelper.ts b/app/client/cypress/support/Pages/AggregateHelper.ts index 39ba576f0889..10a3bd71721b 100644 --- a/app/client/cypress/support/Pages/AggregateHelper.ts +++ b/app/client/cypress/support/Pages/AggregateHelper.ts @@ -1061,11 +1061,11 @@ export class AggregateHelper extends ReusableHelper { this.Sleep(500); //for value set to settle } - public UpdateInputValue(selector: string, value: string) { + public UpdateInputValue(selector: string, value: string, force = false) { this.GetElement(selector) .closest("input") .scrollIntoView({ easing: "linear" }) - .clear() + .clear({ force }) .then(($input: any) => { if (value !== "") { cy.wrap($input).type(value, { delay: 3 }); diff --git a/app/client/cypress/support/Pages/Table.ts b/app/client/cypress/support/Pages/Table.ts index 57f965ea03e1..ba3c8076f314 100644 --- a/app/client/cypress/support/Pages/Table.ts +++ b/app/client/cypress/support/Pages/Table.ts @@ -606,12 +606,14 @@ export class Table { colIndex: number, newValue: "" | number | string, toSaveNewValue = false, + force = false, ) { this.agHelper.UpdateInputValue( this._tableRow(rowIndex, colIndex, "v2") + " " + this._editCellEditorInput, newValue.toString(), + force, ); toSaveNewValue && this.agHelper.TypeText(this._editCellEditorInput, "{enter}", 0, true); diff --git a/app/client/src/WidgetQueryGenerators/MSSQL/index.test.ts b/app/client/src/WidgetQueryGenerators/MSSQL/index.test.ts new file mode 100644 index 000000000000..5110e568f19d --- /dev/null +++ b/app/client/src/WidgetQueryGenerators/MSSQL/index.test.ts @@ -0,0 +1,236 @@ +import MSSQL from "."; + +describe("MSSQL WidgetQueryGenerator", () => { + const initialValues = { + actionConfiguration: { + pluginSpecifiedTemplates: [{ value: true }], + }, + }; + test("should build select form data correctly", () => { + const expr = MSSQL.build( + { + select: { + limit: "data_table.pageSize", + where: 'data_table.searchText || ""', + offset: "(data_table.pageNo - 1) * data_table.pageSize", + orderBy: "data_table.sortOrder.column", + sortOrder: "data_table.sortOrder.order || 'ASC'", + }, + totalRecord: false, + }, + { + tableName: "someTable", + datasourceId: "someId", + aliases: [{ name: "someColumn1", alias: "someColumn1" }], + widgetId: "someWidgetId", + searchableColumn: "title", + columns: [], + primaryColumn: "genres", + }, + initialValues, + ); + + const res = `SELECT + * +FROM + someTable +WHERE + title LIKE '%{{data_table.searchText || \"\"}}%' +ORDER BY + {{data_table.sortOrder.column || 'genres'}} {{data_table.sortOrder.order || 'ASC' ? \"\" : \"DESC\"}} +OFFSET + {{(data_table.pageNo - 1) * data_table.pageSize}} ROWS +FETCH NEXT + {{data_table.pageSize}} ROWS ONLY`; + + expect(expr).toEqual([ + { + name: "Select_someTable", + type: "select", + dynamicBindingPathList: [ + { + key: "body", + }, + ], + payload: { + pluginSpecifiedTemplates: [{ value: false }], + body: res, + }, + }, + ]); + }); + + test("should build select form data correctly without primary column", () => { + const expr = MSSQL.build( + { + select: { + limit: "data_table.pageSize", + where: 'data_table.searchText || ""', + offset: "(data_table.pageNo - 1) * data_table.pageSize", + orderBy: "data_table.sortOrder.column", + sortOrder: `data_table.sortOrder.order !== "desc"`, + }, + totalRecord: false, + }, + { + tableName: "someTable", + datasourceId: "someId", + aliases: [{ name: "someColumn1", alias: "someColumn1" }], + widgetId: "someWidgetId", + searchableColumn: "title", + columns: [], + primaryColumn: "", + }, + initialValues, + ); + + const res = `SELECT + * +FROM + someTable +WHERE + title LIKE '%{{data_table.searchText || \"\"}}%' {{data_table.sortOrder.column ? \"ORDER BY \" + data_table.sortOrder.column + \" \" + (data_table.sortOrder.order !== \"desc\" ? \"\" : \"DESC\") : \"\"}} +OFFSET + {{(data_table.pageNo - 1) * data_table.pageSize}} ROWS +FETCH NEXT + {{data_table.pageSize}} ROWS ONLY`; + + expect(expr).toEqual([ + { + name: "Select_someTable", + type: "select", + dynamicBindingPathList: [ + { + key: "body", + }, + ], + payload: { + pluginSpecifiedTemplates: [{ value: false }], + body: res, + }, + }, + ]); + }); + + test("should not build update form data without primary key ", () => { + const expr = MSSQL.build( + { + update: { + value: `update_form.fieldState'`, + where: `"id" = {{data_table.selectedRow.id}}`, + }, + totalRecord: false, + }, + { + tableName: "someTable", + datasourceId: "someId", + aliases: [{ name: "someColumn1", alias: "someColumn1" }], + widgetId: "someWidgetId", + searchableColumn: "title", + columns: ["id", "name"], + primaryColumn: "", + }, + initialValues, + ); + + expect(expr).toEqual([]); + }); + + test("should build update form data correctly ", () => { + const expr = MSSQL.build( + { + update: { + value: `update_form.fieldState'`, + where: `data_table.selectedRow`, + }, + totalRecord: false, + }, + { + tableName: "someTable", + datasourceId: "someId", + aliases: [{ name: "someColumn1", alias: "someColumn1" }], + widgetId: "someWidgetId", + searchableColumn: "title", + columns: ["id", "name"], + primaryColumn: "id", + }, + initialValues, + ); + + expect(expr).toEqual([ + { + name: "Update_someTable", + type: "update", + dynamicBindingPathList: [ + { + key: "body", + }, + ], + payload: { + body: "UPDATE someTable SET name= '{{update_form.fieldState'.name}}' WHERE id= '{{data_table.selectedRow.id}}';", + pluginSpecifiedTemplates: [{ value: false }], + }, + }, + ]); + }); + + test("should not build insert form data without primary key ", () => { + const expr = MSSQL.build( + { + create: { + value: `update_form.fieldState`, + }, + totalRecord: false, + }, + { + tableName: "someTable", + datasourceId: "someId", + // ignore columns + aliases: [{ name: "someColumn1", alias: "someColumn1" }], + widgetId: "someWidgetId", + searchableColumn: "title", + columns: ["id", "name"], + primaryColumn: "", + }, + initialValues, + ); + expect(expr).toEqual([]); + }); + + test("should build insert form data correctly ", () => { + const expr = MSSQL.build( + { + create: { + value: `update_form.fieldState`, + }, + totalRecord: false, + }, + { + tableName: "someTable", + datasourceId: "someId", + // ignore columns + aliases: [{ name: "someColumn1", alias: "someColumn1" }], + widgetId: "someWidgetId", + searchableColumn: "title", + columns: ["id", "name"], + primaryColumn: "id", + }, + initialValues, + ); + expect(expr).toEqual([ + { + name: "Insert_someTable", + type: "create", + dynamicBindingPathList: [ + { + key: "body", + }, + ], + payload: { + body: "INSERT INTO someTable (name) VALUES ('{{update_form.fieldState.name}}')", + pluginSpecifiedTemplates: [{ value: false }], + }, + }, + ]); + }); +}); diff --git a/app/client/src/WidgetQueryGenerators/MSSQL/index.ts b/app/client/src/WidgetQueryGenerators/MSSQL/index.ts new file mode 100644 index 000000000000..b93aec4356a8 --- /dev/null +++ b/app/client/src/WidgetQueryGenerators/MSSQL/index.ts @@ -0,0 +1,227 @@ +import { BaseQueryGenerator } from "../BaseQueryGenerator"; +import { format } from "sql-formatter"; +import { QUERY_TYPE } from "../types"; +import type { + WidgetQueryGenerationConfig, + WidgetQueryGenerationFormConfig, + ActionConfigurationSQL, +} from "../types"; +import { removeSpecialChars } from "utils/helpers"; +import without from "lodash/without"; +export default abstract class MSSQL extends BaseQueryGenerator { + private static buildSelect( + widgetConfig: WidgetQueryGenerationConfig, + formConfig: WidgetQueryGenerationFormConfig, + ) { + const { select } = widgetConfig; + //if no table name do not build query + if (!select || !formConfig.tableName) { + return; + } + + const { limit, offset, orderBy, sortOrder, where } = select; + const querySegments = [ + { + isValuePresent: formConfig.tableName, + template: "SELECT * FROM ?", + params: [formConfig.tableName], + }, + { + isValuePresent: formConfig.searchableColumn && where, + template: "WHERE ? LIKE ?", + params: [formConfig.searchableColumn, `'%{{${where}}}%'`], + }, + formConfig.primaryColumn + ? { + isValuePresent: orderBy, + template: `ORDER BY ? ?`, + params: [ + `{{${orderBy} || '${formConfig.primaryColumn}'}}`, + `{{${sortOrder} ? "" : "DESC"}}`, + ], + } + : { + isValuePresent: orderBy, + template: "?", + params: [ + `{{${orderBy} ? "ORDER BY " + ${orderBy} + " " + (${sortOrder} ? "" : "DESC") : ""}}`, + ], + }, + { + isValuePresent: offset, + template: "OFFSET ? ROWS", + params: [`{{${offset}}}`], + }, + { + isValuePresent: limit, + template: "FETCH NEXT ? ROWS ONLY", + params: [`{{${limit}}}`], + }, + ]; + + const { params, template } = querySegments + // Filter out query segments which are not defined + .filter(({ isValuePresent }) => !!isValuePresent) + .reduce( + (acc, curr) => { + const { params, template } = curr; + return { + template: acc.template + " " + template, + params: [...acc.params, ...params], + }; + }, + { template: "", params: [] } as { template: string; params: string[] }, + ); + + //formats sql string + const res = format(template, { + params, + language: "sql", + }); + + return { + type: QUERY_TYPE.SELECT, + name: `Select_${removeSpecialChars(formConfig.tableName)}`, + payload: { + body: res, + }, + dynamicBindingPathList: [ + { + key: "body", + }, + ], + }; + } + + private static buildUpdate( + widgetConfig: WidgetQueryGenerationConfig, + formConfig: WidgetQueryGenerationFormConfig, + ) { + const { update } = widgetConfig; + //if no table name do not build query + if (!update || !update.where || !formConfig.tableName) { + return; + } + + const { value, where } = update; + + const columns = without(formConfig.columns, formConfig.primaryColumn); + + return { + type: QUERY_TYPE.UPDATE, + name: `Update_${removeSpecialChars(formConfig.tableName)}`, + payload: { + body: `UPDATE ${formConfig.tableName} SET ${columns + .map((column) => `${column}= '{{${value}.${column}}}'`) + .join(", ")} WHERE ${formConfig.primaryColumn}= '{{${where}.${ + formConfig.primaryColumn + }}}';`, + }, + dynamicBindingPathList: [ + { + key: "body", + }, + ], + }; + } + + private static buildInsert( + widgetConfig: WidgetQueryGenerationConfig, + formConfig: WidgetQueryGenerationFormConfig, + ) { + const { create } = widgetConfig; + //if no table name do not build query + if (!create || !create.value || !formConfig.tableName) { + return; + } + + const columns = without(formConfig.columns, formConfig.primaryColumn); + + return { + type: QUERY_TYPE.CREATE, + name: `Insert_${removeSpecialChars(formConfig.tableName)}`, + payload: { + body: `INSERT INTO ${formConfig.tableName} (${columns.map( + (a) => `${a}`, + )}) VALUES (${columns + .map((d) => `'{{${create.value}.${d}}}'`) + .toString()})`, + }, + dynamicBindingPathList: [ + { + key: "body", + }, + ], + }; + } + + private static buildTotal( + widgetConfig: WidgetQueryGenerationConfig, + formConfig: WidgetQueryGenerationFormConfig, + ) { + const { select, totalRecord } = widgetConfig; + //if no table name do not build query + if (!totalRecord) { + return; + } + + return { + type: QUERY_TYPE.TOTAL_RECORD, + name: `Total_record_${removeSpecialChars(formConfig.tableName)}`, + payload: { + body: `SELECT COUNT(*) from ${formConfig.tableName}${ + formConfig.searchableColumn + ? ` WHERE ${formConfig.searchableColumn} LIKE '%{{${select?.where}}}%'` + : "" + };`, + }, + dynamicBindingPathList: [ + { + key: "body", + }, + ], + }; + } + + public static build( + widgetConfig: WidgetQueryGenerationConfig, + formConfig: WidgetQueryGenerationFormConfig, + pluginInitalValues: { actionConfiguration: ActionConfigurationSQL }, + ) { + const allBuildConfigs = []; + if (widgetConfig.select) { + allBuildConfigs.push(this.buildSelect(widgetConfig, formConfig)); + } + + if (widgetConfig.update && formConfig.primaryColumn) { + allBuildConfigs.push(this.buildUpdate(widgetConfig, formConfig)); + } + + if (widgetConfig.create && formConfig.primaryColumn) { + allBuildConfigs.push(this.buildInsert(widgetConfig, formConfig)); + } + + if (widgetConfig.totalRecord) { + allBuildConfigs.push(this.buildTotal(widgetConfig, formConfig)); + } + + return allBuildConfigs + .filter((val) => !!val) + .map((val) => ({ + ...val, + payload: { + ...(val?.payload || {}), + ...(pluginInitalValues?.actionConfiguration || {}), + pluginSpecifiedTemplates: [ + { + value: false, + }, + ], + }, + })); + } + + static getTotalRecordExpression(binding: string) { + return `${binding}[0].count`; + } +} diff --git a/app/client/src/WidgetQueryGenerators/MySQL/index.test.ts b/app/client/src/WidgetQueryGenerators/MySQL/index.test.ts new file mode 100644 index 000000000000..140dfbb5c87b --- /dev/null +++ b/app/client/src/WidgetQueryGenerators/MySQL/index.test.ts @@ -0,0 +1,236 @@ +import MySQl from "."; + +describe("MySQl WidgetQueryGenerator", () => { + const initialValues = { + actionConfiguration: { + pluginSpecifiedTemplates: [{ value: true }], + }, + }; + test("should build select form data correctly", () => { + const expr = MySQl.build( + { + select: { + limit: "data_table.pageSize", + where: 'data_table.searchText || ""', + offset: "(data_table.pageNo - 1) * data_table.pageSize", + orderBy: "data_table.sortOrder.column", + sortOrder: "data_table.sortOrder.order || 'ASC'", + }, + totalRecord: false, + }, + { + tableName: "someTable", + datasourceId: "someId", + aliases: [{ name: "someColumn1", alias: "someColumn1" }], + widgetId: "someWidgetId", + searchableColumn: "title", + columns: [], + primaryColumn: "genres", + }, + initialValues, + ); + + const res = `SELECT + * +FROM + someTable +WHERE + title LIKE '%{{data_table.searchText || \"\"}}%' +ORDER BY + {{data_table.sortOrder.column || 'genres'}} {{data_table.sortOrder.order || 'ASC' ? \"\" : \"DESC\"}} +LIMIT + {{data_table.pageSize}} +OFFSET + {{(data_table.pageNo - 1) * data_table.pageSize}}`; + + expect(expr).toEqual([ + { + name: "Select_someTable", + type: "select", + dynamicBindingPathList: [ + { + key: "body", + }, + ], + payload: { + pluginSpecifiedTemplates: [{ value: false }], + body: res, + }, + }, + ]); + }); + + test("should build select form data correctly without primary column", () => { + const expr = MySQl.build( + { + select: { + limit: "data_table.pageSize", + where: 'data_table.searchText || ""', + offset: "(data_table.pageNo - 1) * data_table.pageSize", + orderBy: "data_table.sortOrder.column", + sortOrder: `data_table.sortOrder.order !== "desc"`, + }, + totalRecord: false, + }, + { + tableName: "someTable", + datasourceId: "someId", + aliases: [{ name: "someColumn1", alias: "someColumn1" }], + widgetId: "someWidgetId", + searchableColumn: "title", + columns: [], + primaryColumn: "", + }, + initialValues, + ); + + const res = `SELECT + * +FROM + someTable +WHERE + title LIKE '%{{data_table.searchText || \"\"}}%' {{data_table.sortOrder.column ? \"ORDER BY \" + data_table.sortOrder.column + \" \" + (data_table.sortOrder.order !== \"desc\" ? \"\" : \"DESC\") : \"\"}} +LIMIT + {{data_table.pageSize}} +OFFSET + {{(data_table.pageNo - 1) * data_table.pageSize}}`; + + expect(expr).toEqual([ + { + name: "Select_someTable", + type: "select", + dynamicBindingPathList: [ + { + key: "body", + }, + ], + payload: { + pluginSpecifiedTemplates: [{ value: false }], + body: res, + }, + }, + ]); + }); + + test("should not build update form data without primary key ", () => { + const expr = MySQl.build( + { + update: { + value: `update_form.fieldState'`, + where: `"id" = {{data_table.selectedRow.id}}`, + }, + totalRecord: false, + }, + { + tableName: "someTable", + datasourceId: "someId", + aliases: [{ name: "someColumn1", alias: "someColumn1" }], + widgetId: "someWidgetId", + searchableColumn: "title", + columns: ["id", "name"], + primaryColumn: "", + }, + initialValues, + ); + + expect(expr).toEqual([]); + }); + + test("should build update form data correctly ", () => { + const expr = MySQl.build( + { + update: { + value: `update_form.fieldState'`, + where: `data_table.selectedRow`, + }, + totalRecord: false, + }, + { + tableName: "someTable", + datasourceId: "someId", + aliases: [{ name: "someColumn1", alias: "someColumn1" }], + widgetId: "someWidgetId", + searchableColumn: "title", + columns: ["id", "name"], + primaryColumn: "id", + }, + initialValues, + ); + + expect(expr).toEqual([ + { + name: "Update_someTable", + type: "update", + dynamicBindingPathList: [ + { + key: "body", + }, + ], + payload: { + body: "UPDATE someTable SET name= '{{update_form.fieldState'.name}}' WHERE id= '{{data_table.selectedRow.id}}';", + pluginSpecifiedTemplates: [{ value: false }], + }, + }, + ]); + }); + + test("should not build insert form data without primary key ", () => { + const expr = MySQl.build( + { + create: { + value: `update_form.fieldState`, + }, + totalRecord: false, + }, + { + tableName: "someTable", + datasourceId: "someId", + // ignore columns + aliases: [{ name: "someColumn1", alias: "someColumn1" }], + widgetId: "someWidgetId", + searchableColumn: "title", + columns: ["id", "name"], + primaryColumn: "", + }, + initialValues, + ); + expect(expr).toEqual([]); + }); + + test("should build insert form data correctly ", () => { + const expr = MySQl.build( + { + create: { + value: `update_form.fieldState`, + }, + totalRecord: false, + }, + { + tableName: "someTable", + datasourceId: "someId", + // ignore columns + aliases: [{ name: "someColumn1", alias: "someColumn1" }], + widgetId: "someWidgetId", + searchableColumn: "title", + columns: ["id", "name"], + primaryColumn: "id", + }, + initialValues, + ); + expect(expr).toEqual([ + { + name: "Insert_someTable", + type: "create", + dynamicBindingPathList: [ + { + key: "body", + }, + ], + payload: { + body: "INSERT INTO someTable (name) VALUES ('{{update_form.fieldState.name}}')", + pluginSpecifiedTemplates: [{ value: false }], + }, + }, + ]); + }); +}); diff --git a/app/client/src/WidgetQueryGenerators/MySQL/index.ts b/app/client/src/WidgetQueryGenerators/MySQL/index.ts new file mode 100644 index 000000000000..7af5151bad83 --- /dev/null +++ b/app/client/src/WidgetQueryGenerators/MySQL/index.ts @@ -0,0 +1,227 @@ +import { BaseQueryGenerator } from "../BaseQueryGenerator"; +import { format } from "sql-formatter"; +import { QUERY_TYPE } from "../types"; +import type { + WidgetQueryGenerationConfig, + WidgetQueryGenerationFormConfig, + ActionConfigurationSQL, +} from "../types"; +import { removeSpecialChars } from "utils/helpers"; +import without from "lodash/without"; +export default abstract class MySQL extends BaseQueryGenerator { + private static buildSelect( + widgetConfig: WidgetQueryGenerationConfig, + formConfig: WidgetQueryGenerationFormConfig, + ) { + const { select } = widgetConfig; + //if no table name do not build query + if (!select || !formConfig.tableName) { + return; + } + + const { limit, offset, orderBy, sortOrder, where } = select; + const querySegments = [ + { + isValuePresent: formConfig.tableName, + template: "SELECT * FROM ?", + params: [formConfig.tableName], + }, + { + isValuePresent: formConfig.searchableColumn && where, + template: "WHERE ? LIKE ?", + params: [formConfig.searchableColumn, `'%{{${where}}}%'`], + }, + formConfig.primaryColumn + ? { + isValuePresent: orderBy, + template: `ORDER BY ? ?`, + params: [ + `{{${orderBy} || '${formConfig.primaryColumn}'}}`, + `{{${sortOrder} ? "" : "DESC"}}`, + ], + } + : { + isValuePresent: orderBy, + template: "?", + params: [ + `{{${orderBy} ? "ORDER BY " + ${orderBy} + " " + (${sortOrder} ? "" : "DESC") : ""}}`, + ], + }, + { + isValuePresent: limit, + template: "LIMIT ?", + params: [`{{${limit}}}`], + }, + { + isValuePresent: offset, + template: "OFFSET ?", + params: [`{{${offset}}}`], + }, + ]; + + const { params, template } = querySegments + // Filter out query segments which are not defined + .filter(({ isValuePresent }) => !!isValuePresent) + .reduce( + (acc, curr) => { + const { params, template } = curr; + return { + template: acc.template + " " + template, + params: [...acc.params, ...params], + }; + }, + { template: "", params: [] } as { template: string; params: string[] }, + ); + + //formats sql string + const res = format(template, { + params, + language: "mysql", + }); + + return { + type: QUERY_TYPE.SELECT, + name: `Select_${removeSpecialChars(formConfig.tableName)}`, + payload: { + body: res, + }, + dynamicBindingPathList: [ + { + key: "body", + }, + ], + }; + } + + private static buildUpdate( + widgetConfig: WidgetQueryGenerationConfig, + formConfig: WidgetQueryGenerationFormConfig, + ) { + const { update } = widgetConfig; + //if no table name do not build query + if (!update || !update.where || !formConfig.tableName) { + return; + } + + const { value, where } = update; + + const columns = without(formConfig.columns, formConfig.primaryColumn); + + return { + type: QUERY_TYPE.UPDATE, + name: `Update_${removeSpecialChars(formConfig.tableName)}`, + payload: { + body: `UPDATE ${formConfig.tableName} SET ${columns + .map((column) => `${column}= '{{${value}.${column}}}'`) + .join(", ")} WHERE ${formConfig.primaryColumn}= '{{${where}.${ + formConfig.primaryColumn + }}}';`, + }, + dynamicBindingPathList: [ + { + key: "body", + }, + ], + }; + } + + private static buildInsert( + widgetConfig: WidgetQueryGenerationConfig, + formConfig: WidgetQueryGenerationFormConfig, + ) { + const { create } = widgetConfig; + //if no table name do not build query + if (!create || !create.value || !formConfig.tableName) { + return; + } + + const columns = without(formConfig.columns, formConfig.primaryColumn); + + return { + type: QUERY_TYPE.CREATE, + name: `Insert_${removeSpecialChars(formConfig.tableName)}`, + payload: { + body: `INSERT INTO ${formConfig.tableName} (${columns.map( + (a) => `${a}`, + )}) VALUES (${columns + .map((d) => `'{{${create.value}.${d}}}'`) + .toString()})`, + }, + dynamicBindingPathList: [ + { + key: "body", + }, + ], + }; + } + + private static buildTotal( + widgetConfig: WidgetQueryGenerationConfig, + formConfig: WidgetQueryGenerationFormConfig, + ) { + const { select, totalRecord } = widgetConfig; + //if no table name do not build query + if (!totalRecord) { + return; + } + + return { + type: QUERY_TYPE.TOTAL_RECORD, + name: `Total_record_${removeSpecialChars(formConfig.tableName)}`, + payload: { + body: `SELECT COUNT(*) from ${formConfig.tableName}${ + formConfig.searchableColumn + ? ` WHERE ${formConfig.searchableColumn} LIKE '%{{${select?.where}}}%'` + : "" + };`, + }, + dynamicBindingPathList: [ + { + key: "body", + }, + ], + }; + } + + public static build( + widgetConfig: WidgetQueryGenerationConfig, + formConfig: WidgetQueryGenerationFormConfig, + pluginInitalValues: { actionConfiguration: ActionConfigurationSQL }, + ) { + const allBuildConfigs = []; + if (widgetConfig.select) { + allBuildConfigs.push(this.buildSelect(widgetConfig, formConfig)); + } + + if (widgetConfig.update && formConfig.primaryColumn) { + allBuildConfigs.push(this.buildUpdate(widgetConfig, formConfig)); + } + + if (widgetConfig.create && formConfig.primaryColumn) { + allBuildConfigs.push(this.buildInsert(widgetConfig, formConfig)); + } + + if (widgetConfig.totalRecord) { + allBuildConfigs.push(this.buildTotal(widgetConfig, formConfig)); + } + + return allBuildConfigs + .filter((val) => !!val) + .map((val) => ({ + ...val, + payload: { + ...(val?.payload || {}), + ...(pluginInitalValues?.actionConfiguration || {}), + pluginSpecifiedTemplates: [ + { + value: false, + }, + ], + }, + })); + } + + static getTotalRecordExpression(binding: string) { + return `${binding}[0].count`; + } +} diff --git a/app/client/src/WidgetQueryGenerators/PostgreSQL/index.ts b/app/client/src/WidgetQueryGenerators/PostgreSQL/index.ts index c5457c56537c..fd8bb825581f 100644 --- a/app/client/src/WidgetQueryGenerators/PostgreSQL/index.ts +++ b/app/client/src/WidgetQueryGenerators/PostgreSQL/index.ts @@ -4,7 +4,7 @@ import { QUERY_TYPE } from "../types"; import type { WidgetQueryGenerationConfig, WidgetQueryGenerationFormConfig, - ActionConfigurationPostgreSQL, + ActionConfigurationSQL, } from "../types"; import { removeSpecialChars } from "utils/helpers"; export default abstract class PostgreSQL extends BaseQueryGenerator { @@ -190,7 +190,7 @@ export default abstract class PostgreSQL extends BaseQueryGenerator { public static build( widgetConfig: WidgetQueryGenerationConfig, formConfig: WidgetQueryGenerationFormConfig, - pluginInitalValues: { actionConfiguration: ActionConfigurationPostgreSQL }, + pluginInitalValues: { actionConfiguration: ActionConfigurationSQL }, ) { const allBuildConfigs = []; if (widgetConfig.select) { diff --git a/app/client/src/WidgetQueryGenerators/Snowflake/index.test.ts b/app/client/src/WidgetQueryGenerators/Snowflake/index.test.ts new file mode 100644 index 000000000000..f4c79c542c7f --- /dev/null +++ b/app/client/src/WidgetQueryGenerators/Snowflake/index.test.ts @@ -0,0 +1,236 @@ +import Snowflake from "."; + +describe("Snowflake WidgetQueryGenerator", () => { + const initialValues = { + actionConfiguration: { + pluginSpecifiedTemplates: [{ value: true }], + }, + }; + test("should build select form data correctly", () => { + const expr = Snowflake.build( + { + select: { + limit: "data_table.pageSize", + where: 'data_table.searchText || ""', + offset: "(data_table.pageNo - 1) * data_table.pageSize", + orderBy: "data_table.sortOrder.column", + sortOrder: "data_table.sortOrder.order || 'ASC'", + }, + totalRecord: false, + }, + { + tableName: "someTable", + datasourceId: "someId", + aliases: [{ name: "someColumn1", alias: "someColumn1" }], + widgetId: "someWidgetId", + searchableColumn: "title", + columns: [], + primaryColumn: "genres", + }, + initialValues, + ); + + const res = `SELECT + * +FROM + someTable +WHERE + title LIKE '%{{data_table.searchText || \"\"}}%' +ORDER BY + {{data_table.sortOrder.column || 'genres'}} {{data_table.sortOrder.order || 'ASC' ? \"\" : \"DESC\"}} +LIMIT + {{data_table.pageSize}} +OFFSET + {{(data_table.pageNo - 1) * data_table.pageSize}}`; + + expect(expr).toEqual([ + { + name: "Select_someTable", + type: "select", + dynamicBindingPathList: [ + { + key: "body", + }, + ], + payload: { + pluginSpecifiedTemplates: [{ value: false }], + body: res, + }, + }, + ]); + }); + + test("should build select form data correctly without primary column", () => { + const expr = Snowflake.build( + { + select: { + limit: "data_table.pageSize", + where: 'data_table.searchText || ""', + offset: "(data_table.pageNo - 1) * data_table.pageSize", + orderBy: "data_table.sortOrder.column", + sortOrder: `data_table.sortOrder.order !== "desc"`, + }, + totalRecord: false, + }, + { + tableName: "someTable", + datasourceId: "someId", + aliases: [{ name: "someColumn1", alias: "someColumn1" }], + widgetId: "someWidgetId", + searchableColumn: "title", + columns: [], + primaryColumn: "", + }, + initialValues, + ); + + const res = `SELECT + * +FROM + someTable +WHERE + title LIKE '%{{data_table.searchText || \"\"}}%' {{data_table.sortOrder.column ? \"ORDER BY \" + data_table.sortOrder.column + \" \" + (data_table.sortOrder.order !== \"desc\" ? \"\" : \"DESC\") : \"\"}} +LIMIT + {{data_table.pageSize}} +OFFSET + {{(data_table.pageNo - 1) * data_table.pageSize}}`; + + expect(expr).toEqual([ + { + name: "Select_someTable", + type: "select", + dynamicBindingPathList: [ + { + key: "body", + }, + ], + payload: { + pluginSpecifiedTemplates: [{ value: false }], + body: res, + }, + }, + ]); + }); + + test("should not build update form data without primary key ", () => { + const expr = Snowflake.build( + { + update: { + value: `update_form.fieldState'`, + where: `"id" = {{data_table.selectedRow.id}}`, + }, + totalRecord: false, + }, + { + tableName: "someTable", + datasourceId: "someId", + aliases: [{ name: "someColumn1", alias: "someColumn1" }], + widgetId: "someWidgetId", + searchableColumn: "title", + columns: ["id", "name"], + primaryColumn: "", + }, + initialValues, + ); + + expect(expr).toEqual([]); + }); + + test("should build update form data correctly ", () => { + const expr = Snowflake.build( + { + update: { + value: `update_form.fieldState'`, + where: `data_table.selectedRow`, + }, + totalRecord: false, + }, + { + tableName: "someTable", + datasourceId: "someId", + aliases: [{ name: "someColumn1", alias: "someColumn1" }], + widgetId: "someWidgetId", + searchableColumn: "title", + columns: ["id", "name"], + primaryColumn: "id", + }, + initialValues, + ); + + expect(expr).toEqual([ + { + name: "Update_someTable", + type: "update", + dynamicBindingPathList: [ + { + key: "body", + }, + ], + payload: { + body: "UPDATE someTable SET name= '{{update_form.fieldState'.name}}' WHERE id= '{{data_table.selectedRow.id}}';", + pluginSpecifiedTemplates: [{ value: false }], + }, + }, + ]); + }); + + test("should not build insert form data without primary key ", () => { + const expr = Snowflake.build( + { + create: { + value: `update_form.fieldState`, + }, + totalRecord: false, + }, + { + tableName: "someTable", + datasourceId: "someId", + // ignore columns + aliases: [{ name: "someColumn1", alias: "someColumn1" }], + widgetId: "someWidgetId", + searchableColumn: "title", + columns: ["id", "name"], + primaryColumn: "", + }, + initialValues, + ); + expect(expr).toEqual([]); + }); + + test("should build insert form data correctly ", () => { + const expr = Snowflake.build( + { + create: { + value: `update_form.fieldState`, + }, + totalRecord: false, + }, + { + tableName: "someTable", + datasourceId: "someId", + // ignore columns + aliases: [{ name: "someColumn1", alias: "someColumn1" }], + widgetId: "someWidgetId", + searchableColumn: "title", + columns: ["id", "name"], + primaryColumn: "id", + }, + initialValues, + ); + expect(expr).toEqual([ + { + name: "Insert_someTable", + type: "create", + dynamicBindingPathList: [ + { + key: "body", + }, + ], + payload: { + body: "INSERT INTO someTable (name) VALUES ('{{update_form.fieldState.name}}')", + pluginSpecifiedTemplates: [{ value: false }], + }, + }, + ]); + }); +}); diff --git a/app/client/src/WidgetQueryGenerators/Snowflake/index.ts b/app/client/src/WidgetQueryGenerators/Snowflake/index.ts new file mode 100644 index 000000000000..1e4892e7eb4f --- /dev/null +++ b/app/client/src/WidgetQueryGenerators/Snowflake/index.ts @@ -0,0 +1,231 @@ +import { BaseQueryGenerator } from "../BaseQueryGenerator"; +import { format } from "sql-formatter"; +import { QUERY_TYPE } from "../types"; +import type { + WidgetQueryGenerationConfig, + WidgetQueryGenerationFormConfig, + ActionConfigurationSQL, +} from "../types"; +import { removeSpecialChars } from "utils/helpers"; +import { without } from "workers/common/JSLibrary/lodash-wrapper"; + +export default abstract class Snowflake extends BaseQueryGenerator { + private static buildSelect( + widgetConfig: WidgetQueryGenerationConfig, + formConfig: WidgetQueryGenerationFormConfig, + ) { + const { select } = widgetConfig; + //if no table name do not build query + if (!select || !formConfig.tableName) { + return; + } + + const { limit, offset, orderBy, sortOrder, where } = select; + const querySegments = [ + { + isValuePresent: formConfig.tableName, + template: "SELECT * FROM ?", + params: [formConfig.tableName], + }, + { + isValuePresent: formConfig.searchableColumn && where, + template: "WHERE ? LIKE ?", + params: [formConfig.searchableColumn, `'%{{${where}}}%'`], + }, + formConfig.primaryColumn + ? { + isValuePresent: orderBy, + template: `ORDER BY ? ?`, + params: [ + `{{${orderBy} || '${formConfig.primaryColumn}'}}`, + `{{${sortOrder} ? "" : "DESC"}}`, + ], + } + : { + isValuePresent: orderBy, + template: "?", + params: [ + `{{${orderBy} ? "ORDER BY " + ${orderBy} + " " + (${sortOrder} ? "" : "DESC") : ""}}`, + ], + }, + { + isValuePresent: limit, + template: "LIMIT ?", + params: [`{{${limit}}}`], + }, + { + isValuePresent: offset, + template: "OFFSET ?", + params: [`{{${offset}}}`], + }, + ]; + + const { params, template } = querySegments + // Filter out query segments which are not defined + .filter(({ isValuePresent }) => !!isValuePresent) + .reduce( + (acc, curr) => { + const { params, template } = curr; + return { + template: acc.template + " " + template, + params: [...acc.params, ...params], + }; + }, + { template: "", params: [] } as { template: string; params: string[] }, + ); + + //formats sql string + const res = format(template, { + params, + language: "snowflake", + paramTypes: { + positional: true, + }, + }); + + return { + type: QUERY_TYPE.SELECT, + name: `Select_${removeSpecialChars(formConfig.tableName)}`, + payload: { + body: res, + }, + dynamicBindingPathList: [ + { + key: "body", + }, + ], + }; + } + + private static buildUpdate( + widgetConfig: WidgetQueryGenerationConfig, + formConfig: WidgetQueryGenerationFormConfig, + ) { + const { update } = widgetConfig; + //if no table name do not build query + if (!update || !update.where || !formConfig.tableName) { + return; + } + + const { value, where } = update; + + const columns = without(formConfig.columns, formConfig.primaryColumn); + + return { + type: QUERY_TYPE.UPDATE, + name: `Update_${removeSpecialChars(formConfig.tableName)}`, + payload: { + body: `UPDATE ${formConfig.tableName} SET ${columns + .map((column) => `${column}= '{{${value}.${column}}}'`) + .join(", ")} WHERE ${formConfig.primaryColumn}= '{{${where}.${ + formConfig.primaryColumn + }}}';`, + }, + dynamicBindingPathList: [ + { + key: "body", + }, + ], + }; + } + + private static buildInsert( + widgetConfig: WidgetQueryGenerationConfig, + formConfig: WidgetQueryGenerationFormConfig, + ) { + const { create } = widgetConfig; + //if no table name do not build query + if (!create || !create.value || !formConfig.tableName) { + return; + } + + const columns = without(formConfig.columns, formConfig.primaryColumn); + + return { + type: QUERY_TYPE.CREATE, + name: `Insert_${removeSpecialChars(formConfig.tableName)}`, + payload: { + body: `INSERT INTO ${formConfig.tableName} (${columns.map( + (a) => `${a}`, + )}) VALUES (${columns + .map((d) => `'{{${create.value}.${d}}}'`) + .toString()})`, + }, + dynamicBindingPathList: [ + { + key: "body", + }, + ], + }; + } + + private static buildTotal( + widgetConfig: WidgetQueryGenerationConfig, + formConfig: WidgetQueryGenerationFormConfig, + ) { + const { select, totalRecord } = widgetConfig; + //if no table name do not build query + if (!totalRecord) { + return; + } + + return { + type: QUERY_TYPE.TOTAL_RECORD, + name: `Total_record_${removeSpecialChars(formConfig.tableName)}`, + payload: { + body: `SELECT COUNT(*) from ${formConfig.tableName}${ + formConfig.searchableColumn + ? ` WHERE ${formConfig.searchableColumn} LIKE '%{{${select?.where}}}%'` + : "" + };`, + }, + dynamicBindingPathList: [ + { + key: "body", + }, + ], + }; + } + + public static build( + widgetConfig: WidgetQueryGenerationConfig, + formConfig: WidgetQueryGenerationFormConfig, + pluginInitalValues: { actionConfiguration: ActionConfigurationSQL }, + ) { + const allBuildConfigs = []; + if (widgetConfig.select) { + allBuildConfigs.push(this.buildSelect(widgetConfig, formConfig)); + } + + if (widgetConfig.update && formConfig.primaryColumn) { + allBuildConfigs.push(this.buildUpdate(widgetConfig, formConfig)); + } + + if (widgetConfig.create && formConfig.primaryColumn) { + allBuildConfigs.push(this.buildInsert(widgetConfig, formConfig)); + } + + if (widgetConfig.totalRecord) { + allBuildConfigs.push(this.buildTotal(widgetConfig, formConfig)); + } + + return allBuildConfigs + .filter((val) => !!val) + .map((val) => ({ + ...val, + payload: { + ...(val?.payload || {}), + ...(pluginInitalValues?.actionConfiguration || {}), + pluginSpecifiedTemplates: [ + { + value: false, + }, + ], + }, + })); + } + + static getTotalRecordExpression(binding: string) { + return `${binding}[0].count`; + } +} diff --git a/app/client/src/WidgetQueryGenerators/index.ts b/app/client/src/WidgetQueryGenerators/index.ts index 37d52b807439..e9073f33b276 100644 --- a/app/client/src/WidgetQueryGenerators/index.ts +++ b/app/client/src/WidgetQueryGenerators/index.ts @@ -3,7 +3,13 @@ import WidgetQueryGeneratorRegistry from "utils/WidgetQueryGeneratorRegistry"; import GSheets from "./GSheets"; import MongoDB from "./MongoDB"; import PostgreSQL from "./PostgreSQL"; +import MySQL from "./MySQL"; +import MsSQL from "./MSSQL"; +import Snowflake from "./Snowflake"; WidgetQueryGeneratorRegistry.register(PluginPackageName.MONGO, MongoDB); WidgetQueryGeneratorRegistry.register(PluginPackageName.POSTGRES, PostgreSQL); WidgetQueryGeneratorRegistry.register(PluginPackageName.GOOGLE_SHEETS, GSheets); +WidgetQueryGeneratorRegistry.register(PluginPackageName.MY_SQL, MySQL); +WidgetQueryGeneratorRegistry.register(PluginPackageName.MS_SQL, MsSQL); +WidgetQueryGeneratorRegistry.register(PluginPackageName.SNOWFLAKE, Snowflake); diff --git a/app/client/src/WidgetQueryGenerators/types.ts b/app/client/src/WidgetQueryGenerators/types.ts index fcc588ac175e..25434b6e6e15 100644 --- a/app/client/src/WidgetQueryGenerators/types.ts +++ b/app/client/src/WidgetQueryGenerators/types.ts @@ -60,7 +60,7 @@ export type ActionConfigurationMongoDB = { formData: MongoDBFormData; }; -export type ActionConfigurationPostgreSQL = { +export type ActionConfigurationSQL = { pluginSpecifiedTemplates: Array; }; diff --git a/app/client/src/entities/Action/index.ts b/app/client/src/entities/Action/index.ts index f210bcf41580..558a9c174218 100644 --- a/app/client/src/entities/Action/index.ts +++ b/app/client/src/entities/Action/index.ts @@ -23,6 +23,9 @@ export enum PluginPackageName { GRAPHQL = "graphql-plugin", JS = "js-plugin", ORACLE = "oracle-plugin", + MY_SQL = "mysql-plugin", + MS_SQL = "mssql-plugin", + SNOWFLAKE = "snowflake-plugin", } // more can be added subsequently.