Updating how aliasing is handled.
This commit is contained in:
parent
09a0d00aa7
commit
bb0b776684
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue