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") jest.unmock("mssql")
describe.each([ describe.each([
// ["internal", undefined], ["internal", undefined],
["internal-sqs", undefined], ["internal-sqs", undefined],
// [DatabaseName.POSTGRES, getDatasource(DatabaseName.POSTGRES)], [DatabaseName.POSTGRES, getDatasource(DatabaseName.POSTGRES)],
// [DatabaseName.MYSQL, getDatasource(DatabaseName.MYSQL)], [DatabaseName.MYSQL, getDatasource(DatabaseName.MYSQL)],
// [DatabaseName.SQL_SERVER, getDatasource(DatabaseName.SQL_SERVER)], [DatabaseName.SQL_SERVER, getDatasource(DatabaseName.SQL_SERVER)],
// [DatabaseName.MARIADB, getDatasource(DatabaseName.MARIADB)], [DatabaseName.MARIADB, getDatasource(DatabaseName.MARIADB)],
])("/api/:sourceId/search (%s)", (name, dsProvider) => { ])("/api/:sourceId/search (%s)", (name, dsProvider) => {
const isSqs = name === "internal-sqs" const isSqs = name === "internal-sqs"
const isInternal = name === "internal" const isInternal = name === "internal"
@ -337,6 +337,20 @@ describe.each([
expectQuery({ expectQuery({
range: { age: { low: 5, high: 9 } }, range: { age: { low: 5, high: 9 } },
}).toFindNothing()) }).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", () => { describe("sort", () => {
@ -441,6 +455,20 @@ describe.each([
expectQuery({ expectQuery({
range: { dob: { low: JAN_5TH, high: JAN_9TH } }, range: { dob: { low: JAN_5TH, high: JAN_9TH } },
}).toFindNothing()) }).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", () => { describe("sort", () => {
@ -616,7 +644,7 @@ describe.each([
// we've decided not to spend time on it. // we've decided not to spend time on it.
!isInternal && !isInternal &&
describe("range", () => { describe("range", () => {
it.only("successfully finds a row", () => it("successfully finds a row", () =>
expectQuery({ expectQuery({
range: { num: { low: SMALL, high: "5" } }, range: { num: { low: SMALL, high: "5" } },
}).toContainExactly([{ num: SMALL }])) }).toContainExactly([{ num: SMALL }]))
@ -635,6 +663,16 @@ describe.each([
expectQuery({ expectQuery({
range: { num: { low: "5", high: "5" } }, range: { num: { low: "5", high: "5" } },
}).toFindNothing()) }).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( addFilters(
query: Knex.QueryBuilder, query: Knex.QueryBuilder,
filters: SearchFilters | undefined, filters: SearchFilters | undefined,
tableName: string, table: Table,
opts: { aliases?: Record<string, string>; relationship?: boolean } opts: { aliases?: Record<string, string>; relationship?: boolean }
): Knex.QueryBuilder { ): Knex.QueryBuilder {
function getTableName(name: string) { function getTableName(name: string) {
@ -161,7 +161,7 @@ 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(`${getTableName(tableName)}.${updatedKey}`, value) fn(`${getTableName(table.name)}.${updatedKey}`, value)
} }
if (opts.relationship && isRelationshipField) { if (opts.relationship && isRelationshipField) {
const [filterTableName, property] = updatedKey.split(".") const [filterTableName, property] = updatedKey.split(".")
@ -276,6 +276,9 @@ class InternalBuilder {
} }
if (filters.range) { if (filters.range) {
iterate(filters.range, (key, value) => { iterate(filters.range, (key, value) => {
const fieldName = key.split(".")[1]
const field = table.schema[fieldName]
const isEmptyObject = (val: any) => { const isEmptyObject = (val: any) => {
return ( return (
val && val &&
@ -293,16 +296,46 @@ class InternalBuilder {
highValid = isValidFilter(value.high) highValid = isValidFilter(value.high)
if (lowValid && highValid) { if (lowValid && highValid) {
// Use a between operator if we have 2 valid range values // Use a between operator if we have 2 valid range values
const fnc = allOr ? "orWhereBetween" : "whereBetween" if (
query = query[fnc](key, [value.low, value.high]) 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) { } else if (lowValid) {
// Use just a single greater than operator if we only have a low // Use just a single greater than operator if we only have a low
const fnc = allOr ? "orWhere" : "where" if (
query = query[fnc](key, ">", value.low) 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) { } else if (highValid) {
// Use just a single less than operator if we only have a high // Use just a single less than operator if we only have a high
const fnc = allOr ? "orWhere" : "where" if (
query = query[fnc](key, "<", value.high) 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) { if (foundOffset) {
query = query.offset(foundOffset) query = query.offset(foundOffset)
} }
query = this.addFilters(query, filters, tableName, { query = this.addFilters(query, filters, json.meta?.table!, {
aliases: tableAliases, aliases: tableAliases,
}) })
// add sorting to pre-query // add sorting to pre-query
@ -553,7 +586,7 @@ class InternalBuilder {
endpoint.schema, endpoint.schema,
tableAliases tableAliases
) )
return this.addFilters(query, filters, tableName, { return this.addFilters(query, filters, json.meta?.table!, {
relationship: true, relationship: true,
aliases: tableAliases, aliases: tableAliases,
}) })
@ -563,7 +596,7 @@ class InternalBuilder {
const { endpoint, body, filters, tableAliases } = json const { endpoint, body, filters, tableAliases } = json
let query = this.knexWithAlias(knex, endpoint, tableAliases) let query = this.knexWithAlias(knex, endpoint, tableAliases)
const parsedBody = parseBody(body) const parsedBody = parseBody(body)
query = this.addFilters(query, filters, endpoint.entityId, { query = this.addFilters(query, filters, json.meta?.table!, {
aliases: tableAliases, aliases: tableAliases,
}) })
// mysql can't use returning // mysql can't use returning
@ -577,7 +610,7 @@ class InternalBuilder {
delete(knex: Knex, json: QueryJson, opts: QueryOptions): Knex.QueryBuilder { delete(knex: Knex, json: QueryJson, opts: QueryOptions): Knex.QueryBuilder {
const { endpoint, filters, tableAliases } = json const { endpoint, filters, tableAliases } = json
let query = this.knexWithAlias(knex, endpoint, tableAliases) let query = this.knexWithAlias(knex, endpoint, tableAliases)
query = this.addFilters(query, filters, endpoint.entityId, { query = this.addFilters(query, filters, json.meta?.table!, {
aliases: tableAliases, aliases: tableAliases,
}) })
// mysql can't use returning // mysql can't use returning

View File

@ -185,6 +185,6 @@ export async function search(
} }
} catch (err: any) { } catch (err: any) {
const msg = typeof err === "string" ? err : err.message 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 [key: string]: string
} }
range?: { range?: {
[key: string]: { [key: string]:
high: number | string | {
low: number | string high: number | string
} low: number | string
}
| { high: number | string }
| { low: number | string }
} }
equal?: { equal?: {
[key: string]: any [key: string]: any