Implementing a JSON aggregate method of selecting relationships.
This commit is contained in:
parent
548940e2e1
commit
ab5f50d2b8
|
@ -126,12 +126,20 @@ class InternalBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateSelectStatement(): (string | Knex.Raw)[] | "*" {
|
private generateSelectStatement(): (string | Knex.Raw)[] | "*" {
|
||||||
const { resource, meta } = this.query
|
const { endpoint, resource, meta, tableAliases } = this.query
|
||||||
|
|
||||||
if (!resource || !resource.fields || resource.fields.length === 0) {
|
if (!resource || !resource.fields || resource.fields.length === 0) {
|
||||||
return "*"
|
return "*"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// no relationships - select everything in SQLite
|
||||||
|
if (this.client === SqlClient.SQL_LITE) {
|
||||||
|
const alias = tableAliases?.[endpoint.entityId]
|
||||||
|
? tableAliases?.[endpoint.entityId]
|
||||||
|
: endpoint.entityId
|
||||||
|
return [this.knex.raw(`${this.quote(alias)}.*`)]
|
||||||
|
}
|
||||||
|
|
||||||
const schema = meta.table.schema
|
const schema = meta.table.schema
|
||||||
return resource.fields.map(field => {
|
return resource.fields.map(field => {
|
||||||
const parts = field.split(/\./g)
|
const parts = field.split(/\./g)
|
||||||
|
@ -745,16 +753,83 @@ class InternalBuilder {
|
||||||
return withSchema
|
return withSchema
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addJsonRelationships(
|
||||||
|
query: Knex.QueryBuilder,
|
||||||
|
fromTable: string,
|
||||||
|
relationships: RelationshipsJson[]
|
||||||
|
): Knex.QueryBuilder {
|
||||||
|
const { resource, tableAliases: aliases, endpoint } = this.query
|
||||||
|
const fields = resource?.fields || []
|
||||||
|
const jsonField = (field: string) => {
|
||||||
|
const unAliased = field.split(".").slice(1).join(".")
|
||||||
|
return `'${unAliased}',${field}`
|
||||||
|
}
|
||||||
|
for (let relationship of relationships) {
|
||||||
|
const {
|
||||||
|
tableName: toTable,
|
||||||
|
through: throughTable,
|
||||||
|
to: toKey,
|
||||||
|
from: fromKey,
|
||||||
|
fromPrimary,
|
||||||
|
toPrimary,
|
||||||
|
} = relationship
|
||||||
|
// skip invalid relationships
|
||||||
|
if (!toTable || !fromTable || !fromPrimary || !toPrimary) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (!throughTable) {
|
||||||
|
throw new Error("Only many-to-many implemented for JSON relationships")
|
||||||
|
}
|
||||||
|
const toAlias = aliases?.[toTable] || toTable,
|
||||||
|
throughAlias = aliases?.[throughTable] || throughTable,
|
||||||
|
fromAlias = aliases?.[fromTable] || fromTable
|
||||||
|
let toTableWithSchema = this.tableNameWithSchema(toTable, {
|
||||||
|
alias: toAlias,
|
||||||
|
schema: endpoint.schema,
|
||||||
|
})
|
||||||
|
let throughTableWithSchema = this.tableNameWithSchema(throughTable, {
|
||||||
|
alias: throughAlias,
|
||||||
|
schema: endpoint.schema,
|
||||||
|
})
|
||||||
|
const relationshipFields = fields.filter(
|
||||||
|
field => field.split(".")[0] === toAlias
|
||||||
|
)
|
||||||
|
const fieldList: string = relationshipFields
|
||||||
|
.map(field => jsonField(field))
|
||||||
|
.join(",")
|
||||||
|
let rawJsonArray: Knex.Raw
|
||||||
|
switch (this.client) {
|
||||||
|
case SqlClient.SQL_LITE:
|
||||||
|
rawJsonArray = this.knex.raw(
|
||||||
|
`json_group_array(json_object(${fieldList}))`
|
||||||
|
)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new Error(`JSON relationships not implement for ${this.client}`)
|
||||||
|
}
|
||||||
|
const subQuery = this.knex
|
||||||
|
.select(rawJsonArray)
|
||||||
|
.from(toTableWithSchema)
|
||||||
|
.join(throughTableWithSchema, function () {
|
||||||
|
this.on(`${toAlias}.${toPrimary}`, "=", `${throughAlias}.${toKey}`)
|
||||||
|
})
|
||||||
|
.where(
|
||||||
|
`${throughAlias}.${fromKey}`,
|
||||||
|
"=",
|
||||||
|
this.knex.raw(this.quotedIdentifier(`${fromAlias}.${fromPrimary}`))
|
||||||
|
)
|
||||||
|
query = query.select({ [relationship.column]: subQuery })
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
addRelationships(
|
addRelationships(
|
||||||
query: Knex.QueryBuilder,
|
query: Knex.QueryBuilder,
|
||||||
fromTable: string,
|
fromTable: string,
|
||||||
relationships: RelationshipsJson[] | undefined,
|
relationships: RelationshipsJson[],
|
||||||
schema: string | undefined,
|
schema?: string,
|
||||||
aliases?: Record<string, string>
|
aliases?: Record<string, string>
|
||||||
): Knex.QueryBuilder {
|
): Knex.QueryBuilder {
|
||||||
if (!relationships) {
|
|
||||||
return query
|
|
||||||
}
|
|
||||||
const tableSets: Record<string, [RelationshipsJson]> = {}
|
const tableSets: Record<string, [RelationshipsJson]> = {}
|
||||||
// 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) {
|
||||||
|
@ -957,42 +1032,27 @@ class InternalBuilder {
|
||||||
if (foundOffset != null) {
|
if (foundOffset != null) {
|
||||||
query = query.offset(foundOffset)
|
query = query.offset(foundOffset)
|
||||||
}
|
}
|
||||||
// add sorting to pre-query
|
|
||||||
// no point in sorting when counting
|
|
||||||
query = this.addSorting(query)
|
|
||||||
}
|
}
|
||||||
// add filters to the query (where)
|
|
||||||
query = this.addFilters(query, filters)
|
|
||||||
|
|
||||||
const alias = tableAliases?.[tableName] || tableName
|
|
||||||
let preQuery: Knex.QueryBuilder = this.knex({
|
|
||||||
// the typescript definition for the knex constructor doesn't support this
|
|
||||||
// syntax, but it is the only way to alias a pre-query result as part of
|
|
||||||
// a query - there is an alias dictionary type, but it assumes it can only
|
|
||||||
// be a table name, not a pre-query
|
|
||||||
[alias]: query as any,
|
|
||||||
})
|
|
||||||
// if counting, use distinct count, else select
|
// if counting, use distinct count, else select
|
||||||
preQuery = !counting
|
query = !counting
|
||||||
? preQuery.select(this.generateSelectStatement())
|
? query.select(this.generateSelectStatement())
|
||||||
: this.addDistinctCount(preQuery)
|
: this.addDistinctCount(query)
|
||||||
// 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 && !counting) {
|
if (this.client !== SqlClient.MS_SQL && !counting) {
|
||||||
preQuery = this.addSorting(preQuery)
|
query = this.addSorting(query)
|
||||||
}
|
}
|
||||||
// handle joins
|
// handle joins
|
||||||
query = this.addRelationships(
|
if (relationships && this.client === SqlClient.SQL_LITE) {
|
||||||
preQuery,
|
query = this.addJsonRelationships(query, tableName, relationships)
|
||||||
tableName,
|
} else if (relationships) {
|
||||||
relationships,
|
query = this.addRelationships(
|
||||||
endpoint.schema,
|
query,
|
||||||
tableAliases
|
tableName,
|
||||||
)
|
relationships,
|
||||||
|
endpoint.schema,
|
||||||
// add a base limit over the whole query
|
tableAliases
|
||||||
// if counting we can't set this limit
|
)
|
||||||
if (limits?.base) {
|
|
||||||
query = query.limit(limits.base)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.addFilters(query, filters, { relationship: true })
|
return this.addFilters(query, filters, { relationship: true })
|
||||||
|
|
Loading…
Reference in New Issue