diff --git a/client/packages/lowcoder/package.json b/client/packages/lowcoder/package.json index b3cf51247..1364faf96 100644 --- a/client/packages/lowcoder/package.json +++ b/client/packages/lowcoder/package.json @@ -36,6 +36,7 @@ "@types/react-signature-canvas": "^1.0.2", "@types/react-test-renderer": "^18.0.0", "@types/react-virtualized": "^9.21.21", + "alasql": "^4.6.2", "animate.css": "^4.1.1", "antd": "^5.20.0", "axios": "^1.7.7", diff --git a/client/packages/lowcoder/src/components/ResCreatePanel.tsx b/client/packages/lowcoder/src/components/ResCreatePanel.tsx index 72cd2854e..1225b99c7 100644 --- a/client/packages/lowcoder/src/components/ResCreatePanel.tsx +++ b/client/packages/lowcoder/src/components/ResCreatePanel.tsx @@ -169,6 +169,13 @@ const ResButton = (props: { compType: "streamApi", }, }, + alasql: { + label: trans("query.quickAlasql"), + type: BottomResTypeEnum.Query, + extra: { + compType: "alasql", + }, + }, graphql: { label: trans("query.quickGraphql"), type: BottomResTypeEnum.Query, @@ -319,6 +326,7 @@ export function ResCreatePanel(props: ResCreateModalProps) { + {datasource.map((i) => ( diff --git a/client/packages/lowcoder/src/comps/queries/httpQuery/alasqlQuery.tsx b/client/packages/lowcoder/src/comps/queries/httpQuery/alasqlQuery.tsx new file mode 100644 index 000000000..a49dada44 --- /dev/null +++ b/client/packages/lowcoder/src/comps/queries/httpQuery/alasqlQuery.tsx @@ -0,0 +1,134 @@ +import { QueryConfigItemWrapper, QueryConfigLabel, QueryConfigWrapper } from "components/query"; +import { simpleMultiComp } from "comps/generators/multi"; +import { JSONValue } from "../../../util/jsonTypes"; +import { ParamsStringControl } from "../../controls/paramsControl"; +import { dropdownControl } from "@lowcoder-ee/comps/controls/dropdownControl"; +import { QueryResult } from "../queryComp"; +import { QUERY_EXECUTION_ERROR, QUERY_EXECUTION_OK } from "@lowcoder-ee/constants/queryConstants"; +import { getDynamicStringSegments, isDynamicSegment } from "lowcoder-core"; +import alasql from "alasql"; +import { trans } from "i18n"; + +const childrenMap = { + databaseType: dropdownControl( + [ + { label: "Data Query", value: "dataQuery" }, + { label: "Local Database", value: "localDB" }, + ] as const, + "dataQuery" + ), + database: dropdownControl( + [ + { label: "Local Storage", value: "LOCALSTORAGE" }, + { label: "IndexedDB", value: "INDEXEDDB" }, + ] as const, + "LOCALSTORAGE" + ), + sql: ParamsStringControl, +}; + +const AlaSqlTmpQuery = simpleMultiComp(childrenMap); + +// TODO: Support multiple queries +export class AlaSqlQuery extends AlaSqlTmpQuery { + override getView() { + const children = this.children; + const params = [ ...children.sql.getQueryParams() ]; + const databaseType = children.databaseType.getView(); + const selectedDB = children.database.getView(); + const paramsMap: Record = {}; + params.forEach(({key, value}) => { + paramsMap[key] = value(); + }); + + const sqlQuery = children.sql.children.text.unevaledValue.replace(/ +/g, ' '); + const isCreateDBQuery = sqlQuery.toUpperCase().startsWith('CREATE DATABASE'); + + return async (p: { args?: Record }): Promise => { + try { + let result: JSONValue; + const timer = performance.now(); + + if (databaseType === 'localDB' && isCreateDBQuery) { + const updatedQuery = `${sqlQuery.slice(0, 6)} ${selectedDB} ${sqlQuery.slice(6)}`; + const tableName = updatedQuery.split(' ').pop()?.replace(';', ''); + result = alasql(updatedQuery); + result = alasql(`ATTACH ${selectedDB} DATABASE ${tableName};`); + } else { + let segments = getDynamicStringSegments(sqlQuery); + let dataArr: any = []; + segments = segments.map((segment) => { + if (isDynamicSegment(segment)) { + const key = segment.replace('{{','').replace('}}',''); + dataArr.push(paramsMap[key]); + return '?'; + } + return segment; + }) + result = alasql(segments.join(' '), dataArr); + } + + return { + data: result as JSONValue, + code: QUERY_EXECUTION_OK, + success: true, + runTime: Number((performance.now() - timer).toFixed()), + }; + } catch (e) { + return { + success: false, + data: "", + code: QUERY_EXECUTION_ERROR, + message: (e as any).message || "", + }; + } + }; + } + + propertyView(props: { datasourceId: string }) { + return ; + } +} + +const PropertyView = (props: { comp: InstanceType; datasourceId: string }) => { + const { comp } = props; + const { children } = comp; + + return ( + <> + + {trans("query.databaseType")} + + {children.databaseType.propertyView({ + styleName: "medium", + width: "100%", + })} + + + + {children.databaseType.getView() === 'localDB' && ( + + {trans("query.chooseDatabase")} + + {children.database.propertyView({ + styleName: "medium", + width: "100%", + })} + + + )} + + + + {children.sql.propertyView({ + placement: "bottom", + placeholder: "SELECT * FROM users WHERE user_id = {{userId}}::uuid", + styleName: "medium", + language: "sql", + enableMetaCompletion: true, + })} + + + + ); +}; diff --git a/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx b/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx index 07f4ef1e0..05c0f939d 100644 --- a/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx +++ b/client/packages/lowcoder/src/comps/queries/queryComp/queryPropertyView.tsx @@ -734,6 +734,7 @@ function useDatasourceStatus(datasourceId: string, datasourceType: ResourceType) datasourceType === "js" || datasourceType === "streamApi" || datasourceType === "libraryQuery" || + datasourceType === "alasql" || datasourceId === QUICK_REST_API_ID || datasourceId === QUICK_GRAPHQL_ID ) { diff --git a/client/packages/lowcoder/src/comps/queries/resourceDropdown.tsx b/client/packages/lowcoder/src/comps/queries/resourceDropdown.tsx index 8e3b742d6..3907a3d92 100644 --- a/client/packages/lowcoder/src/comps/queries/resourceDropdown.tsx +++ b/client/packages/lowcoder/src/comps/queries/resourceDropdown.tsx @@ -102,6 +102,11 @@ const QuickGraphqlValue: ResourceOptionValue = { type: "graphql", }; +const QuickAlasqlValue: ResourceOptionValue = { + id: "", + type: "alasql", +}; + interface ResourceDropdownProps { changeResource: (datasourceId: string, value: string) => void; selectedResource: ResourceOptionValue; @@ -265,6 +270,17 @@ export const ResourceDropdown = (props: ResourceDropdownProps) => { {trans("query.quickStreamAPI")} + + + + {getBottomResIcon("restApi")} + {trans("query.quickAlasql")} + + [] = [ "clickHouse", "snowflake", "mariadb", + "alasql", ]; export const apiPluginsForQueryLibrary: Partial[] = [ diff --git a/client/packages/lowcoder/src/constants/libConstants.ts b/client/packages/lowcoder/src/constants/libConstants.ts index 0820cf01c..cff4ded40 100644 --- a/client/packages/lowcoder/src/constants/libConstants.ts +++ b/client/packages/lowcoder/src/constants/libConstants.ts @@ -1 +1 @@ -export const libNames = new Set(["uuid", "numbro", "Papa", "supabase"]); +export const libNames = new Set(["uuid", "numbro", "Papa", "supabase", "alasql"]); diff --git a/client/packages/lowcoder/src/constants/queryConstants.ts b/client/packages/lowcoder/src/constants/queryConstants.ts index 89e420b05..be78de0d6 100644 --- a/client/packages/lowcoder/src/constants/queryConstants.ts +++ b/client/packages/lowcoder/src/constants/queryConstants.ts @@ -13,6 +13,7 @@ import { GraphqlQuery } from "../comps/queries/httpQuery/graphqlQuery"; import { toPluginQuery } from "comps/queries/pluginQuery/pluginQuery"; import { MultiCompConstructor } from "lowcoder-core"; import { DataSourcePluginMeta } from "lowcoder-sdk/dataSource"; +import { AlaSqlQuery } from "@lowcoder-ee/comps/queries/httpQuery/alasqlQuery"; export type DatasourceType = | "mysql" @@ -29,13 +30,15 @@ export type DatasourceType = | "googleSheets" | "graphql" | "snowflake" - | "mariadb"; + | "mariadb" + | "alasql"; export type ResourceType = DatasourceType | "js" | "libraryQuery" | "view"; export const QueryMap = { js: JSQuery, mysql: SQLQuery, + alasql: AlaSqlQuery, restApi: HttpQuery, streamApi: StreamQuery, mongodb: MongoQuery, diff --git a/client/packages/lowcoder/src/global.ts b/client/packages/lowcoder/src/global.ts index 2a57ab990..8c347d4d0 100644 --- a/client/packages/lowcoder/src/global.ts +++ b/client/packages/lowcoder/src/global.ts @@ -9,5 +9,6 @@ declare global { numbro: any; Papa: any; uuid: any; + alasql: any; } } diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index 9c8cdddfa..399bb3b87 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -731,6 +731,9 @@ export const en = { "quickRestAPI": "REST Query", "quickStreamAPI": "Stream Query", "quickGraphql": "GraphQL Query", + "quickAlasql": "Local SQL Query", + "databaseType": "Database Type", + "chooseDatabase": "Choose Database", "lowcoderAPI": "Lowcoder API", "executeJSCode": "Run JavaScript Code", "importFromQueryLibrary": "Import from Query Library", diff --git a/client/packages/lowcoder/src/index.sdk.ts b/client/packages/lowcoder/src/index.sdk.ts index ffea3d32b..4d9f9bcf6 100644 --- a/client/packages/lowcoder/src/index.sdk.ts +++ b/client/packages/lowcoder/src/index.sdk.ts @@ -2,6 +2,7 @@ import numbro from "numbro"; import Papa from "papaparse"; import * as uuid from "uuid"; import * as supabase from "@supabase/supabase-js"; +import * as alasql from "alasql"; import * as styledNameExports from "styled-components"; import styledDefault from "styled-components"; @@ -136,3 +137,4 @@ window.numbro = numbro; window.Papa = Papa; window.uuid = uuid; window.supabase = supabase; +window.alasql = alasql; diff --git a/client/packages/lowcoder/src/index.ts b/client/packages/lowcoder/src/index.ts index 55906f99c..310ff61c7 100644 --- a/client/packages/lowcoder/src/index.ts +++ b/client/packages/lowcoder/src/index.ts @@ -3,6 +3,7 @@ import ResizeObserver from "resize-observer-polyfill"; import numbro from "numbro"; import Papa from "papaparse"; import * as supabase from "@supabase/supabase-js"; +import * as alasql from "alasql"; import * as uuid from "uuid"; import "regenerator-runtime/runtime"; @@ -18,6 +19,7 @@ window.numbro = numbro; window.Papa = Papa; window.uuid = uuid; window.supabase = supabase; +window.alasql = alasql; // for chrome 63 if (!window.ResizeObserver) { diff --git a/client/yarn.lock b/client/yarn.lock index c55b51a7f..8a81f9328 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -5932,6 +5932,18 @@ __metadata: languageName: node linkType: hard +"alasql@npm:^4.6.2": + version: 4.6.2 + resolution: "alasql@npm:4.6.2" + dependencies: + cross-fetch: 4.1.0 + yargs: 16 + bin: + alasql: bin/alasql-cli.js + checksum: cc68e87eeaa72ddaec5f20c4ca631e2a8ddb45e38d4b7de41cb14661ead657b1afec8d9530160f66fe5253e9724db9ada5fc63ba2c5bcacf5b8f9583c7b0870f + languageName: node + linkType: hard + "animate.css@npm:^4.1.1": version: 4.1.1 resolution: "animate.css@npm:4.1.1" @@ -7309,6 +7321,17 @@ __metadata: languageName: node linkType: hard +"cliui@npm:^7.0.2": + version: 7.0.4 + resolution: "cliui@npm:7.0.4" + dependencies: + string-width: ^4.2.0 + strip-ansi: ^6.0.0 + wrap-ansi: ^7.0.0 + checksum: ce2e8f578a4813806788ac399b9e866297740eecd4ad1823c27fd344d78b22c5f8597d548adbcc46f0573e43e21e751f39446c5a5e804a12aace402b7a315d7f + languageName: node + linkType: hard + "cliui@npm:^8.0.1": version: 8.0.1 resolution: "cliui@npm:8.0.1" @@ -7870,6 +7893,15 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard +"cross-fetch@npm:4.1.0": + version: 4.1.0 + resolution: "cross-fetch@npm:4.1.0" + dependencies: + node-fetch: ^2.7.0 + checksum: c02fa85d59f83e50dbd769ee472c9cc984060c403ee5ec8654659f61a525c1a655eef1c7a35e365c1a107b4e72d76e786718b673d1cb3c97f61d4644cb0a9f9d + languageName: node + linkType: hard + "cross-fetch@npm:^3.1.5": version: 3.1.8 resolution: "cross-fetch@npm:3.1.8" @@ -14090,6 +14122,7 @@ coolshapes-react@lowcoder-org/coolshapes-react: "@types/regenerator-runtime": ^0.13.1 "@types/uuid": ^8.3.4 "@vitejs/plugin-react": ^2.2.0 + alasql: ^4.6.2 animate.css: ^4.1.1 antd: ^5.20.0 axios: ^1.7.7 @@ -15684,7 +15717,7 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard -"node-fetch@npm:^2.6.12": +"node-fetch@npm:^2.6.12, node-fetch@npm:^2.7.0": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -22446,6 +22479,13 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard +"yargs-parser@npm:^20.2.2": + version: 20.2.9 + resolution: "yargs-parser@npm:20.2.9" + checksum: 8bb69015f2b0ff9e17b2c8e6bfe224ab463dd00ca211eece72a4cd8a906224d2703fb8a326d36fdd0e68701e201b2a60ed7cf81ce0fd9b3799f9fe7745977ae3 + languageName: node + linkType: hard + "yargs-parser@npm:^21.1.1": version: 21.1.1 resolution: "yargs-parser@npm:21.1.1" @@ -22453,6 +22493,21 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard +"yargs@npm:16": + version: 16.2.0 + resolution: "yargs@npm:16.2.0" + dependencies: + cliui: ^7.0.2 + escalade: ^3.1.1 + get-caller-file: ^2.0.5 + require-directory: ^2.1.1 + string-width: ^4.2.0 + y18n: ^5.0.5 + yargs-parser: ^20.2.2 + checksum: b14afbb51e3251a204d81937c86a7e9d4bdbf9a2bcee38226c900d00f522969ab675703bee2a6f99f8e20103f608382936034e64d921b74df82b63c07c5e8f59 + languageName: node + linkType: hard + "yargs@npm:^17.3.1, yargs@npm:^17.5.1": version: 17.7.2 resolution: "yargs@npm:17.7.2"