Updating how aliasing is handled.

This commit is contained in:
mike12345567 2024-01-30 17:57:10 +00:00
parent 09a0d00aa7
commit bb0b776684
5 changed files with 158 additions and 137 deletions

View File

@ -113,7 +113,7 @@ export default class AliasTables {
} }
json.meta.tables = aliasedTables json.meta.tables = aliasedTables
} }
json.endpoint.alias = this.getAlias(json.endpoint.entityId) json.tableAliases = this.tableAliases
const response = await getDatasourceAndQuery(json) const response = await getDatasourceAndQuery(json)
return this.reverse(response) return this.reverse(response)
} }

View File

@ -129,8 +129,13 @@ class InternalBuilder {
addFilters( addFilters(
query: KnexQuery, query: KnexQuery,
filters: SearchFilters | undefined, filters: SearchFilters | undefined,
opts: { relationship?: boolean; tableName?: string } tableName: string,
opts: { aliases?: Record<string, string>; relationship?: boolean }
): KnexQuery { ): KnexQuery {
function getTableName(name: string) {
const alias = opts.aliases?.[name]
return alias || name
}
function iterate( function iterate(
structure: { [key: string]: any }, structure: { [key: string]: any },
fn: (key: string, value: any) => void fn: (key: string, value: any) => void
@ -139,10 +144,11 @@ class InternalBuilder {
const updatedKey = dbCore.removeKeyNumbering(key) const updatedKey = dbCore.removeKeyNumbering(key)
const isRelationshipField = updatedKey.includes(".") const isRelationshipField = updatedKey.includes(".")
if (!opts.relationship && !isRelationshipField) { if (!opts.relationship && !isRelationshipField) {
fn(`${opts.tableName}.${updatedKey}`, value) fn(`${getTableName(tableName)}.${updatedKey}`, value)
} }
if (opts.relationship && isRelationshipField) { if (opts.relationship && isRelationshipField) {
fn(updatedKey, value) const [filterTableName, property] = updatedKey.split(".")
fn(`${getTableName(filterTableName)}.${property}`, value)
} }
} }
} }
@ -345,17 +351,15 @@ class InternalBuilder {
query: KnexQuery, query: KnexQuery,
fromTable: string, fromTable: string,
relationships: RelationshipsJson[] | undefined, relationships: RelationshipsJson[] | undefined,
schema: string | undefined schema: string | undefined,
aliases?: Record<string, string>
): KnexQuery { ): KnexQuery {
if (!relationships) { if (!relationships) {
return query return query
} }
const tableSets: Record<string, [RelationshipsJson]> = {} const tableSets: Record<string, [RelationshipsJson]> = {}
// add up all aliases
let aliases: Record<string, string> = {}
// aggregate into table sets (all the same to tables) // aggregate into table sets (all the same to tables)
for (let relationship of relationships) { for (let relationship of relationships) {
aliases = { ...aliases, ...relationship.aliases }
const keyObj: { toTable: string; throughTable: string | undefined } = { const keyObj: { toTable: string; throughTable: string | undefined } = {
toTable: relationship.tableName, toTable: relationship.tableName,
throughTable: undefined, throughTable: undefined,
@ -372,9 +376,9 @@ class InternalBuilder {
} }
for (let [key, relationships] of Object.entries(tableSets)) { for (let [key, relationships] of Object.entries(tableSets)) {
const { toTable, throughTable } = JSON.parse(key) const { toTable, throughTable } = JSON.parse(key)
const toAlias = aliases[toTable] || toTable, const toAlias = aliases?.[toTable] || toTable,
throughAlias = aliases[throughTable] || throughTable, throughAlias = aliases?.[throughTable] || throughTable,
fromAlias = aliases[fromTable] || fromTable fromAlias = aliases?.[fromTable] || fromTable
let toTableWithSchema = this.tableNameWithSchema(toTable, { let toTableWithSchema = this.tableNameWithSchema(toTable, {
alias: toAlias, alias: toAlias,
schema, schema,
@ -423,22 +427,23 @@ class InternalBuilder {
knexWithAlias( knexWithAlias(
knex: Knex, knex: Knex,
endpoint: { entityId: string; alias?: string; schema?: string } endpoint: QueryJson["endpoint"],
): { query: KnexQuery; aliased: string } { aliases?: QueryJson["tableAliases"]
): KnexQuery {
const tableName = endpoint.entityId const tableName = endpoint.entityId
const alias = endpoint.alias const tableAliased = aliases?.[tableName]
const aliased = alias ? alias : tableName ? `${tableName} as ${aliases?.[tableName]}`
const tableAliased = alias ? `${tableName} as ${alias}` : tableName : tableName
let query = knex(tableAliased) let query = knex(tableAliased)
if (endpoint.schema) { if (endpoint.schema) {
query = query.withSchema(endpoint.schema) query = query.withSchema(endpoint.schema)
} }
return { query, aliased } return query
} }
create(knex: Knex, json: QueryJson, opts: QueryOptions): KnexQuery { create(knex: Knex, json: QueryJson, opts: QueryOptions): KnexQuery {
const { endpoint, body } = json const { endpoint, body } = json
let { query } = this.knexWithAlias(knex, endpoint) let query = this.knexWithAlias(knex, endpoint)
const parsedBody = parseBody(body) const parsedBody = parseBody(body)
// make sure no null values in body for creation // make sure no null values in body for creation
for (let [key, value] of Object.entries(parsedBody)) { for (let [key, value] of Object.entries(parsedBody)) {
@ -457,7 +462,7 @@ class InternalBuilder {
bulkCreate(knex: Knex, json: QueryJson): KnexQuery { bulkCreate(knex: Knex, json: QueryJson): KnexQuery {
const { endpoint, body } = json const { endpoint, body } = json
let { query } = this.knexWithAlias(knex, endpoint) let query = this.knexWithAlias(knex, endpoint)
if (!Array.isArray(body)) { if (!Array.isArray(body)) {
return query return query
} }
@ -466,8 +471,10 @@ class InternalBuilder {
} }
read(knex: Knex, json: QueryJson, limit: number): KnexQuery { read(knex: Knex, json: QueryJson, limit: number): KnexQuery {
let { endpoint, resource, filters, paginate, relationships } = json let { endpoint, resource, filters, paginate, relationships, tableAliases } =
json
const tableName = endpoint.entityId
// select all if not specified // select all if not specified
if (!resource) { if (!resource) {
resource = { fields: [] } resource = { fields: [] }
@ -493,19 +500,20 @@ class InternalBuilder {
} }
// start building the query // start building the query
let { query, aliased } = this.knexWithAlias(knex, endpoint) let query = this.knexWithAlias(knex, endpoint, tableAliases)
query = query.limit(foundLimit) query = query.limit(foundLimit)
if (foundOffset) { if (foundOffset) {
query = query.offset(foundOffset) query = query.offset(foundOffset)
} }
query = this.addFilters(query, filters, { tableName: aliased }) query = this.addFilters(query, filters, tableName, {
aliases: tableAliases,
})
// add sorting to pre-query // add sorting to pre-query
query = this.addSorting(query, json) query = this.addSorting(query, json)
// @ts-ignore const alias = tableAliases?.[tableName] || tableName
let preQuery: KnexQuery = knex({ let preQuery = knex({
// @ts-ignore [alias]: query,
[aliased]: query, } as any).select(selectStatement) as any
}).select(selectStatement)
// have to add after as well (this breaks MS-SQL) // have to add after as well (this breaks MS-SQL)
if (this.client !== SqlClient.MS_SQL) { if (this.client !== SqlClient.MS_SQL) {
preQuery = this.addSorting(preQuery, json) preQuery = this.addSorting(preQuery, json)
@ -513,18 +521,24 @@ class InternalBuilder {
// handle joins // handle joins
query = this.addRelationships( query = this.addRelationships(
preQuery, preQuery,
aliased, tableName,
relationships, relationships,
endpoint.schema endpoint.schema,
tableAliases
) )
return this.addFilters(query, filters, { relationship: true }) return this.addFilters(query, filters, tableName, {
relationship: true,
aliases: tableAliases,
})
} }
update(knex: Knex, json: QueryJson, opts: QueryOptions): KnexQuery { update(knex: Knex, json: QueryJson, opts: QueryOptions): KnexQuery {
const { endpoint, body, filters } = json const { endpoint, body, filters, tableAliases } = json
let { query, aliased } = this.knexWithAlias(knex, endpoint) let query = this.knexWithAlias(knex, endpoint, tableAliases)
const parsedBody = parseBody(body) const parsedBody = parseBody(body)
query = this.addFilters(query, filters, { tableName: aliased }) query = this.addFilters(query, filters, endpoint.entityId, {
aliases: tableAliases,
})
// mysql can't use returning // mysql can't use returning
if (opts.disableReturning) { if (opts.disableReturning) {
return query.update(parsedBody) return query.update(parsedBody)
@ -534,9 +548,11 @@ class InternalBuilder {
} }
delete(knex: Knex, json: QueryJson, opts: QueryOptions): KnexQuery { delete(knex: Knex, json: QueryJson, opts: QueryOptions): KnexQuery {
const { endpoint, filters } = json const { endpoint, filters, tableAliases } = json
let { query, aliased } = this.knexWithAlias(knex, endpoint) let query = this.knexWithAlias(knex, endpoint, tableAliases)
query = this.addFilters(query, filters, { tableName: aliased }) query = this.addFilters(query, filters, endpoint.entityId, {
aliases: tableAliases,
})
// mysql can't use returning // mysql can't use returning
if (opts.disableReturning) { if (opts.disableReturning) {
return query.delete() return query.delete()

View File

@ -1,5 +1,7 @@
const Sql = require("../base/sql").default import { SqlClient } from "../utils"
const { SqlClient } = require("../utils") import Sql from "../base/sql"
import { QueryJson } from "@budibase/types"
import { join } from "path"
const TABLE_NAME = "test" const TABLE_NAME = "test"
@ -17,7 +19,7 @@ function generateReadJson({
filters, filters,
sort, sort,
paginate, paginate,
}: any = {}) { }: any = {}): QueryJson {
return { return {
endpoint: endpoint(table || TABLE_NAME, "READ"), endpoint: endpoint(table || TABLE_NAME, "READ"),
resource: { resource: {
@ -30,7 +32,7 @@ function generateReadJson({
table: { table: {
name: table || TABLE_NAME, name: table || TABLE_NAME,
primary: ["id"], primary: ["id"],
}, } as any,
}, },
} }
} }
@ -687,102 +689,12 @@ describe("SQL query builder", () => {
describe("Captures of real examples", () => { describe("Captures of real examples", () => {
const limit = 5000 const limit = 5000
it("should handle filtering by relationship", () => { function getJson(name: string): QueryJson {
const queryJson = { return require(join(__dirname, "sqlQueryJson", name)) as QueryJson
endpoint: {
datasourceId: "datasource_plus_8066e56456784eb2a00129d31be5c3e7",
entityId: "products",
operation: "READ",
alias: "a",
},
resource: {
fields: [
"a.productname",
"a.productid",
"b.executorid",
"b.taskname",
"b.taskid",
"b.completed",
"b.qaid",
],
},
filters: {
equal: {
"1:tasks.taskname": "assembling",
},
onEmptyFilter: "all",
},
sort: {
productname: {
direction: "ASCENDING",
},
},
paginate: {
limit: 100,
page: 1,
},
relationships: [
{
tableName: "tasks",
column: "tasks",
through: "products_tasks",
from: "productid",
to: "taskid",
fromPrimary: "productid",
toPrimary: "taskid",
aliases: {
products_tasks: "c",
tasks: "b",
products: "a",
},
},
],
meta: {
table: {
type: "table",
_id: "datasource_plus_8066e56456784eb2a00129d31be5c3e7__products",
primary: ["productid"],
name: "a",
schema: {
productname: {
type: "string",
externalType: "character varying",
autocolumn: false,
name: "productname",
constraints: {
presence: false,
},
},
productid: {
type: "number",
externalType: "integer",
autocolumn: true,
name: "productid",
constraints: {
presence: false,
},
},
tasks: {
tableId:
"datasource_plus_8066e56456784eb2a00129d31be5c3e7__tasks",
name: "tasks",
relationshipType: "many-to-many",
fieldName: "taskid",
through:
"datasource_plus_8066e56456784eb2a00129d31be5c3e7__products_tasks",
throughFrom: "taskid",
throughTo: "productid",
type: "link",
main: true,
_id: "ca6862d9ba09146dd8a68e3b5b7055a09",
},
},
sourceId: "datasource_plus_8066e56456784eb2a00129d31be5c3e7",
sourceType: "external",
primaryDisplay: "productname",
},
},
} }
it("should handle filtering by relationship", () => {
const queryJson = getJson(`filterByRelationship.json`)
let query = new Sql(SqlClient.POSTGRES, limit)._query(queryJson) let query = new Sql(SqlClient.POSTGRES, limit)._query(queryJson)
expect(query).toEqual({ expect(query).toEqual({
bindings: [100, "assembling", limit], bindings: [100, "assembling", limit],

View File

@ -0,0 +1,94 @@
{
"endpoint": {
"datasourceId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7",
"entityId": "products",
"operation": "READ"
},
"resource": {
"fields": [
"a.productname",
"a.productid",
"b.executorid",
"b.taskname",
"b.taskid",
"b.completed",
"b.qaid"
]
},
"filters": {
"equal": {
"1:tasks.taskname": "assembling"
},
"onEmptyFilter": "all"
},
"sort": {
"productname": {
"direction": "ASCENDING"
}
},
"paginate": {
"limit": 100,
"page": 1
},
"relationships": [
{
"tableName": "tasks",
"column": "tasks",
"through": "products_tasks",
"from": "productid",
"to": "taskid",
"fromPrimary": "productid",
"toPrimary": "taskid"
}
],
"tableAliases": {
"products_tasks": "c",
"tasks": "b",
"products": "a"
},
"meta": {
"table": {
"type": "table",
"_id": "datasource_plus_8066e56456784eb2a00129d31be5c3e7__products",
"primary": [
"productid"
],
"name": "a",
"schema": {
"productname": {
"type": "string",
"externalType": "character varying",
"autocolumn": false,
"name": "productname",
"constraints": {
"presence": false
}
},
"productid": {
"type": "number",
"externalType": "integer",
"autocolumn": true,
"name": "productid",
"constraints": {
"presence": false
}
},
"tasks": {
"tableId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7__tasks",
"name": "tasks",
"relationshipType": "many-to-many",
"fieldName": "taskid",
"through": "datasource_plus_8066e56456784eb2a00129d31be5c3e7__products_tasks",
"throughFrom": "taskid",
"throughTo": "productid",
"type": "link",
"main": true,
"_id": "ca6862d9ba09146dd8a68e3b5b7055a09"
}
},
"sourceId": "datasource_plus_8066e56456784eb2a00129d31be5c3e7",
"sourceType": "external",
"primaryDisplay": "productname"
}
}
}

View File

@ -67,7 +67,6 @@ export interface RelationshipsJson {
fromPrimary?: string fromPrimary?: string
toPrimary?: string toPrimary?: string
tableName: string tableName: string
aliases?: Record<string, string>
column: string column: string
} }
@ -75,7 +74,6 @@ export interface QueryJson {
endpoint: { endpoint: {
datasourceId: string datasourceId: string
entityId: string entityId: string
alias?: string
operation: Operation operation: Operation
schema?: string schema?: string
} }
@ -96,6 +94,7 @@ export interface QueryJson {
idFilter?: SearchFilters idFilter?: SearchFilters
} }
relationships?: RelationshipsJson[] relationships?: RelationshipsJson[]
tableAliases?: Record<string, string>
} }
export interface SqlQuery { export interface SqlQuery {