2024-02-28 14:44:52 +01:00
import {
2024-04-17 18:12:26 +02:00
FieldType ,
2024-02-28 14:44:52 +01:00
Operation ,
2024-07-30 13:29:16 +02:00
PaginationJson ,
2024-02-28 14:44:52 +01:00
QueryJson ,
2024-07-30 13:29:16 +02:00
SearchFilters ,
SortJson ,
SqlClient ,
2024-02-28 14:44:52 +01:00
Table ,
2024-04-17 18:12:26 +02:00
TableSourceType ,
2024-02-28 14:44:52 +01:00
} from "@budibase/types"
2024-05-20 19:22:46 +02:00
import { sql } from "@budibase/backend-core"
2024-07-30 13:29:16 +02:00
import { merge } from "lodash"
2024-05-20 19:22:46 +02:00
const Sql = sql . Sql
2021-06-03 17:31:24 +02:00
const TABLE_NAME = "test"
2024-04-16 17:41:39 +02:00
const TABLE : Table = {
type : "table" ,
sourceType : TableSourceType.EXTERNAL ,
sourceId : "SOURCE_ID" ,
2024-04-17 18:12:26 +02:00
schema : {
id : {
name : "id" ,
type : FieldType . NUMBER ,
} ,
} ,
2024-04-16 17:41:39 +02:00
name : TABLE_NAME ,
primary : [ "id" ] ,
}
2021-06-03 17:31:24 +02:00
2024-07-30 13:29:16 +02:00
const ORACLE_TABLE : Partial < Table > = {
schema : {
name : {
name : "name" ,
type : FieldType . STRING ,
} ,
} ,
}
function endpoint ( table : string , operation : Operation ) {
2021-06-03 17:31:24 +02:00
return {
datasourceId : "Postgres" ,
operation : operation ,
entityId : table || TABLE_NAME ,
}
}
2022-08-31 20:21:45 +02:00
function generateReadJson ( {
2024-07-22 22:23:27 +02:00
table ,
fields ,
filters ,
sort ,
paginate ,
2024-07-30 13:29:16 +02:00
} : {
table? : Partial < Table >
fields? : string [ ]
filters? : SearchFilters
sort? : SortJson
paginate? : PaginationJson
} = { } ) : QueryJson {
let tableObj : Table = { . . . TABLE }
2024-04-16 17:41:39 +02:00
if ( table ) {
2024-07-30 13:29:16 +02:00
tableObj = merge ( TABLE , table )
2024-04-16 17:41:39 +02:00
}
2021-06-03 17:31:24 +02:00
return {
2024-07-30 13:29:16 +02:00
endpoint : endpoint ( tableObj . name || TABLE_NAME , Operation . READ ) ,
2021-06-03 17:31:24 +02:00
resource : {
fields : fields || [ ] ,
} ,
filters : filters || { } ,
sort : sort || { } ,
2024-07-30 13:29:16 +02:00
paginate : paginate || undefined ,
2023-08-04 14:53:30 +02:00
meta : {
2024-04-16 17:41:39 +02:00
table : tableObj ,
2023-08-04 14:53:30 +02:00
} ,
2021-06-03 17:31:24 +02:00
}
}
2024-02-28 14:44:52 +01:00
function generateRelationshipJson ( config : { schema? : string } = { } ) : QueryJson {
2022-12-19 19:12:05 +01:00
return {
endpoint : {
datasourceId : "Postgres" ,
entityId : "brands" ,
2024-02-28 14:44:52 +01:00
operation : Operation.READ ,
2022-12-19 19:12:05 +01:00
schema : config.schema ,
} ,
resource : {
fields : [
"brands.brand_id" ,
"brands.brand_name" ,
"products.product_id" ,
"products.product_name" ,
"products.brand_id" ,
] ,
} ,
filters : { } ,
sort : { } ,
relationships : [
{
from : "brand_id" ,
to : "brand_id" ,
tableName : "products" ,
column : "products" ,
} ,
] ,
2024-07-22 22:23:27 +02:00
extra : { idFilter : { } } ,
2024-04-16 17:41:39 +02:00
meta : {
table : TABLE ,
} ,
2022-12-19 19:12:05 +01:00
}
}
function generateManyRelationshipJson ( config : { schema? : string } = { } ) {
return {
endpoint : {
datasourceId : "Postgres" ,
entityId : "stores" ,
operation : "READ" ,
schema : config.schema ,
} ,
resource : {
fields : [
"stores.store_id" ,
"stores.store_name" ,
"products.product_id" ,
"products.product_name" ,
] ,
} ,
filters : { } ,
sort : { } ,
paginate : { } ,
relationships : [
{
from : "store_id" ,
to : "product_id" ,
tableName : "products" ,
column : "products" ,
through : "stocks" ,
fromPrimary : "store_id" ,
toPrimary : "product_id" ,
} ,
] ,
2024-07-22 22:23:27 +02:00
extra : { idFilter : { } } ,
2024-04-17 18:12:26 +02:00
meta : {
table : TABLE ,
} ,
2022-12-19 19:12:05 +01:00
}
}
2021-06-03 17:31:24 +02:00
describe ( "SQL query builder" , ( ) = > {
2024-09-10 18:07:31 +02:00
const relationshipLimit = 500
2021-06-03 17:31:24 +02:00
const limit = 500
2022-08-11 14:50:05 +02:00
const client = SqlClient . POSTGRES
2022-08-31 20:21:45 +02:00
let sql : any
2021-06-03 17:31:24 +02:00
beforeEach ( ( ) = > {
sql = new Sql ( client , limit )
} )
2022-12-19 19:12:05 +01:00
it ( "should add the schema to the LEFT JOIN" , ( ) = > {
2024-07-22 22:23:27 +02:00
const query = sql . _query ( generateRelationshipJson ( { schema : "production" } ) )
2022-12-19 19:12:05 +01:00
expect ( query ) . toEqual ( {
2024-09-11 15:52:09 +02:00
bindings : [ limit , relationshipLimit ] ,
2024-09-16 20:20:58 +02:00
sql : ` with "paginated" as (select "brands".* from "production"."brands" order by "test"."id" asc limit $ 1) select "brands".*, (select json_agg(json_build_object('brand_id',"products"."brand_id",'product_id',"products"."product_id",'product_name',"products"."product_name")) from (select "products".* from "production"."products" as "products" where "products"."brand_id" = "brands"."brand_id" order by "products"."brand_id" asc limit $ 2) as "products") as "products" from "paginated" as "brands" order by "test"."id" asc ` ,
2022-12-19 19:12:05 +01:00
} )
} )
it ( "should handle if the schema is not present when doing a LEFT JOIN" , ( ) = > {
const query = sql . _query ( generateRelationshipJson ( ) )
expect ( query ) . toEqual ( {
2024-09-11 15:52:09 +02:00
bindings : [ limit , relationshipLimit ] ,
2024-09-16 20:20:58 +02:00
sql : ` with "paginated" as (select "brands".* from "brands" order by "test"."id" asc limit $ 1) select "brands".*, (select json_agg(json_build_object('brand_id',"products"."brand_id",'product_id',"products"."product_id",'product_name',"products"."product_name")) from (select "products".* from "products" as "products" where "products"."brand_id" = "brands"."brand_id" order by "products"."brand_id" asc limit $ 2) as "products") as "products" from "paginated" as "brands" order by "test"."id" asc ` ,
2022-12-19 19:12:05 +01:00
} )
} )
it ( "should add the schema to both the toTable and throughTable in many-to-many join" , ( ) = > {
const query = sql . _query (
2024-07-22 22:23:27 +02:00
generateManyRelationshipJson ( { schema : "production" } )
2022-12-19 19:12:05 +01:00
)
expect ( query ) . toEqual ( {
2024-09-11 15:52:09 +02:00
bindings : [ limit , relationshipLimit ] ,
sql : ` with "paginated" as (select "stores".* from "production"."stores" order by "test"."id" asc limit $ 1) select "stores".*, (select json_agg(json_build_object('product_id',"products"."product_id",'product_name',"products"."product_name")) from (select "products".* from "production"."products" as "products" inner join "production"."stocks" as "stocks" on "products"."product_id" = "stocks"."product_id" where "stocks"."store_id" = "stores"."store_id" order by "products"."product_id" asc limit $ 2) as "products") as "products" from "paginated" as "stores" order by "test"."id" asc ` ,
2022-12-19 19:12:05 +01:00
} )
} )
2023-01-20 15:03:14 +01:00
2023-03-28 12:46:29 +02:00
it ( "should lowercase the values for Oracle LIKE statements" , ( ) = > {
let query = new Sql ( SqlClient . ORACLE , limit ) . _query (
generateReadJson ( {
filters : {
string : {
name : "John" ,
} ,
} ,
} )
)
expect ( query ) . toEqual ( {
2024-09-05 19:12:53 +02:00
bindings : [ "john%" , limit ] ,
sql : ` select * from (select * from "test" where LOWER("test"."name") LIKE :1 order by "test"."id" asc) where rownum <= :2 ` ,
2023-03-28 12:46:29 +02:00
} )
query = new Sql ( SqlClient . ORACLE , limit ) . _query (
generateReadJson ( {
filters : {
contains : {
age : [ 20 , 25 ] ,
name : [ "John" , "Mary" ] ,
} ,
} ,
} )
)
2024-08-19 17:49:40 +02:00
const filterSet = [ ` %20% ` , ` %25% ` , ` %"john"% ` , ` %"mary"% ` ]
2023-03-28 12:46:29 +02:00
expect ( query ) . toEqual ( {
2024-09-05 19:12:53 +02:00
bindings : [ . . . filterSet , limit ] ,
sql : ` select * from (select * from "test" where COALESCE(LOWER("test"."age"), '') LIKE :1 AND COALESCE(LOWER("test"."age"), '') LIKE :2 and COALESCE(LOWER("test"."name"), '') LIKE :3 AND COALESCE(LOWER("test"."name"), '') LIKE :4 order by "test"."id" asc) where rownum <= :5 ` ,
2023-03-28 12:46:29 +02:00
} )
query = new Sql ( SqlClient . ORACLE , limit ) . _query (
generateReadJson ( {
filters : {
fuzzy : {
name : "Jo" ,
} ,
} ,
} )
)
expect ( query ) . toEqual ( {
2024-09-05 19:12:53 +02:00
bindings : [ ` %jo% ` , limit ] ,
sql : ` select * from (select * from "test" where LOWER("test"."name") LIKE :1 order by "test"."id" asc) where rownum <= :2 ` ,
2024-07-22 22:00:20 +02:00
} )
} )
2024-07-23 12:42:21 +02:00
it ( "should use an oracle compatible coalesce query for oracle when using the equals filter" , ( ) = > {
2024-07-22 22:00:20 +02:00
let query = new Sql ( SqlClient . ORACLE , limit ) . _query (
generateReadJson ( {
2024-07-30 13:29:16 +02:00
table : ORACLE_TABLE ,
2024-07-22 22:00:20 +02:00
filters : {
equal : {
name : "John" ,
} ,
} ,
2024-07-22 22:23:27 +02:00
} )
)
2024-07-22 22:00:20 +02:00
expect ( query ) . toEqual ( {
2024-09-05 19:12:53 +02:00
bindings : [ "John" , limit ] ,
sql : ` select * from (select * from "test" where (to_char("test"."name") IS NOT NULL AND to_char("test"."name") = :1) order by "test"."id" asc) where rownum <= :2 ` ,
2023-03-28 12:46:29 +02:00
} )
} )
2024-07-22 22:28:44 +02:00
2024-07-23 12:42:21 +02:00
it ( "should use an oracle compatible coalesce query for oracle when using the not equals filter" , ( ) = > {
2024-07-22 22:28:44 +02:00
let query = new Sql ( SqlClient . ORACLE , limit ) . _query (
generateReadJson ( {
2024-07-30 13:29:16 +02:00
table : ORACLE_TABLE ,
2024-07-22 22:28:44 +02:00
filters : {
notEqual : {
name : "John" ,
} ,
} ,
} )
)
expect ( query ) . toEqual ( {
2024-09-05 19:12:53 +02:00
bindings : [ "John" , limit ] ,
sql : ` select * from (select * from "test" where (to_char("test"."name") IS NOT NULL AND to_char("test"."name") != :1) OR to_char("test"."name") IS NULL order by "test"."id" asc) where rownum <= :2 ` ,
2024-07-22 22:28:44 +02:00
} )
} )
2021-06-03 17:45:43 +02:00
} )