Saving at this point - got exists working.
This commit is contained in:
parent
413628ca3f
commit
49c1f34b5d
|
@ -343,18 +343,24 @@ class InternalBuilder {
|
||||||
whereCb: (query: Knex.QueryBuilder) => Knex.QueryBuilder
|
whereCb: (query: Knex.QueryBuilder) => Knex.QueryBuilder
|
||||||
): Knex.QueryBuilder {
|
): Knex.QueryBuilder {
|
||||||
const mainKnex = this.knex
|
const mainKnex = this.knex
|
||||||
const { relationships, endpoint } = this.query
|
const { relationships, endpoint, tableAliases: aliases } = this.query
|
||||||
const tableName = endpoint.entityId
|
const tableName = endpoint.entityId
|
||||||
if (!relationships) {
|
if (!relationships) {
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
for (const relationship of relationships) {
|
for (const relationship of relationships) {
|
||||||
// this is the relationship which is being filtered
|
// this is the relationship which is being filtered
|
||||||
if (filterKey.startsWith(relationship.column) && relationship.to) {
|
if (
|
||||||
const subQuery = query.whereExists(function () {
|
filterKey.startsWith(`${relationship.tableName}.`) &&
|
||||||
this.select(mainKnex.raw(1)).from(relationship.tableName!)
|
relationship.to &&
|
||||||
})
|
relationship.tableName
|
||||||
query = whereCb(
|
) {
|
||||||
|
const relatedTableName = relationship.tableName
|
||||||
|
const alias = aliases?.[relatedTableName] || relatedTableName
|
||||||
|
let subQuery = mainKnex
|
||||||
|
.select(mainKnex.raw(1))
|
||||||
|
.from({ [alias]: relatedTableName })
|
||||||
|
subQuery = whereCb(
|
||||||
this.addJoin(
|
this.addJoin(
|
||||||
subQuery,
|
subQuery,
|
||||||
{
|
{
|
||||||
|
@ -365,6 +371,7 @@ class InternalBuilder {
|
||||||
[relationship]
|
[relationship]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
query = query.whereExists(subQuery)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -382,12 +389,13 @@ class InternalBuilder {
|
||||||
if (!filters) {
|
if (!filters) {
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
const builder = this
|
||||||
filters = this.parseFilters({ ...filters })
|
filters = this.parseFilters({ ...filters })
|
||||||
const aliases = this.query.tableAliases
|
const aliases = this.query.tableAliases
|
||||||
// if all or specified in filters, then everything is an or
|
// if all or specified in filters, then everything is an or
|
||||||
const allOr = filters.allOr
|
const allOr = filters.allOr
|
||||||
const tableName =
|
const isSqlite = this.client === SqlClient.SQL_LITE
|
||||||
this.client === SqlClient.SQL_LITE ? this.table._id! : this.table.name
|
const tableName = isSqlite ? this.table._id! : this.table.name
|
||||||
|
|
||||||
function getTableAlias(name: string) {
|
function getTableAlias(name: string) {
|
||||||
const alias = aliases?.[name]
|
const alias = aliases?.[name]
|
||||||
|
@ -395,13 +403,33 @@ class InternalBuilder {
|
||||||
}
|
}
|
||||||
function iterate(
|
function iterate(
|
||||||
structure: AnySearchFilter,
|
structure: AnySearchFilter,
|
||||||
fn: (key: string, value: any) => void,
|
fn: (
|
||||||
complexKeyFn?: (key: string[], value: any) => void
|
query: Knex.QueryBuilder,
|
||||||
|
key: string,
|
||||||
|
value: any
|
||||||
|
) => Knex.QueryBuilder,
|
||||||
|
complexKeyFn?: (
|
||||||
|
query: Knex.QueryBuilder,
|
||||||
|
key: string[],
|
||||||
|
value: any
|
||||||
|
) => Knex.QueryBuilder
|
||||||
) {
|
) {
|
||||||
|
const handleRelationship = (
|
||||||
|
q: Knex.QueryBuilder,
|
||||||
|
key: string,
|
||||||
|
value: any
|
||||||
|
) => {
|
||||||
|
const [filterTableName, ...otherProperties] = key.split(".")
|
||||||
|
const property = otherProperties.join(".")
|
||||||
|
const alias = getTableAlias(filterTableName)
|
||||||
|
return fn(q, alias ? `${alias}.${property}` : property, value)
|
||||||
|
}
|
||||||
for (const key in structure) {
|
for (const key in structure) {
|
||||||
const value = structure[key]
|
const value = structure[key]
|
||||||
const updatedKey = dbCore.removeKeyNumbering(key)
|
const updatedKey = dbCore.removeKeyNumbering(key)
|
||||||
const isRelationshipField = updatedKey.includes(".")
|
const isRelationshipField = updatedKey.includes(".")
|
||||||
|
const shouldProcessRelationship =
|
||||||
|
opts?.relationship && isRelationshipField
|
||||||
|
|
||||||
let castedTypeValue
|
let castedTypeValue
|
||||||
if (
|
if (
|
||||||
|
@ -410,7 +438,8 @@ class InternalBuilder {
|
||||||
complexKeyFn
|
complexKeyFn
|
||||||
) {
|
) {
|
||||||
const alias = getTableAlias(tableName)
|
const alias = getTableAlias(tableName)
|
||||||
complexKeyFn(
|
query = complexKeyFn(
|
||||||
|
query,
|
||||||
castedTypeValue.id.map((x: string) =>
|
castedTypeValue.id.map((x: string) =>
|
||||||
alias ? `${alias}.${x}` : x
|
alias ? `${alias}.${x}` : x
|
||||||
),
|
),
|
||||||
|
@ -418,27 +447,31 @@ class InternalBuilder {
|
||||||
)
|
)
|
||||||
} else if (!isRelationshipField) {
|
} else if (!isRelationshipField) {
|
||||||
const alias = getTableAlias(tableName)
|
const alias = getTableAlias(tableName)
|
||||||
fn(alias ? `${alias}.${updatedKey}` : updatedKey, value)
|
query = fn(
|
||||||
}
|
query,
|
||||||
if (opts?.relationship && isRelationshipField) {
|
alias ? `${alias}.${updatedKey}` : updatedKey,
|
||||||
// TODO: need to update fn to take the query
|
value
|
||||||
const [filterTableName, property] = updatedKey.split(".")
|
)
|
||||||
const alias = getTableAlias(filterTableName)
|
} else if (isSqlite && shouldProcessRelationship) {
|
||||||
fn(alias ? `${alias}.${property}` : property, value)
|
query = builder.addRelationshipForFilter(query, updatedKey, q => {
|
||||||
|
return handleRelationship(q, updatedKey, value)
|
||||||
|
})
|
||||||
|
} else if (shouldProcessRelationship) {
|
||||||
|
query = handleRelationship(query, updatedKey, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const like = (key: string, value: any) => {
|
const like = (q: Knex.QueryBuilder, key: string, value: any) => {
|
||||||
const fuzzyOr = filters?.fuzzyOr
|
const fuzzyOr = filters?.fuzzyOr
|
||||||
const fnc = fuzzyOr || allOr ? "orWhere" : "where"
|
const fnc = fuzzyOr || allOr ? "orWhere" : "where"
|
||||||
// postgres supports ilike, nothing else does
|
// postgres supports ilike, nothing else does
|
||||||
if (this.client === SqlClient.POSTGRES) {
|
if (this.client === SqlClient.POSTGRES) {
|
||||||
query = query[fnc](key, "ilike", `%${value}%`)
|
return q[fnc](key, "ilike", `%${value}%`)
|
||||||
} else {
|
} else {
|
||||||
const rawFnc = `${fnc}Raw`
|
const rawFnc = `${fnc}Raw`
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
query = query[rawFnc](`LOWER(${this.quotedIdentifier(key)}) LIKE ?`, [
|
return q[rawFnc](`LOWER(${this.quotedIdentifier(key)}) LIKE ?`, [
|
||||||
`%${value.toLowerCase()}%`,
|
`%${value.toLowerCase()}%`,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
@ -456,13 +489,13 @@ class InternalBuilder {
|
||||||
return `[${value.join(",")}]`
|
return `[${value.join(",")}]`
|
||||||
}
|
}
|
||||||
if (this.client === SqlClient.POSTGRES) {
|
if (this.client === SqlClient.POSTGRES) {
|
||||||
iterate(mode, (key, value) => {
|
iterate(mode, (q, key, value) => {
|
||||||
const wrap = any ? "" : "'"
|
const wrap = any ? "" : "'"
|
||||||
const op = any ? "\\?| array" : "@>"
|
const op = any ? "\\?| array" : "@>"
|
||||||
const fieldNames = key.split(/\./g)
|
const fieldNames = key.split(/\./g)
|
||||||
const table = fieldNames[0]
|
const table = fieldNames[0]
|
||||||
const col = fieldNames[1]
|
const col = fieldNames[1]
|
||||||
query = query[rawFnc](
|
return q[rawFnc](
|
||||||
`${not}COALESCE("${table}"."${col}"::jsonb ${op} ${wrap}${stringifyArray(
|
`${not}COALESCE("${table}"."${col}"::jsonb ${op} ${wrap}${stringifyArray(
|
||||||
value,
|
value,
|
||||||
any ? "'" : '"'
|
any ? "'" : '"'
|
||||||
|
@ -471,8 +504,8 @@ class InternalBuilder {
|
||||||
})
|
})
|
||||||
} else if (this.client === SqlClient.MY_SQL) {
|
} else if (this.client === SqlClient.MY_SQL) {
|
||||||
const jsonFnc = any ? "JSON_OVERLAPS" : "JSON_CONTAINS"
|
const jsonFnc = any ? "JSON_OVERLAPS" : "JSON_CONTAINS"
|
||||||
iterate(mode, (key, value) => {
|
iterate(mode, (q, key, value) => {
|
||||||
query = query[rawFnc](
|
return q[rawFnc](
|
||||||
`${not}COALESCE(${jsonFnc}(${key}, '${stringifyArray(
|
`${not}COALESCE(${jsonFnc}(${key}, '${stringifyArray(
|
||||||
value
|
value
|
||||||
)}'), FALSE)`
|
)}'), FALSE)`
|
||||||
|
@ -480,7 +513,7 @@ class InternalBuilder {
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const andOr = mode === filters?.containsAny ? " OR " : " AND "
|
const andOr = mode === filters?.containsAny ? " OR " : " AND "
|
||||||
iterate(mode, (key, value) => {
|
iterate(mode, (q, key, value) => {
|
||||||
let statement = ""
|
let statement = ""
|
||||||
const identifier = this.quotedIdentifier(key)
|
const identifier = this.quotedIdentifier(key)
|
||||||
for (let i in value) {
|
for (let i in value) {
|
||||||
|
@ -495,16 +528,16 @@ class InternalBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statement === "") {
|
if (statement === "") {
|
||||||
return
|
return q
|
||||||
}
|
}
|
||||||
|
|
||||||
if (not) {
|
if (not) {
|
||||||
query = query[rawFnc](
|
return q[rawFnc](
|
||||||
`(NOT (${statement}) OR ${identifier} IS NULL)`,
|
`(NOT (${statement}) OR ${identifier} IS NULL)`,
|
||||||
value
|
value
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
query = query[rawFnc](statement, value)
|
return q[rawFnc](statement, value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -534,39 +567,39 @@ class InternalBuilder {
|
||||||
const fnc = allOr ? "orWhereIn" : "whereIn"
|
const fnc = allOr ? "orWhereIn" : "whereIn"
|
||||||
iterate(
|
iterate(
|
||||||
filters.oneOf,
|
filters.oneOf,
|
||||||
(key: string, array) => {
|
(q, key: string, array) => {
|
||||||
if (this.client === SqlClient.ORACLE) {
|
if (this.client === SqlClient.ORACLE) {
|
||||||
key = this.convertClobs(key)
|
key = this.convertClobs(key)
|
||||||
array = Array.isArray(array) ? array : [array]
|
array = Array.isArray(array) ? array : [array]
|
||||||
const binding = new Array(array.length).fill("?").join(",")
|
const binding = new Array(array.length).fill("?").join(",")
|
||||||
query = query.whereRaw(`${key} IN (${binding})`, array)
|
return q.whereRaw(`${key} IN (${binding})`, array)
|
||||||
} else {
|
} else {
|
||||||
query = query[fnc](key, Array.isArray(array) ? array : [array])
|
return q[fnc](key, Array.isArray(array) ? array : [array])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(key: string[], array) => {
|
(q, key: string[], array) => {
|
||||||
if (this.client === SqlClient.ORACLE) {
|
if (this.client === SqlClient.ORACLE) {
|
||||||
const keyStr = `(${key.map(k => this.convertClobs(k)).join(",")})`
|
const keyStr = `(${key.map(k => this.convertClobs(k)).join(",")})`
|
||||||
const binding = `(${array
|
const binding = `(${array
|
||||||
.map((a: any) => `(${new Array(a.length).fill("?").join(",")})`)
|
.map((a: any) => `(${new Array(a.length).fill("?").join(",")})`)
|
||||||
.join(",")})`
|
.join(",")})`
|
||||||
query = query.whereRaw(`${keyStr} IN ${binding}`, array.flat())
|
return q.whereRaw(`${keyStr} IN ${binding}`, array.flat())
|
||||||
} else {
|
} else {
|
||||||
query = query[fnc](key, Array.isArray(array) ? array : [array])
|
return q[fnc](key, Array.isArray(array) ? array : [array])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (filters.string) {
|
if (filters.string) {
|
||||||
iterate(filters.string, (key, value) => {
|
iterate(filters.string, (q, key, value) => {
|
||||||
const fnc = allOr ? "orWhere" : "where"
|
const fnc = allOr ? "orWhere" : "where"
|
||||||
// postgres supports ilike, nothing else does
|
// postgres supports ilike, nothing else does
|
||||||
if (this.client === SqlClient.POSTGRES) {
|
if (this.client === SqlClient.POSTGRES) {
|
||||||
query = query[fnc](key, "ilike", `${value}%`)
|
return q[fnc](key, "ilike", `${value}%`)
|
||||||
} else {
|
} else {
|
||||||
const rawFnc = `${fnc}Raw`
|
const rawFnc = `${fnc}Raw`
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
query = query[rawFnc](`LOWER(${this.quotedIdentifier(key)}) LIKE ?`, [
|
return q[rawFnc](`LOWER(${this.quotedIdentifier(key)}) LIKE ?`, [
|
||||||
`${value.toLowerCase()}%`,
|
`${value.toLowerCase()}%`,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
@ -576,7 +609,7 @@ class InternalBuilder {
|
||||||
iterate(filters.fuzzy, like)
|
iterate(filters.fuzzy, like)
|
||||||
}
|
}
|
||||||
if (filters.range) {
|
if (filters.range) {
|
||||||
iterate(filters.range, (key, value) => {
|
iterate(filters.range, (q, key, value) => {
|
||||||
const isEmptyObject = (val: any) => {
|
const isEmptyObject = (val: any) => {
|
||||||
return (
|
return (
|
||||||
val &&
|
val &&
|
||||||
|
@ -605,97 +638,93 @@ class InternalBuilder {
|
||||||
schema?.type === FieldType.BIGINT &&
|
schema?.type === FieldType.BIGINT &&
|
||||||
this.client === SqlClient.SQL_LITE
|
this.client === SqlClient.SQL_LITE
|
||||||
) {
|
) {
|
||||||
query = query.whereRaw(
|
return q.whereRaw(
|
||||||
`CAST(${key} AS INTEGER) BETWEEN CAST(? AS INTEGER) AND CAST(? AS INTEGER)`,
|
`CAST(${key} AS INTEGER) BETWEEN CAST(? AS INTEGER) AND CAST(? AS INTEGER)`,
|
||||||
[value.low, value.high]
|
[value.low, value.high]
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
const fnc = allOr ? "orWhereBetween" : "whereBetween"
|
const fnc = allOr ? "orWhereBetween" : "whereBetween"
|
||||||
query = query[fnc](key, [value.low, value.high])
|
return q[fnc](key, [value.low, value.high])
|
||||||
}
|
}
|
||||||
} else if (lowValid) {
|
} else if (lowValid) {
|
||||||
if (
|
if (
|
||||||
schema?.type === FieldType.BIGINT &&
|
schema?.type === FieldType.BIGINT &&
|
||||||
this.client === SqlClient.SQL_LITE
|
this.client === SqlClient.SQL_LITE
|
||||||
) {
|
) {
|
||||||
query = query.whereRaw(
|
return q.whereRaw(`CAST(${key} AS INTEGER) >= CAST(? AS INTEGER)`, [
|
||||||
`CAST(${key} AS INTEGER) >= CAST(? AS INTEGER)`,
|
value.low,
|
||||||
[value.low]
|
])
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
const fnc = allOr ? "orWhere" : "where"
|
const fnc = allOr ? "orWhere" : "where"
|
||||||
query = query[fnc](key, ">=", value.low)
|
return q[fnc](key, ">=", value.low)
|
||||||
}
|
}
|
||||||
} else if (highValid) {
|
} else if (highValid) {
|
||||||
if (
|
if (
|
||||||
schema?.type === FieldType.BIGINT &&
|
schema?.type === FieldType.BIGINT &&
|
||||||
this.client === SqlClient.SQL_LITE
|
this.client === SqlClient.SQL_LITE
|
||||||
) {
|
) {
|
||||||
query = query.whereRaw(
|
return q.whereRaw(`CAST(${key} AS INTEGER) <= CAST(? AS INTEGER)`, [
|
||||||
`CAST(${key} AS INTEGER) <= CAST(? AS INTEGER)`,
|
value.high,
|
||||||
[value.high]
|
])
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
const fnc = allOr ? "orWhere" : "where"
|
const fnc = allOr ? "orWhere" : "where"
|
||||||
query = query[fnc](key, "<=", value.high)
|
return q[fnc](key, "<=", value.high)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return q
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (filters.equal) {
|
if (filters.equal) {
|
||||||
iterate(filters.equal, (key, value) => {
|
iterate(filters.equal, (q, key, value) => {
|
||||||
const fnc = allOr ? "orWhereRaw" : "whereRaw"
|
const fnc = allOr ? "orWhereRaw" : "whereRaw"
|
||||||
if (this.client === SqlClient.MS_SQL) {
|
if (this.client === SqlClient.MS_SQL) {
|
||||||
query = query[fnc](
|
return q[fnc](
|
||||||
`CASE WHEN ${this.quotedIdentifier(key)} = ? THEN 1 ELSE 0 END = 1`,
|
`CASE WHEN ${this.quotedIdentifier(key)} = ? THEN 1 ELSE 0 END = 1`,
|
||||||
[value]
|
[value]
|
||||||
)
|
)
|
||||||
} else if (this.client === SqlClient.ORACLE) {
|
} else if (this.client === SqlClient.ORACLE) {
|
||||||
const identifier = this.convertClobs(key)
|
const identifier = this.convertClobs(key)
|
||||||
query = query[fnc](
|
return q[fnc](`(${identifier} IS NOT NULL AND ${identifier} = ?)`, [
|
||||||
`(${identifier} IS NOT NULL AND ${identifier} = ?)`,
|
value,
|
||||||
[value]
|
])
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
query = query[fnc](
|
return q[fnc](`COALESCE(${this.quotedIdentifier(key)} = ?, FALSE)`, [
|
||||||
`COALESCE(${this.quotedIdentifier(key)} = ?, FALSE)`,
|
value,
|
||||||
[value]
|
])
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (filters.notEqual) {
|
if (filters.notEqual) {
|
||||||
iterate(filters.notEqual, (key, value) => {
|
iterate(filters.notEqual, (q, key, value) => {
|
||||||
const fnc = allOr ? "orWhereRaw" : "whereRaw"
|
const fnc = allOr ? "orWhereRaw" : "whereRaw"
|
||||||
if (this.client === SqlClient.MS_SQL) {
|
if (this.client === SqlClient.MS_SQL) {
|
||||||
query = query[fnc](
|
return q[fnc](
|
||||||
`CASE WHEN ${this.quotedIdentifier(key)} = ? THEN 1 ELSE 0 END = 0`,
|
`CASE WHEN ${this.quotedIdentifier(key)} = ? THEN 1 ELSE 0 END = 0`,
|
||||||
[value]
|
[value]
|
||||||
)
|
)
|
||||||
} else if (this.client === SqlClient.ORACLE) {
|
} else if (this.client === SqlClient.ORACLE) {
|
||||||
const identifier = this.convertClobs(key)
|
const identifier = this.convertClobs(key)
|
||||||
query = query[fnc](
|
return q[fnc](
|
||||||
`(${identifier} IS NOT NULL AND ${identifier} != ?) OR ${identifier} IS NULL`,
|
`(${identifier} IS NOT NULL AND ${identifier} != ?) OR ${identifier} IS NULL`,
|
||||||
[value]
|
[value]
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
query = query[fnc](
|
return q[fnc](`COALESCE(${this.quotedIdentifier(key)} != ?, TRUE)`, [
|
||||||
`COALESCE(${this.quotedIdentifier(key)} != ?, TRUE)`,
|
value,
|
||||||
[value]
|
])
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (filters.empty) {
|
if (filters.empty) {
|
||||||
iterate(filters.empty, key => {
|
iterate(filters.empty, (q, key) => {
|
||||||
const fnc = allOr ? "orWhereNull" : "whereNull"
|
const fnc = allOr ? "orWhereNull" : "whereNull"
|
||||||
query = query[fnc](key)
|
return q[fnc](key)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (filters.notEmpty) {
|
if (filters.notEmpty) {
|
||||||
iterate(filters.notEmpty, key => {
|
iterate(filters.notEmpty, (q, key) => {
|
||||||
const fnc = allOr ? "orWhereNotNull" : "whereNotNull"
|
const fnc = allOr ? "orWhereNotNull" : "whereNotNull"
|
||||||
query = query[fnc](key)
|
return q[fnc](key)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (filters.contains) {
|
if (filters.contains) {
|
||||||
|
|
Loading…
Reference in New Issue