Range tests passing.

This commit is contained in:
Sam Rose 2024-04-16 17:28:21 +01:00
parent 253fa0def8
commit 03b1823463
No known key found for this signature in database
4 changed files with 97 additions and 23 deletions

View File

@ -18,12 +18,12 @@ import _ from "lodash"
jest.unmock("mssql")
describe.each([
// ["internal", undefined],
["internal", undefined],
["internal-sqs", undefined],
// [DatabaseName.POSTGRES, getDatasource(DatabaseName.POSTGRES)],
// [DatabaseName.MYSQL, getDatasource(DatabaseName.MYSQL)],
// [DatabaseName.SQL_SERVER, getDatasource(DatabaseName.SQL_SERVER)],
// [DatabaseName.MARIADB, getDatasource(DatabaseName.MARIADB)],
[DatabaseName.POSTGRES, getDatasource(DatabaseName.POSTGRES)],
[DatabaseName.MYSQL, getDatasource(DatabaseName.MYSQL)],
[DatabaseName.SQL_SERVER, getDatasource(DatabaseName.SQL_SERVER)],
[DatabaseName.MARIADB, getDatasource(DatabaseName.MARIADB)],
])("/api/:sourceId/search (%s)", (name, dsProvider) => {
const isSqs = name === "internal-sqs"
const isInternal = name === "internal"
@ -337,6 +337,20 @@ describe.each([
expectQuery({
range: { age: { low: 5, high: 9 } },
}).toFindNothing())
// We never implemented half-open ranges in Lucene.
!isInternal &&
it("can search using just a low value", () =>
expectQuery({
range: { age: { low: 5 } },
}).toContainExactly([{ age: 10 }]))
// We never implemented half-open ranges in Lucene.
!isInternal &&
it("can search using just a high value", () =>
expectQuery({
range: { age: { high: 5 } },
}).toContainExactly([{ age: 1 }]))
})
describe("sort", () => {
@ -441,6 +455,20 @@ describe.each([
expectQuery({
range: { dob: { low: JAN_5TH, high: JAN_9TH } },
}).toFindNothing())
// We never implemented half-open ranges in Lucene.
!isInternal &&
it("can search using just a low value", () =>
expectQuery({
range: { dob: { low: JAN_5TH } },
}).toContainExactly([{ dob: JAN_10TH }]))
// We never implemented half-open ranges in Lucene.
!isInternal &&
it("can search using just a high value", () =>
expectQuery({
range: { dob: { high: JAN_5TH } },
}).toContainExactly([{ dob: JAN_1ST }]))
})
describe("sort", () => {
@ -616,7 +644,7 @@ describe.each([
// we've decided not to spend time on it.
!isInternal &&
describe("range", () => {
it.only("successfully finds a row", () =>
it("successfully finds a row", () =>
expectQuery({
range: { num: { low: SMALL, high: "5" } },
}).toContainExactly([{ num: SMALL }]))
@ -635,6 +663,16 @@ describe.each([
expectQuery({
range: { num: { low: "5", high: "5" } },
}).toFindNothing())
it("can search using just a low value", () =>
expectQuery({
range: { num: { low: MEDIUM } },
}).toContainExactly([{ num: MEDIUM }, { num: BIG }]))
it("can search using just a high value", () =>
expectQuery({
range: { num: { high: MEDIUM } },
}).toContainExactly([{ num: SMALL }, { num: MEDIUM }]))
})
})
})

View File

@ -146,7 +146,7 @@ class InternalBuilder {
addFilters(
query: Knex.QueryBuilder,
filters: SearchFilters | undefined,
tableName: string,
table: Table,
opts: { aliases?: Record<string, string>; relationship?: boolean }
): Knex.QueryBuilder {
function getTableName(name: string) {
@ -161,7 +161,7 @@ class InternalBuilder {
const updatedKey = dbCore.removeKeyNumbering(key)
const isRelationshipField = updatedKey.includes(".")
if (!opts.relationship && !isRelationshipField) {
fn(`${getTableName(tableName)}.${updatedKey}`, value)
fn(`${getTableName(table.name)}.${updatedKey}`, value)
}
if (opts.relationship && isRelationshipField) {
const [filterTableName, property] = updatedKey.split(".")
@ -276,6 +276,9 @@ class InternalBuilder {
}
if (filters.range) {
iterate(filters.range, (key, value) => {
const fieldName = key.split(".")[1]
const field = table.schema[fieldName]
const isEmptyObject = (val: any) => {
return (
val &&
@ -293,16 +296,46 @@ class InternalBuilder {
highValid = isValidFilter(value.high)
if (lowValid && highValid) {
// Use a between operator if we have 2 valid range values
const fnc = allOr ? "orWhereBetween" : "whereBetween"
query = query[fnc](key, [value.low, value.high])
if (
field.type === FieldType.BIGINT &&
this.client === SqlClient.SQL_LITE
) {
query = query.whereRaw(
`CAST(${key} AS INTEGER) BETWEEN CAST(? AS INTEGER) AND CAST(? AS INTEGER)`,
[value.low, value.high]
)
} else {
const fnc = allOr ? "orWhereBetween" : "whereBetween"
query = query[fnc](key, [value.low, value.high])
}
} else if (lowValid) {
// Use just a single greater than operator if we only have a low
const fnc = allOr ? "orWhere" : "where"
query = query[fnc](key, ">", value.low)
if (
field.type === FieldType.BIGINT &&
this.client === SqlClient.SQL_LITE
) {
query = query.whereRaw(
`CAST(${key} AS INTEGER) >= CAST(? AS INTEGER)`,
[value.low]
)
} else {
const fnc = allOr ? "orWhere" : "where"
query = query[fnc](key, ">=", value.low)
}
} else if (highValid) {
// Use just a single less than operator if we only have a high
const fnc = allOr ? "orWhere" : "where"
query = query[fnc](key, "<", value.high)
if (
field.type === FieldType.BIGINT &&
this.client === SqlClient.SQL_LITE
) {
query = query.whereRaw(
`CAST(${key} AS INTEGER) <= CAST(? AS INTEGER)`,
[value.high]
)
} else {
const fnc = allOr ? "orWhere" : "where"
query = query[fnc](key, "<=", value.high)
}
}
})
}
@ -532,7 +565,7 @@ class InternalBuilder {
if (foundOffset) {
query = query.offset(foundOffset)
}
query = this.addFilters(query, filters, tableName, {
query = this.addFilters(query, filters, json.meta?.table!, {
aliases: tableAliases,
})
// add sorting to pre-query
@ -553,7 +586,7 @@ class InternalBuilder {
endpoint.schema,
tableAliases
)
return this.addFilters(query, filters, tableName, {
return this.addFilters(query, filters, json.meta?.table!, {
relationship: true,
aliases: tableAliases,
})
@ -563,7 +596,7 @@ class InternalBuilder {
const { endpoint, body, filters, tableAliases } = json
let query = this.knexWithAlias(knex, endpoint, tableAliases)
const parsedBody = parseBody(body)
query = this.addFilters(query, filters, endpoint.entityId, {
query = this.addFilters(query, filters, json.meta?.table!, {
aliases: tableAliases,
})
// mysql can't use returning
@ -577,7 +610,7 @@ class InternalBuilder {
delete(knex: Knex, json: QueryJson, opts: QueryOptions): Knex.QueryBuilder {
const { endpoint, filters, tableAliases } = json
let query = this.knexWithAlias(knex, endpoint, tableAliases)
query = this.addFilters(query, filters, endpoint.entityId, {
query = this.addFilters(query, filters, json.meta?.table!, {
aliases: tableAliases,
})
// mysql can't use returning

View File

@ -185,6 +185,6 @@ export async function search(
}
} catch (err: any) {
const msg = typeof err === "string" ? err : err.message
throw new Error(`Unable to search by SQL - ${msg}`)
throw new Error(`Unable to search by SQL - ${msg}`, { cause: err })
}
}

View File

@ -13,10 +13,13 @@ export interface SearchFilters {
[key: string]: string
}
range?: {
[key: string]: {
high: number | string
low: number | string
}
[key: string]:
| {
high: number | string
low: number | string
}
| { high: number | string }
| { low: number | string }
}
equal?: {
[key: string]: any