feat: MySQL & MSSQL query generator (#24516)
Co-authored-by: Aishwarya UR <aishwarya@appsmith.com>
This commit is contained in:
parent
820beae5bd
commit
f24ecc2473
|
|
@ -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"));
|
||||
});
|
||||
});
|
||||
|
|
@ -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 <tablename>` 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({
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
236
app/client/src/WidgetQueryGenerators/MSSQL/index.test.ts
Normal file
236
app/client/src/WidgetQueryGenerators/MSSQL/index.test.ts
Normal file
|
|
@ -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 }],
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
227
app/client/src/WidgetQueryGenerators/MSSQL/index.ts
Normal file
227
app/client/src/WidgetQueryGenerators/MSSQL/index.ts
Normal file
|
|
@ -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`;
|
||||
}
|
||||
}
|
||||
236
app/client/src/WidgetQueryGenerators/MySQL/index.test.ts
Normal file
236
app/client/src/WidgetQueryGenerators/MySQL/index.test.ts
Normal file
|
|
@ -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 }],
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
227
app/client/src/WidgetQueryGenerators/MySQL/index.ts
Normal file
227
app/client/src/WidgetQueryGenerators/MySQL/index.ts
Normal file
|
|
@ -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`;
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
236
app/client/src/WidgetQueryGenerators/Snowflake/index.test.ts
Normal file
236
app/client/src/WidgetQueryGenerators/Snowflake/index.test.ts
Normal file
|
|
@ -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 }],
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
231
app/client/src/WidgetQueryGenerators/Snowflake/index.ts
Normal file
231
app/client/src/WidgetQueryGenerators/Snowflake/index.ts
Normal file
|
|
@ -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`;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export type ActionConfigurationMongoDB = {
|
|||
formData: MongoDBFormData;
|
||||
};
|
||||
|
||||
export type ActionConfigurationPostgreSQL = {
|
||||
export type ActionConfigurationSQL = {
|
||||
pluginSpecifiedTemplates: Array<object>;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user