Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
49 changes: 36 additions & 13 deletions packages/backend-core/src/sql/sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ function wrap(value: string, quoteChar = '"'): string {
return `${quoteChar}${escapeQuotes(value, quoteChar)}${quoteChar}`
}

function stringifyArray(value: any[], quoteStyle = '"'): string {
for (let i in value) {
function stringifyArray(value: unknown[], quoteStyle = '"'): string {
for (const i in value) {
if (typeof value[i] === "string") {
value[i] = wrap(value[i], quoteStyle)
}
Expand Down Expand Up @@ -162,6 +162,12 @@ class InternalBuilder {

// states the various situations in which we need a full mapped select statement
private readonly SPECIAL_SELECT_CASES = {
POSTGRES_ARRAY: (field: FieldSchema | undefined) => {
return (
this.client === SqlClient.POSTGRES &&
field?.externalType?.toLowerCase() === "array"
)
},
POSTGRES_MONEY: (field: FieldSchema | undefined) => {
return (
this.client === SqlClient.POSTGRES &&
Expand Down Expand Up @@ -233,11 +239,6 @@ class InternalBuilder {
return key.map(part => this.quote(part)).join(".")
}

private quotedValue(value: string): string {
const formatter = this.knexClient.formatter(this.knexClient.queryBuilder())
return formatter.wrap(value, false)
}

private castIntToString(identifier: string | Knex.Raw): Knex.Raw {
switch (this.client) {
case SqlClient.ORACLE: {
Expand Down Expand Up @@ -409,6 +410,10 @@ class InternalBuilder {
return JSON.stringify(input)
}

if (this.SPECIAL_SELECT_CASES.POSTGRES_ARRAY(schema)) {
return `{${input}}`
}

if (
this.client === SqlClient.ORACLE &&
schema.type === FieldType.DATETIME &&
Expand Down Expand Up @@ -766,15 +771,33 @@ class InternalBuilder {
if (this.client === SqlClient.POSTGRES) {
iterate(mode, ArrayOperator.CONTAINS, (q, key, value) => {
q = addModifiers(q)
const schema = this.getFieldSchema(key)
let cast = "::jsonb"
if (this.SPECIAL_SELECT_CASES.POSTGRES_ARRAY(schema)) {
cast = ""
const values = (value as string[]).map(value =>
value.substring(1, value.length - 1)
)
value = `{${values}}`
}
if (any) {
return q.whereRaw(`COALESCE(??::jsonb \\?| array??, FALSE)`, [
this.rawQuotedIdentifier(key),
this.knex.raw(stringifyArray(value, "'")),
])
return q.whereRaw(
cast
? `COALESCE(??::jsonb \\?| array??`
: `COALESCE(?? && '??', FALSE)`,
[
this.rawQuotedIdentifier(key),
cast
? this.knex.raw(stringifyArray(value))
: this.knex.raw(value),
]
)
} else {
return q.whereRaw(`COALESCE(??::jsonb @> '??', FALSE)`, [
return q.whereRaw(`COALESCE(??${cast} @> '??', FALSE)`, [
this.rawQuotedIdentifier(key),
this.knex.raw(stringifyArray(value)),
cast
? this.knex.raw(stringifyArray(value))
: this.knex.raw(value),
])
}
})
Expand Down
6 changes: 3 additions & 3 deletions packages/server/src/integrations/postgres.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ import { env, sql } from "@budibase/backend-core"
// This lets us reference the original stored timezone.
// types is undefined when running in a test env for some reason.
if (types) {
types.setTypeParser(1114, (val: any) => val) // timestamp
types.setTypeParser(1082, (val: any) => val) // date
types.setTypeParser(1184, (val: any) => val) // timestampz
types.setTypeParser(1114, (val: unknown) => val) // timestamp
types.setTypeParser(1082, (val: unknown) => val) // date
types.setTypeParser(1184, (val: unknown) => val) // timestampz
}

const JSON_REGEX = /'{\s*.*?\s*}'::json/gs
Expand Down
16 changes: 12 additions & 4 deletions packages/server/src/integrations/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Datasource,
FieldType,
FieldSchema,
StringFieldSubType,
} from "@budibase/types"
import { context, objectStore, sql } from "@budibase/backend-core"
import { v4 } from "uuid"
Expand Down Expand Up @@ -73,7 +74,7 @@ const SQL_TIME_ONLY_TYPES = [
"time with time zone",
]

const SQL_STRING_TYPE_MAP: Record<string, PrimitiveTypes> = {
const SQL_STRING_TYPE_MAP: Record<string, FieldType.STRING> = {
varchar: FieldType.STRING,
char: FieldType.STRING,
nchar: FieldType.STRING,
Expand All @@ -83,15 +84,16 @@ const SQL_STRING_TYPE_MAP: Record<string, PrimitiveTypes> = {
blob: FieldType.STRING,
long: FieldType.STRING,
text: FieldType.STRING,
array: FieldType.STRING,
}

const SQL_BOOLEAN_TYPE_MAP: Record<string, PrimitiveTypes> = {
const SQL_BOOLEAN_TYPE_MAP: Record<string, FieldType.BOOLEAN> = {
boolean: FieldType.BOOLEAN,
bit: FieldType.BOOLEAN,
tinyint: FieldType.BOOLEAN,
}

const SQL_OPTIONS_TYPE_MAP: Record<string, PrimitiveTypes> = {
const SQL_OPTIONS_TYPE_MAP: Record<string, FieldType.OPTIONS> = {
"user-defined": FieldType.OPTIONS,
}

Expand Down Expand Up @@ -178,7 +180,7 @@ export function generateColumnDefinition(config: {
name,
constraints: {
presence,
inclusion: options!,
inclusion: options ?? [],
},
}
} else {
Expand All @@ -196,6 +198,12 @@ export function generateColumnDefinition(config: {
schema.dateOnly = SQL_DATE_ONLY_TYPES.includes(lowerCaseType)
schema.timeOnly = SQL_TIME_ONLY_TYPES.includes(lowerCaseType)
}

// Set subtype for Postgres array types
if (schema.type === FieldType.STRING && lowerCaseType === "array") {
schema.subtype = StringFieldSubType.ARRAY
}

return schema
}

Expand Down
24 changes: 18 additions & 6 deletions packages/shared-core/src/filters.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {
ArrayOperator,
BasicOperator,
BBReferenceFieldSubType,
Datasource,
EmptyFilterOption,
FieldConstraints,
FieldType,
FieldSubType,
FormulaType,
isArraySearchOperator,
isBasicSearchOperator,
Expand All @@ -25,6 +25,7 @@ import {
Table,
UILogicalOperator,
UISearchFilter,
StringFieldSubType,
} from "@budibase/types"
import dayjs from "dayjs"
import { OperatorOptions, SqlNumberTypeRangeMap } from "./constants"
Expand All @@ -47,7 +48,7 @@ const SEARCH_OPERATORS = [
export const getValidOperatorsForType = (
fieldType: {
type: FieldType
subtype?: BBReferenceFieldSubType
subtype?: FieldSubType
formulaType?: FormulaType
constraints?: FieldConstraints
},
Expand All @@ -73,19 +74,30 @@ export const getValidOperatorsForType = (
Op.NotEmpty,
Op.In,
]
const arrayOps = [
Op.Contains,
Op.NotContains,
Op.ContainsAny,
Op.Empty,
Op.NotEmpty,
]
let ops: {
value: string
label: string
}[] = []
const { type, formulaType } = fieldType
const { type, formulaType, subtype } = fieldType
if (type === FieldType.STRING) {
ops = stringOps
if (subtype === StringFieldSubType.ARRAY) {
ops = arrayOps
} else {
ops = stringOps
}
} else if (type === FieldType.NUMBER || type === FieldType.BIGINT) {
ops = numOps
} else if (type === FieldType.OPTIONS) {
ops = [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty, Op.In]
} else if (type === FieldType.ARRAY) {
ops = [Op.Contains, Op.NotContains, Op.Empty, Op.NotEmpty, Op.ContainsAny]
ops = arrayOps
} else if (type === FieldType.BOOLEAN) {
ops = [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty]
} else if (type === FieldType.LONGFORM) {
Expand All @@ -102,7 +114,7 @@ export const getValidOperatorsForType = (
) {
ops = [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty, Op.In]
} else if (type === FieldType.BB_REFERENCE) {
ops = [Op.Contains, Op.NotContains, Op.ContainsAny, Op.Empty, Op.NotEmpty]
ops = arrayOps
} else if (type === FieldType.BARCODEQR) {
ops = stringOps
}
Expand Down
3 changes: 2 additions & 1 deletion packages/shared-core/src/helpers/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import {
BBReferenceFieldSubType,
FieldConstraints,
FieldSchema,
FieldSubType,
FieldType,
} from "@budibase/types"

export function isDeprecatedSingleUserColumn(
schema: Pick<FieldSchema, "type" | "subtype" | "constraints">
): schema is {
type: FieldType.BB_REFERENCE
subtype: BBReferenceFieldSubType.USER
subtype: FieldSubType
} {
const result =
schema.type === FieldType.BB_REFERENCE &&
Expand Down
9 changes: 7 additions & 2 deletions packages/types/src/documents/app/table/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ export enum AutoReason {

export type FieldSubType =
| AutoFieldSubType
| JsonFieldSubType
| BBReferenceFieldSubType
| AttachmentSubType
| BBReferenceFieldSubType
| JsonFieldSubType
| StringFieldSubType

export enum AutoFieldSubType {
CREATED_BY = "createdBy",
Expand Down Expand Up @@ -44,6 +45,10 @@ export enum AttachmentSubType {
IMAGE = "image",
}

export enum StringFieldSubType {
ARRAY = "array",
}

export type SupportedSqlTypes =
| FieldType.STRING
| FieldType.BARCODEQR
Expand Down
4 changes: 3 additions & 1 deletion packages/types/src/documents/app/table/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
FormulaType,
JsonFieldSubType,
RelationshipType,
StringFieldSubType,
} from "./constants"
import { AIOperationEnum } from "../../../sdk/ai"

Expand Down Expand Up @@ -107,8 +108,9 @@ export interface LongFormFieldMetadata extends BaseFieldSchema {
default?: string
}

export interface StringFieldMetadata extends BaseFieldSchema {
export interface StringFieldMetadata extends Omit<BaseFieldSchema, "subtype"> {
type: FieldType.STRING
subtype?: StringFieldSubType.ARRAY
default?: string
}

Expand Down
Loading