Fix date-only searching.
This commit is contained in:
parent
b85b2426ed
commit
26c0861e68
|
@ -32,8 +32,12 @@ export async function errorHandling(ctx: any, next: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (environment.isTest() && ctx.headers["x-budibase-include-stacktrace"]) {
|
if (environment.isTest() && ctx.headers["x-budibase-include-stacktrace"]) {
|
||||||
|
let rootErr = err
|
||||||
|
while (rootErr.cause) {
|
||||||
|
rootErr = rootErr.cause
|
||||||
|
}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
error.stack = err.stack
|
error.stack = rootErr.stack
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.body = error
|
ctx.body = error
|
||||||
|
|
|
@ -816,14 +816,29 @@ class InternalBuilder {
|
||||||
filters.oneOf,
|
filters.oneOf,
|
||||||
ArrayOperator.ONE_OF,
|
ArrayOperator.ONE_OF,
|
||||||
(q, key: string, array) => {
|
(q, key: string, array) => {
|
||||||
|
const schema = this.getFieldSchema(key)
|
||||||
|
const values = Array.isArray(array) ? array : [array]
|
||||||
if (shouldOr) {
|
if (shouldOr) {
|
||||||
q = q.or
|
q = q.or
|
||||||
}
|
}
|
||||||
if (this.client === SqlClient.ORACLE) {
|
if (this.client === SqlClient.ORACLE) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
key = this.convertClobs(key)
|
key = this.convertClobs(key)
|
||||||
|
} else if (
|
||||||
|
this.client === SqlClient.SQL_LITE &&
|
||||||
|
schema?.type === FieldType.DATETIME &&
|
||||||
|
schema.dateOnly
|
||||||
|
) {
|
||||||
|
for (const value of values) {
|
||||||
|
if (value != null) {
|
||||||
|
q = q.or.whereLike(key, `${value.toISOString().slice(0, 10)}%`)
|
||||||
|
} else {
|
||||||
|
q = q.or.whereNull(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return q
|
||||||
}
|
}
|
||||||
return q.whereIn(key, Array.isArray(array) ? array : [array])
|
return q.whereIn(key, values)
|
||||||
},
|
},
|
||||||
(q, key: string[], array) => {
|
(q, key: string[], array) => {
|
||||||
if (shouldOr) {
|
if (shouldOr) {
|
||||||
|
@ -882,6 +897,19 @@ class InternalBuilder {
|
||||||
let high = value.high
|
let high = value.high
|
||||||
let low = value.low
|
let low = value.low
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.client === SqlClient.SQL_LITE &&
|
||||||
|
schema?.type === FieldType.DATETIME &&
|
||||||
|
schema.dateOnly
|
||||||
|
) {
|
||||||
|
if (high != null) {
|
||||||
|
high = `${high.toISOString().slice(0, 10)}T23:59:59.999Z`
|
||||||
|
}
|
||||||
|
if (low != null) {
|
||||||
|
low = low.toISOString().slice(0, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.client === SqlClient.ORACLE) {
|
if (this.client === SqlClient.ORACLE) {
|
||||||
rawKey = this.convertClobs(key)
|
rawKey = this.convertClobs(key)
|
||||||
} else if (
|
} else if (
|
||||||
|
@ -914,6 +942,7 @@ class InternalBuilder {
|
||||||
}
|
}
|
||||||
if (filters.equal) {
|
if (filters.equal) {
|
||||||
iterate(filters.equal, BasicOperator.EQUAL, (q, key, value) => {
|
iterate(filters.equal, BasicOperator.EQUAL, (q, key, value) => {
|
||||||
|
const schema = this.getFieldSchema(key)
|
||||||
if (shouldOr) {
|
if (shouldOr) {
|
||||||
q = q.or
|
q = q.or
|
||||||
}
|
}
|
||||||
|
@ -928,6 +957,16 @@ class InternalBuilder {
|
||||||
// @ts-expect-error knex types are wrong, raw is fine here
|
// @ts-expect-error knex types are wrong, raw is fine here
|
||||||
subq.whereNotNull(identifier).andWhere(identifier, value)
|
subq.whereNotNull(identifier).andWhere(identifier, value)
|
||||||
)
|
)
|
||||||
|
} else if (
|
||||||
|
this.client === SqlClient.SQL_LITE &&
|
||||||
|
schema?.type === FieldType.DATETIME &&
|
||||||
|
schema.dateOnly
|
||||||
|
) {
|
||||||
|
if (value != null) {
|
||||||
|
return q.whereLike(key, `${value.toISOString().slice(0, 10)}%`)
|
||||||
|
} else {
|
||||||
|
return q.whereNull(key)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return q.whereRaw(`COALESCE(?? = ?, FALSE)`, [
|
return q.whereRaw(`COALESCE(?? = ?, FALSE)`, [
|
||||||
this.rawQuotedIdentifier(key),
|
this.rawQuotedIdentifier(key),
|
||||||
|
@ -938,6 +977,7 @@ class InternalBuilder {
|
||||||
}
|
}
|
||||||
if (filters.notEqual) {
|
if (filters.notEqual) {
|
||||||
iterate(filters.notEqual, BasicOperator.NOT_EQUAL, (q, key, value) => {
|
iterate(filters.notEqual, BasicOperator.NOT_EQUAL, (q, key, value) => {
|
||||||
|
const schema = this.getFieldSchema(key)
|
||||||
if (shouldOr) {
|
if (shouldOr) {
|
||||||
q = q.or
|
q = q.or
|
||||||
}
|
}
|
||||||
|
@ -959,6 +999,18 @@ class InternalBuilder {
|
||||||
// @ts-expect-error knex types are wrong, raw is fine here
|
// @ts-expect-error knex types are wrong, raw is fine here
|
||||||
.or.whereNull(identifier)
|
.or.whereNull(identifier)
|
||||||
)
|
)
|
||||||
|
} else if (
|
||||||
|
this.client === SqlClient.SQL_LITE &&
|
||||||
|
schema?.type === FieldType.DATETIME &&
|
||||||
|
schema.dateOnly
|
||||||
|
) {
|
||||||
|
if (value != null) {
|
||||||
|
return q.not
|
||||||
|
.whereLike(key, `${value.toISOString().slice(0, 10)}%`)
|
||||||
|
.or.whereNull(key)
|
||||||
|
} else {
|
||||||
|
return q.not.whereNull(key)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return q.whereRaw(`COALESCE(?? != ?, TRUE)`, [
|
return q.whereRaw(`COALESCE(?? != ?, TRUE)`, [
|
||||||
this.rawQuotedIdentifier(key),
|
this.rawQuotedIdentifier(key),
|
||||||
|
|
|
@ -14,7 +14,7 @@ import environment from "../environment"
|
||||||
const DOUBLE_SEPARATOR = `${SEPARATOR}${SEPARATOR}`
|
const DOUBLE_SEPARATOR = `${SEPARATOR}${SEPARATOR}`
|
||||||
const ROW_ID_REGEX = /^\[.*]$/g
|
const ROW_ID_REGEX = /^\[.*]$/g
|
||||||
const ENCODED_SPACE = encodeURIComponent(" ")
|
const ENCODED_SPACE = encodeURIComponent(" ")
|
||||||
const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/
|
const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}.\d{3}Z)?$/
|
||||||
const TIME_REGEX = /^(?:\d{2}:)?(?:\d{2}:)(?:\d{2})$/
|
const TIME_REGEX = /^(?:\d{2}:)?(?:\d{2}:)(?:\d{2})$/
|
||||||
|
|
||||||
export function isExternalTableID(tableId: string) {
|
export function isExternalTableID(tableId: string) {
|
||||||
|
@ -149,15 +149,7 @@ export function isInvalidISODateString(str: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isValidISODateString(str: string) {
|
export function isValidISODateString(str: string) {
|
||||||
const trimmedValue = str.trim()
|
return ISO_DATE_REGEX.test(str.trim())
|
||||||
if (!ISO_DATE_REGEX.test(trimmedValue)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
let d = new Date(trimmedValue)
|
|
||||||
if (isNaN(d.getTime())) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return d.toISOString() === trimmedValue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isValidFilter(value: any) {
|
export function isValidFilter(value: any) {
|
||||||
|
|
|
@ -1683,6 +1683,112 @@ if (descriptions.length) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe.only("datetime - date only", () => {
|
||||||
|
const JAN_1ST = "2020-01-01"
|
||||||
|
const JAN_10TH = "2020-01-10"
|
||||||
|
const JAN_30TH = "2020-01-30"
|
||||||
|
const UNEXISTING_DATE = "2020-01-03"
|
||||||
|
const NULL_DATE__ID = `null_date__id`
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
tableOrViewId = await createTableOrView({
|
||||||
|
dateid: { name: "dateid", type: FieldType.STRING },
|
||||||
|
date: {
|
||||||
|
name: "date",
|
||||||
|
type: FieldType.DATETIME,
|
||||||
|
dateOnly: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await createRows([
|
||||||
|
{ dateid: NULL_DATE__ID, date: null },
|
||||||
|
{ date: JAN_1ST },
|
||||||
|
{ date: `${JAN_10TH}T00:00:00.000Z` },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("equal", () => {
|
||||||
|
it("successfully finds a row", async () => {
|
||||||
|
await expectQuery({
|
||||||
|
equal: { date: JAN_1ST },
|
||||||
|
}).toContainExactly([{ date: JAN_1ST }])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("successfully finds an ISO8601 row", async () => {
|
||||||
|
await expectQuery({
|
||||||
|
equal: { date: JAN_10TH },
|
||||||
|
}).toContainExactly([{ date: JAN_10TH }])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("finds a row with ISO8601 timestamp", async () => {
|
||||||
|
await expectQuery({
|
||||||
|
equal: { date: `${JAN_1ST}T00:00:00.000Z` },
|
||||||
|
}).toContainExactly([{ date: JAN_1ST }])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("fails to find nonexistent row", async () => {
|
||||||
|
await expectQuery({
|
||||||
|
equal: { date: UNEXISTING_DATE },
|
||||||
|
}).toFindNothing()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("notEqual", () => {
|
||||||
|
it("successfully finds a row", async () => {
|
||||||
|
await expectQuery({
|
||||||
|
notEqual: { date: JAN_1ST },
|
||||||
|
}).toContainExactly([
|
||||||
|
{ date: JAN_10TH },
|
||||||
|
{ dateid: NULL_DATE__ID },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("fails to find nonexistent row", async () => {
|
||||||
|
await expectQuery({
|
||||||
|
notEqual: { date: JAN_30TH },
|
||||||
|
}).toContainExactly([
|
||||||
|
{ date: JAN_1ST },
|
||||||
|
{ date: JAN_10TH },
|
||||||
|
{ dateid: NULL_DATE__ID },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("oneOf", () => {
|
||||||
|
it("successfully finds a row", async () => {
|
||||||
|
await expectQuery({
|
||||||
|
oneOf: { date: [JAN_1ST] },
|
||||||
|
}).toContainExactly([{ date: JAN_1ST }])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("fails to find nonexistent row", async () => {
|
||||||
|
await expectQuery({
|
||||||
|
oneOf: { date: [UNEXISTING_DATE] },
|
||||||
|
}).toFindNothing()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("range", () => {
|
||||||
|
it("successfully finds a row", async () => {
|
||||||
|
await expectQuery({
|
||||||
|
range: { date: { low: JAN_1ST, high: JAN_1ST } },
|
||||||
|
}).toContainExactly([{ date: JAN_1ST }])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("successfully finds multiple rows", async () => {
|
||||||
|
await expectQuery({
|
||||||
|
range: { date: { low: JAN_1ST, high: JAN_10TH } },
|
||||||
|
}).toContainExactly([{ date: JAN_1ST }, { date: JAN_10TH }])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("successfully finds no rows", async () => {
|
||||||
|
await expectQuery({
|
||||||
|
range: { date: { low: JAN_30TH, high: JAN_30TH } },
|
||||||
|
}).toFindNothing()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
isInternal &&
|
isInternal &&
|
||||||
!isInMemory &&
|
!isInMemory &&
|
||||||
describe("AI Column", () => {
|
describe("AI Column", () => {
|
||||||
|
|
|
@ -411,6 +411,15 @@ export async function coreOutputProcessing(
|
||||||
row[property] = `${hours}:${minutes}:${seconds}`
|
row[property] = `${hours}:${minutes}:${seconds}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (column.type === FieldType.DATETIME && column.dateOnly) {
|
||||||
|
for (const row of rows) {
|
||||||
|
if (typeof row[property] === "string") {
|
||||||
|
row[property] = new Date(row[property])
|
||||||
|
}
|
||||||
|
if (row[property] instanceof Date) {
|
||||||
|
row[property] = row[property].toISOString().slice(0, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (column.type === FieldType.LINK) {
|
} else if (column.type === FieldType.LINK) {
|
||||||
for (let row of rows) {
|
for (let row of rows) {
|
||||||
// if relationship is empty - remove the array, this has been part of the API for some time
|
// if relationship is empty - remove the array, this has been part of the API for some time
|
||||||
|
|
|
@ -699,7 +699,27 @@ export function runQuery<T extends Record<string, any>>(
|
||||||
return docValue._id === testValue
|
return docValue._id === testValue
|
||||||
}
|
}
|
||||||
|
|
||||||
return docValue === testValue
|
if (docValue === testValue) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (docValue == null && testValue != null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (docValue != null && testValue == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const leftDate = dayjs(docValue)
|
||||||
|
if (leftDate.isValid()) {
|
||||||
|
const rightDate = dayjs(testValue)
|
||||||
|
if (rightDate.isValid()) {
|
||||||
|
return leftDate.isSame(rightDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const not =
|
const not =
|
||||||
|
|
Loading…
Reference in New Issue