Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion packages/backend-core/src/sql/tests/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isValidISODateString } from "../utils"
import { isValidISODateString, isValidTime } from "../utils"

describe("ISO date string validity checks", () => {
it("accepts a valid ISO date string without a time", () => {
Expand All @@ -25,3 +25,16 @@ describe("ISO date string validity checks", () => {
expect(valid).toEqual(false)
})
})

describe("time string validity checks", () => {
it.each(["10:14", "10:14:43", "10:14.431534", "10:14:43.431534"])(
"accepts %s",
value => {
expect(isValidTime(value)).toEqual(true)
}
)

it.each(["10:14:431534"])("rejects %s", value => {
expect(isValidTime(value)).toEqual(false)
})
})
2 changes: 1 addition & 1 deletion packages/backend-core/src/sql/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const ISO_DATE_REGEX =
const ISO_DATE_REGEX_NO_TIMEZONE =
/^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}:\d{2}(?:.\d{3})?)?$/
const DATE_REGEX = /(\d{4}-\d{2}-\d{2})/
const TIME_REGEX = /^(?:\d{2}:)?(?:\d{2}:)(?:\d{2})$/
const TIME_REGEX = /^\d{1,2}:\d{2}(?:\.\d{1,6}|:\d{2}(?:\.\d{1,6})?)?$/

export function isExternalTableID(tableId: string) {
return tableId.startsWith(DocumentType.DATASOURCE + SEPARATOR)
Expand Down
24 changes: 24 additions & 0 deletions packages/server/src/api/routes/tests/row.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4285,6 +4285,30 @@ if (descriptions.length) {
const fetchedRow = await config.api.row.get(table._id!, row._id!)
expect(fetchedRow.date).toEqual("2023-01-01T00:00:00.000")
})

if (isSql) {
it("should accept time-only values without seconds for SQL datasources", async () => {
const table = await config.api.table.save(
saveTableRequest({
schema: {
time: {
name: "time",
type: FieldType.DATETIME,
timeOnly: true,
},
},
})
)

const row = await config.api.row.save(table._id!, {
time: "09:30",
})
expect(row.time).toEqual("09:30:00")

const fetchedRow = await config.api.row.get(table._id!, row._id!)
expect(fetchedRow.time).toEqual("09:30:00")
})
}
})

if (isInternal || isMSSQL) {
Expand Down
4 changes: 3 additions & 1 deletion packages/server/src/integrations/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,9 @@ export function generateColumnDefinition(config: {
}
if (schema.type === FieldType.DATETIME) {
schema.dateOnly = SQL_DATE_ONLY_TYPES.includes(lowerCaseType)
schema.timeOnly = SQL_TIME_ONLY_TYPES.includes(lowerCaseType)
schema.timeOnly =
SQL_TIME_ONLY_TYPES.includes(lowerCaseType) ||
lowerCaseType.startsWith("time(")
}

// Set subtype for Postgres array types
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/sdk/workspace/rows/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ function validateTimeOnlyField(
constraints: FieldConstraints | undefined
) {
let res
if (value && !value.match(/^(\d+)(:[0-5]\d){1,2}$/)) {
if (value && !sql.utils.isValidTime(value)) {
res = [`"${fieldName}" is not a valid time`]
} else if (constraints) {
let castedValue = value
Expand Down
43 changes: 43 additions & 0 deletions packages/server/src/tests/integrations/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FieldType } from "@budibase/types"
import type { DateFieldMetadata } from "@budibase/types"

import { generateColumnDefinition } from "../../integrations/utils/utils"

Expand Down Expand Up @@ -36,3 +37,45 @@ describe("generateColumnDefinition tinyint handling", () => {
expect(schema.type).toEqual(FieldType.NUMBER)
})
})

describe("generateColumnDefinition time handling", () => {
const baseConfig = {
autocolumn: false,
name: "col",
presence: false,
}

it("marks time columns as time-only", () => {
const schema = generateColumnDefinition({
...baseConfig,
externalType: "time",
})
const dateSchema = schema as DateFieldMetadata

expect(dateSchema.type).toEqual(FieldType.DATETIME)
expect(dateSchema.timeOnly).toBe(true)
expect(dateSchema.dateOnly).toBe(false)
})

it("marks time with precision columns as time-only", () => {
const schema = generateColumnDefinition({
...baseConfig,
externalType: "time(6)",
})
const dateSchema = schema as DateFieldMetadata

expect(dateSchema.type).toEqual(FieldType.DATETIME)
expect(dateSchema.timeOnly).toBe(true)
})

it("does not treat timestamp as time-only", () => {
const schema = generateColumnDefinition({
...baseConfig,
externalType: "timestamp",
})
const dateSchema = schema as DateFieldMetadata

expect(dateSchema.type).toEqual(FieldType.DATETIME)
expect(dateSchema.timeOnly).toBe(false)
})
})
Loading