Merge branch 'master' of github.com:Budibase/budibase into grid-conflict-improvements
This commit is contained in:
commit
44c1a56134
|
@ -29,6 +29,7 @@ services:
|
||||||
BB_ADMIN_USER_EMAIL: ${BB_ADMIN_USER_EMAIL}
|
BB_ADMIN_USER_EMAIL: ${BB_ADMIN_USER_EMAIL}
|
||||||
BB_ADMIN_USER_PASSWORD: ${BB_ADMIN_USER_PASSWORD}
|
BB_ADMIN_USER_PASSWORD: ${BB_ADMIN_USER_PASSWORD}
|
||||||
PLUGINS_DIR: ${PLUGINS_DIR}
|
PLUGINS_DIR: ${PLUGINS_DIR}
|
||||||
|
SQS_SEARCH_ENABLE: 1
|
||||||
depends_on:
|
depends_on:
|
||||||
- worker-service
|
- worker-service
|
||||||
- redis-service
|
- redis-service
|
||||||
|
@ -56,6 +57,7 @@ services:
|
||||||
INTERNAL_API_KEY: ${INTERNAL_API_KEY}
|
INTERNAL_API_KEY: ${INTERNAL_API_KEY}
|
||||||
REDIS_URL: redis-service:6379
|
REDIS_URL: redis-service:6379
|
||||||
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
||||||
|
SQS_SEARCH_ENABLE: 1
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis-service
|
- redis-service
|
||||||
- minio-service
|
- minio-service
|
||||||
|
|
|
@ -42,12 +42,13 @@ services:
|
||||||
couchdb-service:
|
couchdb-service:
|
||||||
container_name: budi-couchdb3-dev
|
container_name: budi-couchdb3-dev
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
image: budibase/couchdb
|
image: budibase/couchdb:v3.2.1-sqs
|
||||||
environment:
|
environment:
|
||||||
- COUCHDB_PASSWORD=${COUCH_DB_PASSWORD}
|
- COUCHDB_PASSWORD=${COUCH_DB_PASSWORD}
|
||||||
- COUCHDB_USER=${COUCH_DB_USER}
|
- COUCHDB_USER=${COUCH_DB_USER}
|
||||||
ports:
|
ports:
|
||||||
- "${COUCH_DB_PORT}:5984"
|
- "${COUCH_DB_PORT}:5984"
|
||||||
|
- "${COUCH_DB_SQS_PORT}:4984"
|
||||||
volumes:
|
volumes:
|
||||||
- couchdb_data:/data
|
- couchdb_data:/data
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
ARG BASEIMG=budibase/couchdb:v3.3.3
|
||||||
FROM node:20-slim as build
|
FROM node:20-slim as build
|
||||||
|
|
||||||
# install node-gyp dependencies
|
# install node-gyp dependencies
|
||||||
|
@ -32,7 +33,7 @@ COPY packages/worker/dist packages/worker/dist
|
||||||
COPY packages/worker/pm2.config.js packages/worker/pm2.config.js
|
COPY packages/worker/pm2.config.js packages/worker/pm2.config.js
|
||||||
|
|
||||||
|
|
||||||
FROM budibase/couchdb:v3.3.3 as runner
|
FROM $BASEIMG as runner
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ENV TARGETARCH $TARGETARCH
|
ENV TARGETARCH $TARGETARCH
|
||||||
#TARGETBUILD can be set to single (for single docker image) or aas (for azure app service)
|
#TARGETBUILD can be set to single (for single docker image) or aas (for azure app service)
|
||||||
|
|
|
@ -35,6 +35,8 @@
|
||||||
"get-past-client-version": "node scripts/getPastClientVersion.js",
|
"get-past-client-version": "node scripts/getPastClientVersion.js",
|
||||||
"setup": "git config submodule.recurse true && git submodule update && node ./hosting/scripts/setup.js && yarn && yarn build && yarn dev",
|
"setup": "git config submodule.recurse true && git submodule update && node ./hosting/scripts/setup.js && yarn && yarn build && yarn dev",
|
||||||
"build": "NODE_OPTIONS=--max-old-space-size=1500 lerna run build --stream",
|
"build": "NODE_OPTIONS=--max-old-space-size=1500 lerna run build --stream",
|
||||||
|
"build:apps": "yarn build --scope @budibase/server --scope @budibase/worker",
|
||||||
|
"build:cli": "yarn build --scope @budibase/cli",
|
||||||
"build:oss": "NODE_OPTIONS=--max-old-space-size=1500 lerna run build --stream --ignore @budibase/account-portal --ignore @budibase/account-portal-server --ignore @budibase/account-portal-ui",
|
"build:oss": "NODE_OPTIONS=--max-old-space-size=1500 lerna run build --stream --ignore @budibase/account-portal --ignore @budibase/account-portal-server --ignore @budibase/account-portal-ui",
|
||||||
"build:account-portal": "NODE_OPTIONS=--max-old-space-size=1500 lerna run build --stream --scope @budibase/account-portal --scope @budibase/account-portal-server --scope @budibase/account-portal-ui",
|
"build:account-portal": "NODE_OPTIONS=--max-old-space-size=1500 lerna run build --stream --scope @budibase/account-portal --scope @budibase/account-portal-server --scope @budibase/account-portal-ui",
|
||||||
"build:dev": "lerna run --stream prebuild && yarn nx run-many --target=build --output-style=dynamic --watch --preserveWatchOutput",
|
"build:dev": "lerna run --stream prebuild && yarn nx run-many --target=build --output-style=dynamic --watch --preserveWatchOutput",
|
||||||
|
@ -73,6 +75,7 @@
|
||||||
"build:digitalocean": "cd hosting/digitalocean && ./build.sh && cd -",
|
"build:digitalocean": "cd hosting/digitalocean && ./build.sh && cd -",
|
||||||
"build:docker:single:multiarch": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/single/Dockerfile -t budibase:latest .",
|
"build:docker:single:multiarch": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/single/Dockerfile -t budibase:latest .",
|
||||||
"build:docker:single": "./scripts/build-single-image.sh",
|
"build:docker:single": "./scripts/build-single-image.sh",
|
||||||
|
"build:docker:single:sqs": "./scripts/build-single-image-sqs.sh",
|
||||||
"build:docker:dependencies": "docker build -f hosting/dependencies/Dockerfile -t budibase/dependencies:latest ./hosting",
|
"build:docker:dependencies": "docker build -f hosting/dependencies/Dockerfile -t budibase/dependencies:latest ./hosting",
|
||||||
"publish:docker:couch": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/couchdb/Dockerfile -t budibase/couchdb:latest -t budibase/couchdb:v3.3.3 --push ./hosting/couchdb",
|
"publish:docker:couch": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/couchdb/Dockerfile -t budibase/couchdb:latest -t budibase/couchdb:v3.3.3 --push ./hosting/couchdb",
|
||||||
"publish:docker:couch-sqs": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/couchdb/Dockerfile.v2 -t budibase/couchdb:v3.3.3-sqs --push ./hosting/couchdb",
|
"publish:docker:couch-sqs": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/couchdb/Dockerfile.v2 -t budibase/couchdb:v3.3.3-sqs --push ./hosting/couchdb",
|
||||||
|
|
|
@ -54,7 +54,8 @@
|
||||||
"sanitize-s3-objectkey": "0.0.1",
|
"sanitize-s3-objectkey": "0.0.1",
|
||||||
"semver": "^7.5.4",
|
"semver": "^7.5.4",
|
||||||
"tar-fs": "2.1.1",
|
"tar-fs": "2.1.1",
|
||||||
"uuid": "^8.3.2"
|
"uuid": "^8.3.2",
|
||||||
|
"knex": "2.4.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@shopify/jest-koa-mocks": "5.1.1",
|
"@shopify/jest-koa-mocks": "5.1.1",
|
||||||
|
|
|
@ -65,5 +65,11 @@ export const StaticDatabases = {
|
||||||
export const APP_PREFIX = prefixed(DocumentType.APP)
|
export const APP_PREFIX = prefixed(DocumentType.APP)
|
||||||
export const APP_DEV = prefixed(DocumentType.APP_DEV)
|
export const APP_DEV = prefixed(DocumentType.APP_DEV)
|
||||||
export const APP_DEV_PREFIX = APP_DEV
|
export const APP_DEV_PREFIX = APP_DEV
|
||||||
|
export const SQS_DATASOURCE_INTERNAL = "internal"
|
||||||
export const BUDIBASE_DATASOURCE_TYPE = "budibase"
|
export const BUDIBASE_DATASOURCE_TYPE = "budibase"
|
||||||
export const SQLITE_DESIGN_DOC_ID = "_design/sqlite"
|
export const SQLITE_DESIGN_DOC_ID = "_design/sqlite"
|
||||||
|
export const DEFAULT_JOBS_TABLE_ID = "ta_bb_jobs"
|
||||||
|
export const DEFAULT_INVENTORY_TABLE_ID = "ta_bb_inventory"
|
||||||
|
export const DEFAULT_EXPENSES_TABLE_ID = "ta_bb_expenses"
|
||||||
|
export const DEFAULT_EMPLOYEE_TABLE_ID = "ta_bb_employee"
|
||||||
|
export const DEFAULT_BB_DATASOURCE_ID = "datasource_internal_bb_default"
|
||||||
|
|
|
@ -159,6 +159,9 @@ const environment = {
|
||||||
process.env.DEPLOYMENT_ENVIRONMENT || "docker-compose",
|
process.env.DEPLOYMENT_ENVIRONMENT || "docker-compose",
|
||||||
HTTP_LOGGING: httpLogging(),
|
HTTP_LOGGING: httpLogging(),
|
||||||
ENABLE_AUDIT_LOG_IP_ADDR: process.env.ENABLE_AUDIT_LOG_IP_ADDR,
|
ENABLE_AUDIT_LOG_IP_ADDR: process.env.ENABLE_AUDIT_LOG_IP_ADDR,
|
||||||
|
// Couch/search
|
||||||
|
SQL_LOGGING_ENABLE: process.env.SQL_LOGGING_ENABLE,
|
||||||
|
SQL_MAX_ROWS: process.env.SQL_MAX_ROWS,
|
||||||
// smtp
|
// smtp
|
||||||
SMTP_FALLBACK_ENABLED: process.env.SMTP_FALLBACK_ENABLED,
|
SMTP_FALLBACK_ENABLED: process.env.SMTP_FALLBACK_ENABLED,
|
||||||
SMTP_USER: process.env.SMTP_USER,
|
SMTP_USER: process.env.SMTP_USER,
|
||||||
|
|
|
@ -34,6 +34,7 @@ export * as docUpdates from "./docUpdates"
|
||||||
export * from "./utils/Duration"
|
export * from "./utils/Duration"
|
||||||
export * as docIds from "./docIds"
|
export * as docIds from "./docIds"
|
||||||
export * as security from "./security"
|
export * as security from "./security"
|
||||||
|
export * as sql from "./sql"
|
||||||
// Add context to tenancy for backwards compatibility
|
// Add context to tenancy for backwards compatibility
|
||||||
// only do this for external usages to prevent internal
|
// only do this for external usages to prevent internal
|
||||||
// circular dependencies
|
// circular dependencies
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { PreSaveSQLiteDefinition } from "@budibase/types"
|
||||||
|
import { SQLITE_DESIGN_DOC_ID } from "../constants"
|
||||||
|
|
||||||
|
// the table id property defines which property in the document
|
||||||
|
// to use when splitting the documents into different sqlite tables
|
||||||
|
export function base(tableIdProp: string): PreSaveSQLiteDefinition {
|
||||||
|
return {
|
||||||
|
_id: SQLITE_DESIGN_DOC_ID,
|
||||||
|
language: "sqlite",
|
||||||
|
sql: {
|
||||||
|
tables: {},
|
||||||
|
options: {
|
||||||
|
table_name: tableIdProp,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export * as utils from "./utils"
|
||||||
|
|
||||||
|
export { default as Sql } from "./sql"
|
||||||
|
export { default as SqlTable } from "./sqlTable"
|
||||||
|
export * as designDoc from "./designDoc"
|
|
@ -1,13 +1,7 @@
|
||||||
import { Knex, knex } from "knex"
|
import { Knex, knex } from "knex"
|
||||||
import { db as dbCore } from "@budibase/backend-core"
|
import * as dbCore from "../db"
|
||||||
import { QueryOptions } from "../../definitions/datasource"
|
import { isIsoDateString, isValidFilter, getNativeSql } from "./utils"
|
||||||
import {
|
import { SqlStatements } from "./sqlStatements"
|
||||||
isIsoDateString,
|
|
||||||
SqlClient,
|
|
||||||
isValidFilter,
|
|
||||||
getNativeSql,
|
|
||||||
SqlStatements,
|
|
||||||
} from "../utils"
|
|
||||||
import SqlTableQueryBuilder from "./sqlTable"
|
import SqlTableQueryBuilder from "./sqlTable"
|
||||||
import {
|
import {
|
||||||
BBReferenceFieldMetadata,
|
BBReferenceFieldMetadata,
|
||||||
|
@ -24,8 +18,11 @@ import {
|
||||||
Table,
|
Table,
|
||||||
TableSourceType,
|
TableSourceType,
|
||||||
INTERNAL_TABLE_SOURCE_ID,
|
INTERNAL_TABLE_SOURCE_ID,
|
||||||
|
SqlClient,
|
||||||
|
QueryOptions,
|
||||||
|
JsonTypes,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import environment from "../../environment"
|
import environment from "../environment"
|
||||||
import { helpers } from "@budibase/shared-core"
|
import { helpers } from "@budibase/shared-core"
|
||||||
|
|
||||||
type QueryFunction = (query: SqlQuery | SqlQuery[], operation: Operation) => any
|
type QueryFunction = (query: SqlQuery | SqlQuery[], operation: Operation) => any
|
||||||
|
@ -45,6 +42,7 @@ function likeKey(client: string, key: string): string {
|
||||||
case SqlClient.MY_SQL:
|
case SqlClient.MY_SQL:
|
||||||
start = end = "`"
|
start = end = "`"
|
||||||
break
|
break
|
||||||
|
case SqlClient.SQL_LITE:
|
||||||
case SqlClient.ORACLE:
|
case SqlClient.ORACLE:
|
||||||
case SqlClient.POSTGRES:
|
case SqlClient.POSTGRES:
|
||||||
start = end = '"'
|
start = end = '"'
|
||||||
|
@ -53,9 +51,6 @@ function likeKey(client: string, key: string): string {
|
||||||
start = "["
|
start = "["
|
||||||
end = "]"
|
end = "]"
|
||||||
break
|
break
|
||||||
case SqlClient.SQL_LITE:
|
|
||||||
start = end = "'"
|
|
||||||
break
|
|
||||||
default:
|
default:
|
||||||
throw new Error("Unknown client generating like key")
|
throw new Error("Unknown client generating like key")
|
||||||
}
|
}
|
||||||
|
@ -207,17 +202,20 @@ 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(`${getTableAlias(tableName)}.${updatedKey}`, value)
|
const alias = getTableAlias(tableName)
|
||||||
|
fn(alias ? `${alias}.${updatedKey}` : updatedKey, value)
|
||||||
}
|
}
|
||||||
if (opts.relationship && isRelationshipField) {
|
if (opts.relationship && isRelationshipField) {
|
||||||
const [filterTableName, property] = updatedKey.split(".")
|
const [filterTableName, property] = updatedKey.split(".")
|
||||||
fn(`${getTableAlias(filterTableName)}.${property}`, value)
|
const alias = getTableAlias(filterTableName)
|
||||||
|
fn(alias ? `${alias}.${property}` : property, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const like = (key: string, value: any) => {
|
const like = (key: string, value: any) => {
|
||||||
const fnc = allOr ? "orWhere" : "where"
|
const fuzzyOr = filters?.fuzzyOr
|
||||||
|
const fnc = fuzzyOr || allOr ? "orWhere" : "where"
|
||||||
// postgres supports ilike, nothing else does
|
// postgres supports ilike, nothing else does
|
||||||
if (this.client === SqlClient.POSTGRES) {
|
if (this.client === SqlClient.POSTGRES) {
|
||||||
query = query[fnc](key, "ilike", `%${value}%`)
|
query = query[fnc](key, "ilike", `%${value}%`)
|
||||||
|
@ -788,11 +786,11 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
|
||||||
return results.length ? results : [{ [operation.toLowerCase()]: true }]
|
return results.length ? results : [{ [operation.toLowerCase()]: true }]
|
||||||
}
|
}
|
||||||
|
|
||||||
convertJsonStringColumns(
|
convertJsonStringColumns<T extends Record<string, any>>(
|
||||||
table: Table,
|
table: Table,
|
||||||
results: Record<string, any>[],
|
results: T[],
|
||||||
aliases?: Record<string, string>
|
aliases?: Record<string, string>
|
||||||
): Record<string, any>[] {
|
): T[] {
|
||||||
const tableName = getTableName(table)
|
const tableName = getTableName(table)
|
||||||
for (const [name, field] of Object.entries(table.schema)) {
|
for (const [name, field] of Object.entries(table.schema)) {
|
||||||
if (!this._isJsonColumn(field)) {
|
if (!this._isJsonColumn(field)) {
|
||||||
|
@ -801,11 +799,11 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
|
||||||
const aliasedTableName = (tableName && aliases?.[tableName]) || tableName
|
const aliasedTableName = (tableName && aliases?.[tableName]) || tableName
|
||||||
const fullName = `${aliasedTableName}.${name}`
|
const fullName = `${aliasedTableName}.${name}`
|
||||||
for (let row of results) {
|
for (let row of results) {
|
||||||
if (typeof row[fullName] === "string") {
|
if (typeof row[fullName as keyof T] === "string") {
|
||||||
row[fullName] = JSON.parse(row[fullName])
|
row[fullName as keyof T] = JSON.parse(row[fullName])
|
||||||
}
|
}
|
||||||
if (typeof row[name] === "string") {
|
if (typeof row[name as keyof T] === "string") {
|
||||||
row[name] = JSON.parse(row[name])
|
row[name as keyof T] = JSON.parse(row[name])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -816,9 +814,8 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
|
||||||
field: FieldSchema
|
field: FieldSchema
|
||||||
): field is JsonFieldMetadata | BBReferenceFieldMetadata {
|
): field is JsonFieldMetadata | BBReferenceFieldMetadata {
|
||||||
return (
|
return (
|
||||||
field.type === FieldType.JSON ||
|
JsonTypes.includes(field.type) &&
|
||||||
(field.type === FieldType.BB_REFERENCE &&
|
!helpers.schema.isDeprecatedSingleUserColumn(field)
|
||||||
!helpers.schema.isDeprecatedSingleUserColumn(field))
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { FieldType, Table, FieldSchema, SqlClient } from "@budibase/types"
|
||||||
|
import { Knex } from "knex"
|
||||||
|
|
||||||
|
export class SqlStatements {
|
||||||
|
client: string
|
||||||
|
table: Table
|
||||||
|
allOr: boolean | undefined
|
||||||
|
constructor(
|
||||||
|
client: string,
|
||||||
|
table: Table,
|
||||||
|
{ allOr }: { allOr?: boolean } = {}
|
||||||
|
) {
|
||||||
|
this.client = client
|
||||||
|
this.table = table
|
||||||
|
this.allOr = allOr
|
||||||
|
}
|
||||||
|
|
||||||
|
getField(key: string): FieldSchema | undefined {
|
||||||
|
const fieldName = key.split(".")[1]
|
||||||
|
return this.table.schema[fieldName]
|
||||||
|
}
|
||||||
|
|
||||||
|
between(
|
||||||
|
query: Knex.QueryBuilder,
|
||||||
|
key: string,
|
||||||
|
low: number | string,
|
||||||
|
high: number | string
|
||||||
|
) {
|
||||||
|
// Use a between operator if we have 2 valid range values
|
||||||
|
const field = this.getField(key)
|
||||||
|
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)`,
|
||||||
|
[low, high]
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
const fnc = this.allOr ? "orWhereBetween" : "whereBetween"
|
||||||
|
query = query[fnc](key, [low, high])
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
lte(query: Knex.QueryBuilder, key: string, low: number | string) {
|
||||||
|
// Use just a single greater than operator if we only have a low
|
||||||
|
const field = this.getField(key)
|
||||||
|
if (
|
||||||
|
field?.type === FieldType.BIGINT &&
|
||||||
|
this.client === SqlClient.SQL_LITE
|
||||||
|
) {
|
||||||
|
query = query.whereRaw(`CAST(${key} AS INTEGER) >= CAST(? AS INTEGER)`, [
|
||||||
|
low,
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
const fnc = this.allOr ? "orWhere" : "where"
|
||||||
|
query = query[fnc](key, ">=", low)
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
gte(query: Knex.QueryBuilder, key: string, high: number | string) {
|
||||||
|
const field = this.getField(key)
|
||||||
|
// Use just a single less than operator if we only have a high
|
||||||
|
if (
|
||||||
|
field?.type === FieldType.BIGINT &&
|
||||||
|
this.client === SqlClient.SQL_LITE
|
||||||
|
) {
|
||||||
|
query = query.whereRaw(`CAST(${key} AS INTEGER) <= CAST(? AS INTEGER)`, [
|
||||||
|
high,
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
const fnc = this.allOr ? "orWhere" : "where"
|
||||||
|
query = query[fnc](key, "<=", high)
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,8 +9,9 @@ import {
|
||||||
SqlQuery,
|
SqlQuery,
|
||||||
Table,
|
Table,
|
||||||
TableSourceType,
|
TableSourceType,
|
||||||
|
SqlClient,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { breakExternalTableId, getNativeSql, SqlClient } from "../utils"
|
import { breakExternalTableId, getNativeSql } from "./utils"
|
||||||
import { helpers, utils } from "@budibase/shared-core"
|
import { helpers, utils } from "@budibase/shared-core"
|
||||||
import SchemaBuilder = Knex.SchemaBuilder
|
import SchemaBuilder = Knex.SchemaBuilder
|
||||||
import CreateTableBuilder = Knex.CreateTableBuilder
|
import CreateTableBuilder = Knex.CreateTableBuilder
|
|
@ -0,0 +1,134 @@
|
||||||
|
import { DocumentType, SqlQuery, Table, TableSourceType } from "@budibase/types"
|
||||||
|
import { DEFAULT_BB_DATASOURCE_ID } from "../constants"
|
||||||
|
import { Knex } from "knex"
|
||||||
|
import { SEPARATOR } from "../db"
|
||||||
|
|
||||||
|
const DOUBLE_SEPARATOR = `${SEPARATOR}${SEPARATOR}`
|
||||||
|
const ROW_ID_REGEX = /^\[.*]$/g
|
||||||
|
const ENCODED_SPACE = encodeURIComponent(" ")
|
||||||
|
|
||||||
|
export function isExternalTableID(tableId: string) {
|
||||||
|
return tableId.startsWith(DocumentType.DATASOURCE + SEPARATOR)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isInternalTableID(tableId: string) {
|
||||||
|
return !isExternalTableID(tableId)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNativeSql(
|
||||||
|
query: Knex.SchemaBuilder | Knex.QueryBuilder
|
||||||
|
): SqlQuery | SqlQuery[] {
|
||||||
|
let sql = query.toSQL()
|
||||||
|
if (Array.isArray(sql)) {
|
||||||
|
return sql as SqlQuery[]
|
||||||
|
}
|
||||||
|
let native: Knex.SqlNative | undefined
|
||||||
|
if (sql.toNative) {
|
||||||
|
native = sql.toNative()
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
sql: native?.sql || sql.sql,
|
||||||
|
bindings: native?.bindings || sql.bindings,
|
||||||
|
} as SqlQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isExternalTable(table: Table) {
|
||||||
|
if (
|
||||||
|
table?.sourceId &&
|
||||||
|
table.sourceId.includes(DocumentType.DATASOURCE + SEPARATOR) &&
|
||||||
|
table?.sourceId !== DEFAULT_BB_DATASOURCE_ID
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
} else if (table?.sourceType === TableSourceType.EXTERNAL) {
|
||||||
|
return true
|
||||||
|
} else if (table?._id && isExternalTableID(table._id)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildExternalTableId(datasourceId: string, tableName: string) {
|
||||||
|
// encode spaces
|
||||||
|
if (tableName.includes(" ")) {
|
||||||
|
tableName = encodeURIComponent(tableName)
|
||||||
|
}
|
||||||
|
return `${datasourceId}${DOUBLE_SEPARATOR}${tableName}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function breakExternalTableId(tableId: string | undefined) {
|
||||||
|
if (!tableId) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
const parts = tableId.split(DOUBLE_SEPARATOR)
|
||||||
|
let datasourceId = parts.shift()
|
||||||
|
// if they need joined
|
||||||
|
let tableName = parts.join(DOUBLE_SEPARATOR)
|
||||||
|
// if contains encoded spaces, decode it
|
||||||
|
if (tableName.includes(ENCODED_SPACE)) {
|
||||||
|
tableName = decodeURIComponent(tableName)
|
||||||
|
}
|
||||||
|
return { datasourceId, tableName }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateRowIdField(keyProps: any[] = []) {
|
||||||
|
if (!Array.isArray(keyProps)) {
|
||||||
|
keyProps = [keyProps]
|
||||||
|
}
|
||||||
|
for (let index in keyProps) {
|
||||||
|
if (keyProps[index] instanceof Buffer) {
|
||||||
|
keyProps[index] = keyProps[index].toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// this conserves order and types
|
||||||
|
// we have to swap the double quotes to single quotes for use in HBS statements
|
||||||
|
// when using the literal helper the double quotes can break things
|
||||||
|
return encodeURIComponent(JSON.stringify(keyProps).replace(/"/g, "'"))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isRowId(field: any) {
|
||||||
|
return (
|
||||||
|
Array.isArray(field) ||
|
||||||
|
(typeof field === "string" && field.match(ROW_ID_REGEX) != null)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertRowId(field: any) {
|
||||||
|
if (Array.isArray(field)) {
|
||||||
|
return field[0]
|
||||||
|
}
|
||||||
|
if (typeof field === "string" && field.match(ROW_ID_REGEX) != null) {
|
||||||
|
return field.substring(1, field.length - 1)
|
||||||
|
}
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
|
||||||
|
// should always return an array
|
||||||
|
export function breakRowIdField(_id: string | { _id: string }): any[] {
|
||||||
|
if (!_id) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
// have to replace on the way back as we swapped out the double quotes
|
||||||
|
// when encoding, but JSON can't handle the single quotes
|
||||||
|
const id = typeof _id === "string" ? _id : _id._id
|
||||||
|
const decoded: string = decodeURIComponent(id).replace(/'/g, '"')
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(decoded)
|
||||||
|
return Array.isArray(parsed) ? parsed : [parsed]
|
||||||
|
} catch (err) {
|
||||||
|
// wasn't json - likely was handlebars for a many to many
|
||||||
|
return [_id]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isIsoDateString(str: string) {
|
||||||
|
const trimmedValue = str.trim()
|
||||||
|
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(trimmedValue)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
let d = new Date(trimmedValue)
|
||||||
|
return d.toISOString() === trimmedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isValidFilter(value: any) {
|
||||||
|
return value != null && value !== ""
|
||||||
|
}
|
|
@ -4,6 +4,8 @@
|
||||||
import FilterBuilder from "components/design/settings/controls/FilterEditor/FilterBuilder.svelte"
|
import FilterBuilder from "components/design/settings/controls/FilterEditor/FilterBuilder.svelte"
|
||||||
import { getUserBindings } from "dataBinding"
|
import { getUserBindings } from "dataBinding"
|
||||||
import { makePropSafe } from "@budibase/string-templates"
|
import { makePropSafe } from "@budibase/string-templates"
|
||||||
|
import { search } from "@budibase/frontend-core"
|
||||||
|
import { tables } from "stores/builder"
|
||||||
|
|
||||||
export let schema
|
export let schema
|
||||||
export let filters
|
export let filters
|
||||||
|
@ -15,11 +17,10 @@
|
||||||
let drawer
|
let drawer
|
||||||
|
|
||||||
$: tempValue = filters || []
|
$: tempValue = filters || []
|
||||||
$: schemaFields = Object.entries(schema || {}).map(
|
$: schemaFields = search.getFields(
|
||||||
([fieldName, fieldSchema]) => ({
|
$tables.list,
|
||||||
name: fieldName, // Using the key as name if not defined in the schema, for example in some autogenerated columns
|
Object.values(schema || {}),
|
||||||
...fieldSchema,
|
{ allowLinks: true }
|
||||||
})
|
|
||||||
)
|
)
|
||||||
|
|
||||||
$: text = getText(filters)
|
$: text = getText(filters)
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { Button, ActionButton, Drawer } from "@budibase/bbui"
|
import { Button, ActionButton, Drawer } from "@budibase/bbui"
|
||||||
|
import { search } from "@budibase/frontend-core"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import ColumnDrawer from "./ColumnDrawer.svelte"
|
import ColumnDrawer from "./ColumnDrawer.svelte"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { getDatasourceForProvider, getSchemaForDatasource } from "dataBinding"
|
import { getDatasourceForProvider, getSchemaForDatasource } from "dataBinding"
|
||||||
import { selectedScreen } from "stores/builder"
|
import { selectedScreen, tables } from "stores/builder"
|
||||||
import { getFields } from "helpers/searchFields"
|
|
||||||
|
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
export let value = []
|
export let value = []
|
||||||
|
@ -25,9 +25,13 @@
|
||||||
: enrichedSchemaFields?.map(field => field.name)
|
: enrichedSchemaFields?.map(field => field.name)
|
||||||
$: sanitisedValue = getValidColumns(value, options)
|
$: sanitisedValue = getValidColumns(value, options)
|
||||||
$: updateBoundValue(sanitisedValue)
|
$: updateBoundValue(sanitisedValue)
|
||||||
$: enrichedSchemaFields = getFields(Object.values(schema || {}), {
|
$: enrichedSchemaFields = search.getFields(
|
||||||
allowLinks: true,
|
$tables.list,
|
||||||
})
|
Object.values(schema || {}),
|
||||||
|
{
|
||||||
|
allowLinks: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
value = (value || []).filter(
|
value = (value || []).filter(
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
import { dataFilters } from "@budibase/shared-core"
|
import { dataFilters } from "@budibase/shared-core"
|
||||||
import { FilterBuilder } from "@budibase/frontend-core"
|
import { FilterBuilder } from "@budibase/frontend-core"
|
||||||
|
import { tables } from "stores/builder"
|
||||||
|
|
||||||
import { createEventDispatcher, onMount } from "svelte"
|
import { createEventDispatcher, onMount } from "svelte"
|
||||||
|
|
||||||
|
@ -58,6 +59,7 @@
|
||||||
<FilterBuilder
|
<FilterBuilder
|
||||||
bind:filters={rawFilters}
|
bind:filters={rawFilters}
|
||||||
behaviourFilters={true}
|
behaviourFilters={true}
|
||||||
|
tables={$tables.list}
|
||||||
{schemaFields}
|
{schemaFields}
|
||||||
{datasource}
|
{datasource}
|
||||||
{allowBindings}
|
{allowBindings}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { Multiselect } from "@budibase/bbui"
|
import { Multiselect } from "@budibase/bbui"
|
||||||
|
import { search } from "@budibase/frontend-core"
|
||||||
import { getDatasourceForProvider, getSchemaForDatasource } from "dataBinding"
|
import { getDatasourceForProvider, getSchemaForDatasource } from "dataBinding"
|
||||||
import { selectedScreen, tables } from "stores/builder"
|
import { selectedScreen, tables } from "stores/builder"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { getFields } from "helpers/searchFields"
|
|
||||||
|
|
||||||
export let componentInstance = {}
|
export let componentInstance = {}
|
||||||
export let value = ""
|
export let value = ""
|
||||||
|
@ -20,10 +20,9 @@
|
||||||
if (!ds?.tableId) {
|
if (!ds?.tableId) {
|
||||||
return base.map(field => field.name)
|
return base.map(field => field.name)
|
||||||
}
|
}
|
||||||
const currentTable = $tables.list.find(table => table._id === ds.tableId)
|
return search
|
||||||
return getFields(base, { allowLinks: currentTable?.sql }).map(
|
.getFields($tables.list, base, { allowLinks: true })
|
||||||
field => field.name
|
.map(field => field.name)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSelectedOption(selectedOptions, allOptions) {
|
function getSelectedOption(selectedOptions, allOptions) {
|
||||||
|
|
|
@ -16,11 +16,13 @@
|
||||||
import { LuceneUtils, Constants } from "@budibase/frontend-core"
|
import { LuceneUtils, Constants } from "@budibase/frontend-core"
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import FilterUsers from "./FilterUsers.svelte"
|
import FilterUsers from "./FilterUsers.svelte"
|
||||||
|
import { getFields } from "../utils/searchFields"
|
||||||
|
|
||||||
const { OperatorOptions } = Constants
|
const { OperatorOptions } = Constants
|
||||||
|
|
||||||
export let schemaFields
|
export let schemaFields
|
||||||
export let filters = []
|
export let filters = []
|
||||||
|
export let tables = []
|
||||||
export let datasource
|
export let datasource
|
||||||
export let behaviourFilters = false
|
export let behaviourFilters = false
|
||||||
export let allowBindings = false
|
export let allowBindings = false
|
||||||
|
@ -45,12 +47,12 @@
|
||||||
|
|
||||||
const context = getContext("context")
|
const context = getContext("context")
|
||||||
|
|
||||||
$: fieldOptions = (schemaFields ?? [])
|
$: fieldOptions = getFields(tables, schemaFields || [], {
|
||||||
.filter(field => getValidOperatorsForType(field).length)
|
allowLinks: true,
|
||||||
.map(field => ({
|
}).map(field => ({
|
||||||
label: field.displayName || field.name,
|
label: field.displayName || field.name,
|
||||||
value: field.name,
|
value: field.name,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const addFilter = () => {
|
const addFilter = () => {
|
||||||
filters = [
|
filters = [
|
||||||
|
|
|
@ -6,6 +6,15 @@ export { Feature as Features } from "@budibase/types"
|
||||||
import { BpmCorrelationKey } from "@budibase/shared-core"
|
import { BpmCorrelationKey } from "@budibase/shared-core"
|
||||||
import { FieldType, BBReferenceFieldSubType } from "@budibase/types"
|
import { FieldType, BBReferenceFieldSubType } from "@budibase/types"
|
||||||
|
|
||||||
|
export const BannedSearchTypes = [
|
||||||
|
FieldType.LINK,
|
||||||
|
FieldType.ATTACHMENTS,
|
||||||
|
FieldType.FORMULA,
|
||||||
|
FieldType.JSON,
|
||||||
|
"jsonarray",
|
||||||
|
"queryarray",
|
||||||
|
]
|
||||||
|
|
||||||
// Cookie names
|
// Cookie names
|
||||||
export const Cookies = {
|
export const Cookies = {
|
||||||
Auth: "budibase:auth",
|
Auth: "budibase:auth",
|
||||||
|
|
|
@ -4,6 +4,7 @@ export * as CookieUtils from "./cookies"
|
||||||
export * as RoleUtils from "./roles"
|
export * as RoleUtils from "./roles"
|
||||||
export * as Utils from "./utils"
|
export * as Utils from "./utils"
|
||||||
export * as RowUtils from "./rows"
|
export * as RowUtils from "./rows"
|
||||||
|
export * as search from "./searchFields"
|
||||||
export { memo, derivedMemo } from "./memo"
|
export { memo, derivedMemo } from "./memo"
|
||||||
export { createWebsocket } from "./websocket"
|
export { createWebsocket } from "./websocket"
|
||||||
export * from "./download"
|
export * from "./download"
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import { tables } from "stores/builder"
|
import { BannedSearchTypes } from "../constants"
|
||||||
import { BannedSearchTypes } from "../constants/backend"
|
|
||||||
import { get } from "svelte/store"
|
|
||||||
|
|
||||||
export function getTableFields(linkField) {
|
export function getTableFields(tables, linkField) {
|
||||||
const table = get(tables).list.find(table => table._id === linkField.tableId)
|
const table = tables.find(table => table._id === linkField.tableId)
|
||||||
|
// TODO: mdrury - add support for this with SQS at some point
|
||||||
if (!table || !table.sql) {
|
if (!table || !table.sql) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
const linkFields = getFields(Object.values(table.schema), {
|
const linkFields = getFields(tables, Object.values(table.schema), {
|
||||||
allowLinks: false,
|
allowLinks: false,
|
||||||
})
|
})
|
||||||
return linkFields.map(field => ({
|
return linkFields.map(field => ({
|
||||||
|
@ -16,7 +15,11 @@ export function getTableFields(linkField) {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFields(fields, { allowLinks } = { allowLinks: true }) {
|
export function getFields(
|
||||||
|
tables,
|
||||||
|
fields,
|
||||||
|
{ allowLinks } = { allowLinks: true }
|
||||||
|
) {
|
||||||
let filteredFields = fields.filter(
|
let filteredFields = fields.filter(
|
||||||
field => !BannedSearchTypes.includes(field.type)
|
field => !BannedSearchTypes.includes(field.type)
|
||||||
)
|
)
|
||||||
|
@ -24,7 +27,7 @@ export function getFields(fields, { allowLinks } = { allowLinks: true }) {
|
||||||
const linkFields = fields.filter(field => field.type === "link")
|
const linkFields = fields.filter(field => field.type === "link")
|
||||||
for (let linkField of linkFields) {
|
for (let linkField of linkFields) {
|
||||||
// only allow one depth of SQL relationship filtering
|
// only allow one depth of SQL relationship filtering
|
||||||
filteredFields = filteredFields.concat(getTableFields(linkField))
|
filteredFields = filteredFields.concat(getTableFields(tables, linkField))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const staticFormulaFields = fields.filter(
|
const staticFormulaFields = fields.filter(
|
|
@ -1 +1 @@
|
||||||
Subproject commit d3c3077011a8e20ed3c48dcd6301caca4120b6ac
|
Subproject commit 1879d8686b1d9392707595a02cdd4981923e7f99
|
|
@ -4,8 +4,8 @@ import {
|
||||||
SearchRowResponse,
|
SearchRowResponse,
|
||||||
SearchViewRowRequest,
|
SearchViewRowRequest,
|
||||||
RequiredKeys,
|
RequiredKeys,
|
||||||
SearchFilters,
|
|
||||||
RowSearchParams,
|
RowSearchParams,
|
||||||
|
SearchFilterKey,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { dataFilters } from "@budibase/shared-core"
|
import { dataFilters } from "@budibase/shared-core"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
|
@ -45,10 +45,7 @@ export async function searchView(
|
||||||
|
|
||||||
// Carry over filters for unused fields
|
// Carry over filters for unused fields
|
||||||
Object.keys(body.query).forEach(key => {
|
Object.keys(body.query).forEach(key => {
|
||||||
const operator = key as keyof Omit<
|
const operator = key as SearchFilterKey
|
||||||
SearchFilters,
|
|
||||||
"allOr" | "onEmptyFilter"
|
|
||||||
>
|
|
||||||
Object.keys(body.query[operator] || {}).forEach(field => {
|
Object.keys(body.query[operator] || {}).forEach(field => {
|
||||||
if (!existingFields.includes(db.removeKeyNumbering(field))) {
|
if (!existingFields.includes(db.removeKeyNumbering(field))) {
|
||||||
query[operator]![field] = body.query[operator]![field]
|
query[operator]![field] = body.query[operator]![field]
|
||||||
|
|
|
@ -173,8 +173,8 @@ export const devClientVersion = "0.0.0"
|
||||||
export const ObjectStoreBuckets = objectStore.ObjectStoreBuckets
|
export const ObjectStoreBuckets = objectStore.ObjectStoreBuckets
|
||||||
export const MAX_AUTOMATION_RECURRING_ERRORS = 5
|
export const MAX_AUTOMATION_RECURRING_ERRORS = 5
|
||||||
export const GOOGLE_SHEETS_PRIMARY_KEY = "rowNumber"
|
export const GOOGLE_SHEETS_PRIMARY_KEY = "rowNumber"
|
||||||
export const DEFAULT_JOBS_TABLE_ID = "ta_bb_jobs"
|
export const DEFAULT_JOBS_TABLE_ID = constants.DEFAULT_JOBS_TABLE_ID
|
||||||
export const DEFAULT_INVENTORY_TABLE_ID = "ta_bb_inventory"
|
export const DEFAULT_INVENTORY_TABLE_ID = constants.DEFAULT_INVENTORY_TABLE_ID
|
||||||
export const DEFAULT_EXPENSES_TABLE_ID = "ta_bb_expenses"
|
export const DEFAULT_EXPENSES_TABLE_ID = constants.DEFAULT_EXPENSES_TABLE_ID
|
||||||
export const DEFAULT_EMPLOYEE_TABLE_ID = "ta_bb_employee"
|
export const DEFAULT_EMPLOYEE_TABLE_ID = constants.DEFAULT_EMPLOYEE_TABLE_ID
|
||||||
export const DEFAULT_BB_DATASOURCE_ID = "datasource_internal_bb_default"
|
export const DEFAULT_BB_DATASOURCE_ID = constants.DEFAULT_BB_DATASOURCE_ID
|
||||||
|
|
|
@ -40,7 +40,6 @@ export const USER_METDATA_PREFIX = `${DocumentType.ROW}${SEPARATOR}${dbCore.Inte
|
||||||
export const LINK_USER_METADATA_PREFIX = `${DocumentType.LINK}${SEPARATOR}${dbCore.InternalTable.USER_METADATA}${SEPARATOR}`
|
export const LINK_USER_METADATA_PREFIX = `${DocumentType.LINK}${SEPARATOR}${dbCore.InternalTable.USER_METADATA}${SEPARATOR}`
|
||||||
export const TABLE_ROW_PREFIX = `${DocumentType.ROW}${SEPARATOR}${DocumentType.TABLE}`
|
export const TABLE_ROW_PREFIX = `${DocumentType.ROW}${SEPARATOR}${DocumentType.TABLE}`
|
||||||
export const AUTOMATION_LOG_PREFIX = `${DocumentType.AUTOMATION_LOG}${SEPARATOR}`
|
export const AUTOMATION_LOG_PREFIX = `${DocumentType.AUTOMATION_LOG}${SEPARATOR}`
|
||||||
export const SQS_DATASOURCE_INTERNAL = "internal"
|
|
||||||
export const ViewName = dbCore.ViewName
|
export const ViewName = dbCore.ViewName
|
||||||
export const InternalTables = dbCore.InternalTable
|
export const InternalTables = dbCore.InternalTable
|
||||||
export const UNICODE_MAX = dbCore.UNICODE_MAX
|
export const UNICODE_MAX = dbCore.UNICODE_MAX
|
||||||
|
|
|
@ -3,8 +3,3 @@
|
||||||
* internal to the server and don't need to *
|
* internal to the server and don't need to *
|
||||||
* be exposed for use by other services. *
|
* be exposed for use by other services. *
|
||||||
********************************************/
|
********************************************/
|
||||||
|
|
||||||
export interface QueryOptions {
|
|
||||||
disableReturning?: boolean
|
|
||||||
disableBindings?: boolean
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,40 +1,41 @@
|
||||||
import {
|
import {
|
||||||
|
ConnectionInfo,
|
||||||
|
DatasourceFeature,
|
||||||
DatasourceFieldType,
|
DatasourceFieldType,
|
||||||
|
DatasourcePlus,
|
||||||
|
DatasourcePlusQueryResponse,
|
||||||
Integration,
|
Integration,
|
||||||
Operation,
|
Operation,
|
||||||
Table,
|
|
||||||
TableSchema,
|
|
||||||
QueryJson,
|
QueryJson,
|
||||||
QueryType,
|
QueryType,
|
||||||
SqlQuery,
|
|
||||||
DatasourcePlus,
|
|
||||||
DatasourceFeature,
|
|
||||||
ConnectionInfo,
|
|
||||||
SourceName,
|
|
||||||
Schema,
|
Schema,
|
||||||
|
SourceName,
|
||||||
|
SqlClient,
|
||||||
|
SqlQuery,
|
||||||
|
Table,
|
||||||
|
TableSchema,
|
||||||
TableSourceType,
|
TableSourceType,
|
||||||
DatasourcePlusQueryResponse,
|
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import {
|
import {
|
||||||
getSqlQuery,
|
|
||||||
buildExternalTableId,
|
buildExternalTableId,
|
||||||
generateColumnDefinition,
|
|
||||||
finaliseExternalTables,
|
|
||||||
SqlClient,
|
|
||||||
checkExternalTables,
|
checkExternalTables,
|
||||||
|
finaliseExternalTables,
|
||||||
|
generateColumnDefinition,
|
||||||
|
getSqlQuery,
|
||||||
HOST_ADDRESS,
|
HOST_ADDRESS,
|
||||||
} from "./utils"
|
} from "./utils"
|
||||||
import Sql from "./base/sql"
|
import { MSSQLColumn, MSSQLTablesResponse } from "./base/types"
|
||||||
import { MSSQLTablesResponse, MSSQLColumn } from "./base/types"
|
|
||||||
import { getReadableErrorMessage } from "./base/errorMapping"
|
import { getReadableErrorMessage } from "./base/errorMapping"
|
||||||
import sqlServer from "mssql"
|
import sqlServer from "mssql"
|
||||||
|
import { sql } from "@budibase/backend-core"
|
||||||
const DEFAULT_SCHEMA = "dbo"
|
|
||||||
|
|
||||||
import { ConfidentialClientApplication } from "@azure/msal-node"
|
import { ConfidentialClientApplication } from "@azure/msal-node"
|
||||||
|
|
||||||
import { utils } from "@budibase/shared-core"
|
import { utils } from "@budibase/shared-core"
|
||||||
|
|
||||||
|
const Sql = sql.Sql
|
||||||
|
|
||||||
|
const DEFAULT_SCHEMA = "dbo"
|
||||||
|
|
||||||
enum MSSQLConfigAuthType {
|
enum MSSQLConfigAuthType {
|
||||||
AZURE_ACTIVE_DIRECTORY = "Azure Active Directory",
|
AZURE_ACTIVE_DIRECTORY = "Azure Active Directory",
|
||||||
NTLM = "NTLM",
|
NTLM = "NTLM",
|
||||||
|
@ -590,8 +591,7 @@ class SqlServerIntegration extends Sql implements DatasourcePlus {
|
||||||
scriptParts.push(createTableStatement)
|
scriptParts.push(createTableStatement)
|
||||||
}
|
}
|
||||||
|
|
||||||
const schema = scriptParts.join("\n")
|
return scriptParts.join("\n")
|
||||||
return schema
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,10 +14,10 @@ import {
|
||||||
TableSourceType,
|
TableSourceType,
|
||||||
DatasourcePlusQueryResponse,
|
DatasourcePlusQueryResponse,
|
||||||
SqlQueryBinding,
|
SqlQueryBinding,
|
||||||
|
SqlClient,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import {
|
import {
|
||||||
getSqlQuery,
|
getSqlQuery,
|
||||||
SqlClient,
|
|
||||||
buildExternalTableId,
|
buildExternalTableId,
|
||||||
generateColumnDefinition,
|
generateColumnDefinition,
|
||||||
finaliseExternalTables,
|
finaliseExternalTables,
|
||||||
|
@ -26,11 +26,13 @@ import {
|
||||||
} from "./utils"
|
} from "./utils"
|
||||||
import dayjs from "dayjs"
|
import dayjs from "dayjs"
|
||||||
import { NUMBER_REGEX } from "../utilities"
|
import { NUMBER_REGEX } from "../utilities"
|
||||||
import Sql from "./base/sql"
|
|
||||||
import { MySQLColumn } from "./base/types"
|
import { MySQLColumn } from "./base/types"
|
||||||
import { getReadableErrorMessage } from "./base/errorMapping"
|
import { getReadableErrorMessage } from "./base/errorMapping"
|
||||||
|
import { sql } from "@budibase/backend-core"
|
||||||
import mysql from "mysql2/promise"
|
import mysql from "mysql2/promise"
|
||||||
|
|
||||||
|
const Sql = sql.Sql
|
||||||
|
|
||||||
interface MySQLConfig extends mysql.ConnectionOptions {
|
interface MySQLConfig extends mysql.ConnectionOptions {
|
||||||
database: string
|
database: string
|
||||||
rejectUnauthorized: boolean
|
rejectUnauthorized: boolean
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {
|
||||||
TableSourceType,
|
TableSourceType,
|
||||||
Row,
|
Row,
|
||||||
DatasourcePlusQueryResponse,
|
DatasourcePlusQueryResponse,
|
||||||
|
SqlClient,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import {
|
import {
|
||||||
buildExternalTableId,
|
buildExternalTableId,
|
||||||
|
@ -21,10 +22,8 @@ import {
|
||||||
generateColumnDefinition,
|
generateColumnDefinition,
|
||||||
finaliseExternalTables,
|
finaliseExternalTables,
|
||||||
getSqlQuery,
|
getSqlQuery,
|
||||||
SqlClient,
|
|
||||||
HOST_ADDRESS,
|
HOST_ADDRESS,
|
||||||
} from "./utils"
|
} from "./utils"
|
||||||
import Sql from "./base/sql"
|
|
||||||
import {
|
import {
|
||||||
BindParameters,
|
BindParameters,
|
||||||
Connection,
|
Connection,
|
||||||
|
@ -33,6 +32,9 @@ import {
|
||||||
Result,
|
Result,
|
||||||
} from "oracledb"
|
} from "oracledb"
|
||||||
import { OracleTable, OracleColumn, OracleColumnsResponse } from "./base/types"
|
import { OracleTable, OracleColumn, OracleColumnsResponse } from "./base/types"
|
||||||
|
import { sql } from "@budibase/backend-core"
|
||||||
|
|
||||||
|
const Sql = sql.Sql
|
||||||
|
|
||||||
let oracledb: any
|
let oracledb: any
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -13,17 +13,16 @@ import {
|
||||||
Schema,
|
Schema,
|
||||||
TableSourceType,
|
TableSourceType,
|
||||||
DatasourcePlusQueryResponse,
|
DatasourcePlusQueryResponse,
|
||||||
|
SqlClient,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import {
|
import {
|
||||||
getSqlQuery,
|
getSqlQuery,
|
||||||
buildExternalTableId,
|
buildExternalTableId,
|
||||||
generateColumnDefinition,
|
generateColumnDefinition,
|
||||||
finaliseExternalTables,
|
finaliseExternalTables,
|
||||||
SqlClient,
|
|
||||||
checkExternalTables,
|
checkExternalTables,
|
||||||
HOST_ADDRESS,
|
HOST_ADDRESS,
|
||||||
} from "./utils"
|
} from "./utils"
|
||||||
import Sql from "./base/sql"
|
|
||||||
import { PostgresColumn } from "./base/types"
|
import { PostgresColumn } from "./base/types"
|
||||||
import { escapeDangerousCharacters } from "../utilities"
|
import { escapeDangerousCharacters } from "../utilities"
|
||||||
|
|
||||||
|
@ -31,7 +30,7 @@ import { Client, ClientConfig, types } from "pg"
|
||||||
import { getReadableErrorMessage } from "./base/errorMapping"
|
import { getReadableErrorMessage } from "./base/errorMapping"
|
||||||
import { exec } from "child_process"
|
import { exec } from "child_process"
|
||||||
import { storeTempFile } from "../utilities/fileSystem"
|
import { storeTempFile } from "../utilities/fileSystem"
|
||||||
import { env } from "@budibase/backend-core"
|
import { env, sql } from "@budibase/backend-core"
|
||||||
|
|
||||||
// Return "date" and "timestamp" types as plain strings.
|
// Return "date" and "timestamp" types as plain strings.
|
||||||
// This lets us reference the original stored timezone.
|
// This lets us reference the original stored timezone.
|
||||||
|
@ -43,6 +42,7 @@ if (types) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const JSON_REGEX = /'{.*}'::json/s
|
const JSON_REGEX = /'{.*}'::json/s
|
||||||
|
const Sql = sql.Sql
|
||||||
|
|
||||||
interface PostgresConfig {
|
interface PostgresConfig {
|
||||||
host: string
|
host: string
|
||||||
|
|
|
@ -664,6 +664,7 @@ describe("REST Integration", () => {
|
||||||
}),
|
}),
|
||||||
get: (header: any) => {
|
get: (header: any) => {
|
||||||
if (header === "content-type") return contentType
|
if (header === "content-type") return contentType
|
||||||
|
if (header === "content-length") return responseData.byteLength
|
||||||
if (header === "content-disposition")
|
if (header === "content-disposition")
|
||||||
return `attachment; filename="${filename}"`
|
return `attachment; filename="${filename}"`
|
||||||
},
|
},
|
||||||
|
@ -709,6 +710,7 @@ describe("REST Integration", () => {
|
||||||
}),
|
}),
|
||||||
get: (header: any) => {
|
get: (header: any) => {
|
||||||
if (header === "content-type") return contentType
|
if (header === "content-type") return contentType
|
||||||
|
if (header === "content-length") return responseData.byteLength
|
||||||
if (header === "content-disposition")
|
if (header === "content-disposition")
|
||||||
// eslint-disable-next-line no-useless-escape
|
// eslint-disable-next-line no-useless-escape
|
||||||
return `attachment; filename="£ and ? rates.pdf"; filename*=UTF-8'\'%C2%A3%20and%20%E2%82%AC%20rates.pdf`
|
return `attachment; filename="£ and ? rates.pdf"; filename*=UTF-8'\'%C2%A3%20and%20%E2%82%AC%20rates.pdf`
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import { SqlClient } from "../utils"
|
|
||||||
import Sql from "../base/sql"
|
|
||||||
import {
|
import {
|
||||||
FieldType,
|
FieldType,
|
||||||
Operation,
|
Operation,
|
||||||
QueryJson,
|
QueryJson,
|
||||||
Table,
|
Table,
|
||||||
TableSourceType,
|
TableSourceType,
|
||||||
|
SqlClient,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
import { sql } from "@budibase/backend-core"
|
||||||
|
|
||||||
|
const Sql = sql.Sql
|
||||||
|
|
||||||
const TABLE_NAME = "test"
|
const TABLE_NAME = "test"
|
||||||
const TABLE: Table = {
|
const TABLE: Table = {
|
||||||
|
|
|
@ -6,13 +6,15 @@ import {
|
||||||
SqlQuery,
|
SqlQuery,
|
||||||
Table,
|
Table,
|
||||||
TableSourceType,
|
TableSourceType,
|
||||||
|
SqlClient,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
import { sql } from "@budibase/backend-core"
|
||||||
import { join } from "path"
|
import { join } from "path"
|
||||||
import Sql from "../base/sql"
|
|
||||||
import { SqlClient } from "../utils"
|
|
||||||
import { generator } from "@budibase/backend-core/tests"
|
import { generator } from "@budibase/backend-core/tests"
|
||||||
import sdk from "../../sdk"
|
import sdk from "../../sdk"
|
||||||
|
|
||||||
|
const Sql = sql.Sql
|
||||||
|
|
||||||
// this doesn't exist strictly
|
// this doesn't exist strictly
|
||||||
const TABLE: Table = {
|
const TABLE: Table = {
|
||||||
type: "table",
|
type: "table",
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
export * from "./utils"
|
export * from "./utils"
|
||||||
export { SqlStatements } from "./sqlStatements"
|
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
import { FieldType, Table, FieldSchema } from "@budibase/types"
|
|
||||||
import { SqlClient } from "./utils"
|
|
||||||
import { Knex } from "knex"
|
|
||||||
|
|
||||||
export class SqlStatements {
|
|
||||||
client: string
|
|
||||||
table: Table
|
|
||||||
allOr: boolean | undefined
|
|
||||||
constructor(
|
|
||||||
client: string,
|
|
||||||
table: Table,
|
|
||||||
{ allOr }: { allOr?: boolean } = {}
|
|
||||||
) {
|
|
||||||
this.client = client
|
|
||||||
this.table = table
|
|
||||||
this.allOr = allOr
|
|
||||||
}
|
|
||||||
|
|
||||||
getField(key: string): FieldSchema | undefined {
|
|
||||||
const fieldName = key.split(".")[1]
|
|
||||||
return this.table.schema[fieldName]
|
|
||||||
}
|
|
||||||
|
|
||||||
between(
|
|
||||||
query: Knex.QueryBuilder,
|
|
||||||
key: string,
|
|
||||||
low: number | string,
|
|
||||||
high: number | string
|
|
||||||
) {
|
|
||||||
// Use a between operator if we have 2 valid range values
|
|
||||||
const field = this.getField(key)
|
|
||||||
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)`,
|
|
||||||
[low, high]
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
const fnc = this.allOr ? "orWhereBetween" : "whereBetween"
|
|
||||||
query = query[fnc](key, [low, high])
|
|
||||||
}
|
|
||||||
return query
|
|
||||||
}
|
|
||||||
|
|
||||||
lte(query: Knex.QueryBuilder, key: string, low: number | string) {
|
|
||||||
// Use just a single greater than operator if we only have a low
|
|
||||||
const field = this.getField(key)
|
|
||||||
if (
|
|
||||||
field?.type === FieldType.BIGINT &&
|
|
||||||
this.client === SqlClient.SQL_LITE
|
|
||||||
) {
|
|
||||||
query = query.whereRaw(`CAST(${key} AS INTEGER) >= CAST(? AS INTEGER)`, [
|
|
||||||
low,
|
|
||||||
])
|
|
||||||
} else {
|
|
||||||
const fnc = this.allOr ? "orWhere" : "where"
|
|
||||||
query = query[fnc](key, ">=", low)
|
|
||||||
}
|
|
||||||
return query
|
|
||||||
}
|
|
||||||
|
|
||||||
gte(query: Knex.QueryBuilder, key: string, high: number | string) {
|
|
||||||
const field = this.getField(key)
|
|
||||||
// Use just a single less than operator if we only have a high
|
|
||||||
if (
|
|
||||||
field?.type === FieldType.BIGINT &&
|
|
||||||
this.client === SqlClient.SQL_LITE
|
|
||||||
) {
|
|
||||||
query = query.whereRaw(`CAST(${key} AS INTEGER) <= CAST(? AS INTEGER)`, [
|
|
||||||
high,
|
|
||||||
])
|
|
||||||
} else {
|
|
||||||
const fnc = this.allOr ? "orWhere" : "where"
|
|
||||||
query = query[fnc](key, "<=", high)
|
|
||||||
}
|
|
||||||
return query
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,23 +3,16 @@ import {
|
||||||
Table,
|
Table,
|
||||||
Datasource,
|
Datasource,
|
||||||
FieldType,
|
FieldType,
|
||||||
TableSourceType,
|
|
||||||
FieldSchema,
|
FieldSchema,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { context, objectStore } from "@budibase/backend-core"
|
import { context, objectStore, sql } from "@budibase/backend-core"
|
||||||
import { v4 } from "uuid"
|
import { v4 } from "uuid"
|
||||||
import { parseStringPromise as xmlParser } from "xml2js"
|
import { parseStringPromise as xmlParser } from "xml2js"
|
||||||
import { formatBytes } from "../../utilities"
|
import { formatBytes } from "../../utilities"
|
||||||
import bl from "bl"
|
import bl from "bl"
|
||||||
import env from "../../environment"
|
import env from "../../environment"
|
||||||
import { DocumentType, SEPARATOR } from "../../db/utils"
|
import { InvalidColumns } from "../../constants"
|
||||||
import { InvalidColumns, DEFAULT_BB_DATASOURCE_ID } from "../../constants"
|
|
||||||
import { helpers, utils } from "@budibase/shared-core"
|
import { helpers, utils } from "@budibase/shared-core"
|
||||||
import { Knex } from "knex"
|
|
||||||
|
|
||||||
const DOUBLE_SEPARATOR = `${SEPARATOR}${SEPARATOR}`
|
|
||||||
const ROW_ID_REGEX = /^\[.*]$/g
|
|
||||||
const ENCODED_SPACE = encodeURIComponent(" ")
|
|
||||||
|
|
||||||
type PrimitiveTypes =
|
type PrimitiveTypes =
|
||||||
| FieldType.STRING
|
| FieldType.STRING
|
||||||
|
@ -114,13 +107,15 @@ const SQL_TYPE_MAP: Record<string, PrimitiveTypes> = {
|
||||||
...SQL_OPTIONS_TYPE_MAP,
|
...SQL_OPTIONS_TYPE_MAP,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SqlClient {
|
export const isExternalTableID = sql.utils.isExternalTableID
|
||||||
MS_SQL = "mssql",
|
export const isExternalTable = sql.utils.isExternalTable
|
||||||
POSTGRES = "pg",
|
export const buildExternalTableId = sql.utils.buildExternalTableId
|
||||||
MY_SQL = "mysql2",
|
export const breakExternalTableId = sql.utils.breakExternalTableId
|
||||||
ORACLE = "oracledb",
|
export const generateRowIdField = sql.utils.generateRowIdField
|
||||||
SQL_LITE = "sqlite3",
|
export const isRowId = sql.utils.isRowId
|
||||||
}
|
export const convertRowId = sql.utils.convertRowId
|
||||||
|
export const breakRowIdField = sql.utils.breakRowIdField
|
||||||
|
export const isValidFilter = sql.utils.isValidFilter
|
||||||
|
|
||||||
const isCloud = env.isProd() && !env.SELF_HOSTED
|
const isCloud = env.isProd() && !env.SELF_HOSTED
|
||||||
const isSelfHost = env.isProd() && env.SELF_HOSTED
|
const isSelfHost = env.isProd() && env.SELF_HOSTED
|
||||||
|
@ -130,119 +125,6 @@ export const HOST_ADDRESS = isSelfHost
|
||||||
? ""
|
? ""
|
||||||
: "localhost"
|
: "localhost"
|
||||||
|
|
||||||
export function isExternalTableID(tableId: string) {
|
|
||||||
return tableId.includes(DocumentType.DATASOURCE)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isInternalTableID(tableId: string) {
|
|
||||||
return !isExternalTableID(tableId)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getNativeSql(
|
|
||||||
query: Knex.SchemaBuilder | Knex.QueryBuilder
|
|
||||||
): SqlQuery | SqlQuery[] {
|
|
||||||
let sql = query.toSQL()
|
|
||||||
if (Array.isArray(sql)) {
|
|
||||||
return sql as SqlQuery[]
|
|
||||||
}
|
|
||||||
let native: Knex.SqlNative | undefined
|
|
||||||
if (sql.toNative) {
|
|
||||||
native = sql.toNative()
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
sql: native?.sql || sql.sql,
|
|
||||||
bindings: native?.bindings || sql.bindings,
|
|
||||||
} as SqlQuery
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isExternalTable(table: Table) {
|
|
||||||
if (
|
|
||||||
table?.sourceId &&
|
|
||||||
table.sourceId.includes(DocumentType.DATASOURCE + SEPARATOR) &&
|
|
||||||
table?.sourceId !== DEFAULT_BB_DATASOURCE_ID
|
|
||||||
) {
|
|
||||||
return true
|
|
||||||
} else if (table?.sourceType === TableSourceType.EXTERNAL) {
|
|
||||||
return true
|
|
||||||
} else if (table?._id && isExternalTableID(table._id)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildExternalTableId(datasourceId: string, tableName: string) {
|
|
||||||
// encode spaces
|
|
||||||
if (tableName.includes(" ")) {
|
|
||||||
tableName = encodeURIComponent(tableName)
|
|
||||||
}
|
|
||||||
return `${datasourceId}${DOUBLE_SEPARATOR}${tableName}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export function breakExternalTableId(tableId: string | undefined) {
|
|
||||||
if (!tableId) {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
const parts = tableId.split(DOUBLE_SEPARATOR)
|
|
||||||
let datasourceId = parts.shift()
|
|
||||||
// if they need joined
|
|
||||||
let tableName = parts.join(DOUBLE_SEPARATOR)
|
|
||||||
// if contains encoded spaces, decode it
|
|
||||||
if (tableName.includes(ENCODED_SPACE)) {
|
|
||||||
tableName = decodeURIComponent(tableName)
|
|
||||||
}
|
|
||||||
return { datasourceId, tableName }
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generateRowIdField(keyProps: any[] = []) {
|
|
||||||
if (!Array.isArray(keyProps)) {
|
|
||||||
keyProps = [keyProps]
|
|
||||||
}
|
|
||||||
for (let index in keyProps) {
|
|
||||||
if (keyProps[index] instanceof Buffer) {
|
|
||||||
keyProps[index] = keyProps[index].toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// this conserves order and types
|
|
||||||
// we have to swap the double quotes to single quotes for use in HBS statements
|
|
||||||
// when using the literal helper the double quotes can break things
|
|
||||||
return encodeURIComponent(JSON.stringify(keyProps).replace(/"/g, "'"))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isRowId(field: any) {
|
|
||||||
return (
|
|
||||||
Array.isArray(field) ||
|
|
||||||
(typeof field === "string" && field.match(ROW_ID_REGEX) != null)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function convertRowId(field: any) {
|
|
||||||
if (Array.isArray(field)) {
|
|
||||||
return field[0]
|
|
||||||
}
|
|
||||||
if (typeof field === "string" && field.match(ROW_ID_REGEX) != null) {
|
|
||||||
return field.substring(1, field.length - 1)
|
|
||||||
}
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
|
|
||||||
// should always return an array
|
|
||||||
export function breakRowIdField(_id: string | { _id: string }): any[] {
|
|
||||||
if (!_id) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
// have to replace on the way back as we swapped out the double quotes
|
|
||||||
// when encoding, but JSON can't handle the single quotes
|
|
||||||
const id = typeof _id === "string" ? _id : _id._id
|
|
||||||
const decoded: string = decodeURIComponent(id).replace(/'/g, '"')
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(decoded)
|
|
||||||
return Array.isArray(parsed) ? parsed : [parsed]
|
|
||||||
} catch (err) {
|
|
||||||
// wasn't json - likely was handlebars for a many to many
|
|
||||||
return [_id]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generateColumnDefinition(config: {
|
export function generateColumnDefinition(config: {
|
||||||
externalType: string
|
externalType: string
|
||||||
autocolumn: boolean
|
autocolumn: boolean
|
||||||
|
@ -302,15 +184,6 @@ export function isSQL(datasource: Datasource) {
|
||||||
return helpers.isSQL(datasource)
|
return helpers.isSQL(datasource)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isIsoDateString(str: string) {
|
|
||||||
const trimmedValue = str.trim()
|
|
||||||
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(trimmedValue)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
let d = new Date(trimmedValue)
|
|
||||||
return d.toISOString() === trimmedValue
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Looks for columns which need to be copied over into the new table definitions, like relationships,
|
* Looks for columns which need to be copied over into the new table definitions, like relationships,
|
||||||
* options types and views.
|
* options types and views.
|
||||||
|
@ -457,37 +330,8 @@ export function checkExternalTables(
|
||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the provided input is an object, but specifically not a date type object.
|
|
||||||
* Used during coercion of types and relationship handling, dates are considered valid
|
|
||||||
* and can be used as a display field, but objects and arrays cannot.
|
|
||||||
* @param testValue an unknown type which this function will attempt to extract
|
|
||||||
* a valid primary display string from.
|
|
||||||
*/
|
|
||||||
export function getPrimaryDisplay(testValue: unknown): string | undefined {
|
|
||||||
if (testValue instanceof Date) {
|
|
||||||
return testValue.toISOString()
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
Array.isArray(testValue) &&
|
|
||||||
testValue[0] &&
|
|
||||||
typeof testValue[0] !== "object"
|
|
||||||
) {
|
|
||||||
return testValue.join(", ")
|
|
||||||
}
|
|
||||||
if (typeof testValue === "object") {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
return testValue as string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isValidFilter(value: any) {
|
|
||||||
return value != null && value !== ""
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function handleXml(rawXml: string) {
|
export async function handleXml(rawXml: string) {
|
||||||
let data
|
let data =
|
||||||
data =
|
|
||||||
(await xmlParser(rawXml, {
|
(await xmlParser(rawXml, {
|
||||||
explicitArray: false,
|
explicitArray: false,
|
||||||
trim: true,
|
trim: true,
|
||||||
|
@ -522,12 +366,6 @@ export async function handleFileResponse(
|
||||||
const contentLength = response.headers.get("content-length")
|
const contentLength = response.headers.get("content-length")
|
||||||
if (contentLength) {
|
if (contentLength) {
|
||||||
size = parseInt(contentLength, 10)
|
size = parseInt(contentLength, 10)
|
||||||
} else {
|
|
||||||
const chunks: Buffer[] = []
|
|
||||||
for await (const chunk of response.body) {
|
|
||||||
chunks.push(chunk)
|
|
||||||
size += chunk.length
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await objectStore.streamUpload({
|
await objectStore.streamUpload({
|
||||||
|
@ -538,7 +376,7 @@ export async function handleFileResponse(
|
||||||
type: response.headers["content-type"],
|
type: response.headers["content-type"],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
presignedUrl = await objectStore.getPresignedUrl(bucket, key)
|
presignedUrl = objectStore.getPresignedUrl(bucket, key)
|
||||||
return {
|
return {
|
||||||
data: {
|
data: {
|
||||||
size,
|
size,
|
||||||
|
|
|
@ -4,30 +4,33 @@ import {
|
||||||
QueryJson,
|
QueryJson,
|
||||||
RelationshipFieldMetadata,
|
RelationshipFieldMetadata,
|
||||||
Row,
|
Row,
|
||||||
SearchFilters,
|
|
||||||
RowSearchParams,
|
RowSearchParams,
|
||||||
|
SearchFilters,
|
||||||
SearchResponse,
|
SearchResponse,
|
||||||
SortDirection,
|
SortDirection,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
SortType,
|
SortType,
|
||||||
|
SqlClient,
|
||||||
Table,
|
Table,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import SqlQueryBuilder from "../../../../integrations/base/sql"
|
|
||||||
import { SqlClient } from "../../../../integrations/utils"
|
|
||||||
import {
|
import {
|
||||||
buildInternalRelationships,
|
buildInternalRelationships,
|
||||||
sqlOutputProcessing,
|
sqlOutputProcessing,
|
||||||
} from "../../../../api/controllers/row/utils"
|
} from "../../../../api/controllers/row/utils"
|
||||||
import sdk from "../../../index"
|
import sdk from "../../../index"
|
||||||
import { context, SQLITE_DESIGN_DOC_ID } from "@budibase/backend-core"
|
|
||||||
import {
|
import {
|
||||||
CONSTANT_INTERNAL_ROW_COLS,
|
context,
|
||||||
|
sql,
|
||||||
|
SQLITE_DESIGN_DOC_ID,
|
||||||
SQS_DATASOURCE_INTERNAL,
|
SQS_DATASOURCE_INTERNAL,
|
||||||
} from "../../../../db/utils"
|
} from "@budibase/backend-core"
|
||||||
|
import { CONSTANT_INTERNAL_ROW_COLS } from "../../../../db/utils"
|
||||||
import AliasTables from "../sqlAlias"
|
import AliasTables from "../sqlAlias"
|
||||||
import { outputProcessing } from "../../../../utilities/rowProcessor"
|
import { outputProcessing } from "../../../../utilities/rowProcessor"
|
||||||
import pick from "lodash/pick"
|
import pick from "lodash/pick"
|
||||||
|
|
||||||
|
const builder = new sql.Sql(SqlClient.SQL_LITE)
|
||||||
|
|
||||||
function buildInternalFieldList(
|
function buildInternalFieldList(
|
||||||
table: Table,
|
table: Table,
|
||||||
tables: Table[],
|
tables: Table[],
|
||||||
|
@ -97,13 +100,39 @@ function buildTableMap(tables: Table[]) {
|
||||||
return tableMap
|
return tableMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function runSqlQuery(json: QueryJson, tables: Table[]) {
|
||||||
|
const alias = new AliasTables(tables.map(table => table.name))
|
||||||
|
return await alias.queryWithAliasing(json, async json => {
|
||||||
|
const query = builder._query(json, {
|
||||||
|
disableReturning: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (Array.isArray(query)) {
|
||||||
|
throw new Error("SQS cannot currently handle multiple queries")
|
||||||
|
}
|
||||||
|
|
||||||
|
let sql = query.sql
|
||||||
|
let bindings = query.bindings
|
||||||
|
|
||||||
|
// quick hack for docIds
|
||||||
|
sql = sql.replace(/`doc1`.`rowId`/g, "`doc1.rowId`")
|
||||||
|
sql = sql.replace(/`doc2`.`rowId`/g, "`doc2.rowId`")
|
||||||
|
|
||||||
|
if (Array.isArray(query)) {
|
||||||
|
throw new Error("SQS cannot currently handle multiple queries")
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = context.getAppDB()
|
||||||
|
return await db.sql<Row>(sql, bindings)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export async function search(
|
export async function search(
|
||||||
options: RowSearchParams,
|
options: RowSearchParams,
|
||||||
table: Table
|
table: Table
|
||||||
): Promise<SearchResponse<Row>> {
|
): Promise<SearchResponse<Row>> {
|
||||||
const { paginate, query, ...params } = options
|
const { paginate, query, ...params } = options
|
||||||
|
|
||||||
const builder = new SqlQueryBuilder(SqlClient.SQL_LITE)
|
|
||||||
const allTables = await sdk.tables.getAllInternalTables()
|
const allTables = await sdk.tables.getAllInternalTables()
|
||||||
const allTablesMap = buildTableMap(allTables)
|
const allTablesMap = buildTableMap(allTables)
|
||||||
if (!table) {
|
if (!table) {
|
||||||
|
@ -146,62 +175,72 @@ export async function search(
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (params.bookmark && typeof params.bookmark !== "number") {
|
||||||
|
throw new Error("Unable to paginate with string based bookmarks")
|
||||||
|
}
|
||||||
|
const bookmark: number = (params.bookmark as number) || 1
|
||||||
|
const limit = params.limit
|
||||||
if (paginate && params.limit) {
|
if (paginate && params.limit) {
|
||||||
request.paginate = {
|
request.paginate = {
|
||||||
limit: params.limit,
|
limit: params.limit + 1,
|
||||||
page: params.bookmark,
|
page: bookmark,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const alias = new AliasTables(allTables.map(table => table.name))
|
const rows = await runSqlQuery(request, allTables)
|
||||||
const rows = await alias.queryWithAliasing(request, async json => {
|
|
||||||
const query = builder._query(json, {
|
|
||||||
disableReturning: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (Array.isArray(query)) {
|
// process from the format of tableId.column to expected format also
|
||||||
throw new Error("SQS cannot currently handle multiple queries")
|
// make sure JSON columns corrected
|
||||||
}
|
const processed = builder.convertJsonStringColumns<Row>(
|
||||||
|
table,
|
||||||
let sql = query.sql
|
await sqlOutputProcessing(rows, table!, allTablesMap, relationships, {
|
||||||
let bindings = query.bindings
|
|
||||||
|
|
||||||
// quick hack for docIds
|
|
||||||
sql = sql.replace(/`doc1`.`rowId`/g, "`doc1.rowId`")
|
|
||||||
sql = sql.replace(/`doc2`.`rowId`/g, "`doc2.rowId`")
|
|
||||||
|
|
||||||
const db = context.getAppDB()
|
|
||||||
const rows = await db.sql<Row>(sql, bindings)
|
|
||||||
return rows
|
|
||||||
})
|
|
||||||
|
|
||||||
// process from the format of tableId.column to expected format
|
|
||||||
const processed = await sqlOutputProcessing(
|
|
||||||
rows,
|
|
||||||
table!,
|
|
||||||
allTablesMap,
|
|
||||||
relationships,
|
|
||||||
{
|
|
||||||
sqs: true,
|
sqs: true,
|
||||||
}
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const output = {
|
// check for pagination final row
|
||||||
rows: await outputProcessing<Row[]>(table, processed, {
|
let nextRow: Row | undefined
|
||||||
preserveLinks: true,
|
if (paginate && params.limit && processed.length > params.limit) {
|
||||||
squash: true,
|
nextRow = processed.pop()
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get the rows
|
||||||
|
let finalRows = await outputProcessing<Row[]>(table, processed, {
|
||||||
|
preserveLinks: true,
|
||||||
|
squash: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// check if we need to pick specific rows out
|
||||||
if (options.fields) {
|
if (options.fields) {
|
||||||
const fields = [...options.fields, ...CONSTANT_INTERNAL_ROW_COLS]
|
const fields = [...options.fields, ...CONSTANT_INTERNAL_ROW_COLS]
|
||||||
output.rows = output.rows.map((r: any) => pick(r, fields))
|
finalRows = finalRows.map((r: any) => pick(r, fields))
|
||||||
}
|
}
|
||||||
|
|
||||||
return output
|
// check for pagination
|
||||||
|
if (paginate && limit) {
|
||||||
|
const response: SearchResponse<Row> = {
|
||||||
|
rows: finalRows,
|
||||||
|
}
|
||||||
|
const prevLimit = request.paginate!.limit
|
||||||
|
request.paginate = {
|
||||||
|
limit: 1,
|
||||||
|
page: bookmark * prevLimit + 1,
|
||||||
|
}
|
||||||
|
const hasNextPage = !!nextRow
|
||||||
|
response.hasNextPage = hasNextPage
|
||||||
|
if (hasNextPage) {
|
||||||
|
response.bookmark = bookmark + 1
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
rows: finalRows,
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const msg = typeof err === "string" ? err : err.message
|
const msg = typeof err === "string" ? err : err.message
|
||||||
if (err.status === 404 && err.message?.includes(SQLITE_DESIGN_DOC_ID)) {
|
if (err.status === 404 && msg?.includes(SQLITE_DESIGN_DOC_ID)) {
|
||||||
await sdk.tables.sqs.syncDefinition()
|
await sdk.tables.sqs.syncDefinition()
|
||||||
return search(options, table)
|
return search(options, table)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,13 @@ import {
|
||||||
QueryJson,
|
QueryJson,
|
||||||
Row,
|
Row,
|
||||||
SearchFilters,
|
SearchFilters,
|
||||||
|
SqlClient,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
import { SQS_DATASOURCE_INTERNAL } from "@budibase/backend-core"
|
||||||
import { getSQLClient } from "./utils"
|
import { getSQLClient } from "./utils"
|
||||||
import { cloneDeep } from "lodash"
|
import { cloneDeep } from "lodash"
|
||||||
import datasources from "../datasources"
|
import datasources from "../datasources"
|
||||||
import { makeExternalQuery } from "../../../integrations/base/query"
|
import { makeExternalQuery } from "../../../integrations/base/query"
|
||||||
import { SqlClient } from "../../../integrations/utils"
|
|
||||||
import { SQS_DATASOURCE_INTERNAL } from "../../../db/utils"
|
|
||||||
|
|
||||||
const WRITE_OPERATIONS: Operation[] = [
|
const WRITE_OPERATIONS: Operation[] = [
|
||||||
Operation.CREATE,
|
Operation.CREATE,
|
||||||
|
|
|
@ -11,12 +11,13 @@ import {
|
||||||
SourceName,
|
SourceName,
|
||||||
Table,
|
Table,
|
||||||
TableSchema,
|
TableSchema,
|
||||||
|
SqlClient,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { makeExternalQuery } from "../../../integrations/base/query"
|
import { makeExternalQuery } from "../../../integrations/base/query"
|
||||||
import { Format } from "../../../api/controllers/view/exporters"
|
import { Format } from "../../../api/controllers/view/exporters"
|
||||||
import sdk from "../.."
|
import sdk from "../.."
|
||||||
import { isRelationshipColumn } from "../../../db/utils"
|
import { isRelationshipColumn } from "../../../db/utils"
|
||||||
import { isSQL, SqlClient } from "../../../integrations/utils"
|
import { isSQL } from "../../../integrations/utils"
|
||||||
|
|
||||||
const SQL_CLIENT_SOURCE_MAP: Record<SourceName, SqlClient | undefined> = {
|
const SQL_CLIENT_SOURCE_MAP: Record<SourceName, SqlClient | undefined> = {
|
||||||
[SourceName.POSTGRES]: SqlClient.POSTGRES,
|
[SourceName.POSTGRES]: SqlClient.POSTGRES,
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import datasources from "../datasources"
|
import datasources from "../datasources"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
|
import env from "../../../environment"
|
||||||
|
|
||||||
export function processTable(table: Table): Table {
|
export function processTable(table: Table): Table {
|
||||||
if (!table) {
|
if (!table) {
|
||||||
|
@ -27,12 +28,16 @@ export function processTable(table: Table): Table {
|
||||||
sourceType: TableSourceType.EXTERNAL,
|
sourceType: TableSourceType.EXTERNAL,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
const processed: Table = {
|
||||||
...table,
|
...table,
|
||||||
type: "table",
|
type: "table",
|
||||||
sourceId: table.sourceId || INTERNAL_TABLE_SOURCE_ID,
|
sourceId: table.sourceId || INTERNAL_TABLE_SOURCE_ID,
|
||||||
sourceType: TableSourceType.INTERNAL,
|
sourceType: TableSourceType.INTERNAL,
|
||||||
}
|
}
|
||||||
|
if (env.SQS_SEARCH_ENABLE) {
|
||||||
|
processed.sql = !!env.SQS_SEARCH_ENABLE
|
||||||
|
}
|
||||||
|
return processed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,33 +1,20 @@
|
||||||
import { context, SQLITE_DESIGN_DOC_ID } from "@budibase/backend-core"
|
import { context, sql, SQLITE_DESIGN_DOC_ID } from "@budibase/backend-core"
|
||||||
import {
|
import {
|
||||||
FieldType,
|
FieldType,
|
||||||
RelationshipFieldMetadata,
|
RelationshipFieldMetadata,
|
||||||
SQLiteDefinition,
|
SQLiteDefinition,
|
||||||
|
PreSaveSQLiteDefinition,
|
||||||
SQLiteTable,
|
SQLiteTable,
|
||||||
SQLiteTables,
|
SQLiteTables,
|
||||||
SQLiteType,
|
SQLiteType,
|
||||||
Table,
|
Table,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { cloneDeep } from "lodash"
|
|
||||||
import tablesSdk from "../"
|
import tablesSdk from "../"
|
||||||
import {
|
import {
|
||||||
CONSTANT_INTERNAL_ROW_COLS,
|
CONSTANT_INTERNAL_ROW_COLS,
|
||||||
generateJunctionTableID,
|
generateJunctionTableID,
|
||||||
} from "../../../../db/utils"
|
} from "../../../../db/utils"
|
||||||
|
|
||||||
type PreSaveSQLiteDefinition = Omit<SQLiteDefinition, "_rev">
|
|
||||||
|
|
||||||
const BASIC_SQLITE_DOC: PreSaveSQLiteDefinition = {
|
|
||||||
_id: SQLITE_DESIGN_DOC_ID,
|
|
||||||
language: "sqlite",
|
|
||||||
sql: {
|
|
||||||
tables: {},
|
|
||||||
options: {
|
|
||||||
table_name: "tableId",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const FieldTypeMap: Record<FieldType, SQLiteType> = {
|
const FieldTypeMap: Record<FieldType, SQLiteType> = {
|
||||||
[FieldType.BOOLEAN]: SQLiteType.NUMERIC,
|
[FieldType.BOOLEAN]: SQLiteType.NUMERIC,
|
||||||
[FieldType.DATETIME]: SQLiteType.TEXT,
|
[FieldType.DATETIME]: SQLiteType.TEXT,
|
||||||
|
@ -108,7 +95,7 @@ function mapTable(table: Table): SQLiteTables {
|
||||||
// nothing exists, need to iterate though existing tables
|
// nothing exists, need to iterate though existing tables
|
||||||
async function buildBaseDefinition(): Promise<PreSaveSQLiteDefinition> {
|
async function buildBaseDefinition(): Promise<PreSaveSQLiteDefinition> {
|
||||||
const tables = await tablesSdk.getAllInternalTables()
|
const tables = await tablesSdk.getAllInternalTables()
|
||||||
const definition = cloneDeep(BASIC_SQLITE_DOC)
|
const definition = sql.designDoc.base("tableId")
|
||||||
for (let table of tables) {
|
for (let table of tables) {
|
||||||
definition.sql.tables = {
|
definition.sql.tables = {
|
||||||
...definition.sql.tables,
|
...definition.sql.tables,
|
||||||
|
|
|
@ -28,11 +28,6 @@ import fs from "fs"
|
||||||
|
|
||||||
let STARTUP_RAN = false
|
let STARTUP_RAN = false
|
||||||
|
|
||||||
if (env.isProd() && env.SQS_SEARCH_ENABLE) {
|
|
||||||
console.error("Stopping service - SQS search support is not yet available.")
|
|
||||||
process.exit(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function initRoutes(app: Koa) {
|
async function initRoutes(app: Koa) {
|
||||||
if (!env.isTest()) {
|
if (!env.isTest()) {
|
||||||
const plugin = await bullboard.init()
|
const plugin = await bullboard.init()
|
||||||
|
|
|
@ -118,6 +118,15 @@ export enum FieldType {
|
||||||
BB_REFERENCE_SINGLE = "bb_reference_single",
|
BB_REFERENCE_SINGLE = "bb_reference_single",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const JsonTypes = [
|
||||||
|
FieldType.ATTACHMENT_SINGLE,
|
||||||
|
FieldType.ATTACHMENTS,
|
||||||
|
// only BB_REFERENCE is JSON, it's an array, BB_REFERENCE_SINGLE is a string type
|
||||||
|
FieldType.BB_REFERENCE,
|
||||||
|
FieldType.JSON,
|
||||||
|
FieldType.ARRAY,
|
||||||
|
]
|
||||||
|
|
||||||
export interface RowAttachment {
|
export interface RowAttachment {
|
||||||
size: number
|
size: number
|
||||||
name: string
|
name: string
|
||||||
|
|
|
@ -29,3 +29,5 @@ export interface SQLiteDefinition {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PreSaveSQLiteDefinition = Omit<SQLiteDefinition, "_rev">
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { Document } from "../document"
|
||||||
import { Event } from "../../sdk"
|
import { Event } from "../../sdk"
|
||||||
|
|
||||||
export const AuditLogSystemUser = "SYSTEM"
|
export const AuditLogSystemUser = "SYSTEM"
|
||||||
|
export const AUDIT_LOG_TYPE = "auditLog"
|
||||||
|
|
||||||
export type FallbackInfo = {
|
export type FallbackInfo = {
|
||||||
appName?: string
|
appName?: string
|
||||||
|
@ -15,5 +16,6 @@ export interface AuditLogDoc extends Document {
|
||||||
timestamp: string
|
timestamp: string
|
||||||
metadata: any
|
metadata: any
|
||||||
name: string
|
name: string
|
||||||
|
type?: "auditLog"
|
||||||
fallback?: FallbackInfo
|
fallback?: FallbackInfo
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,9 @@ export enum SearchFilterOperator {
|
||||||
|
|
||||||
export interface SearchFilters {
|
export interface SearchFilters {
|
||||||
allOr?: boolean
|
allOr?: boolean
|
||||||
|
// TODO: this is just around for now - we need a better way to do or/and
|
||||||
|
// allows just fuzzy to be or - all the fuzzy/like parameters
|
||||||
|
fuzzyOr?: boolean
|
||||||
onEmptyFilter?: EmptyFilterOption
|
onEmptyFilter?: EmptyFilterOption
|
||||||
[SearchFilterOperator.STRING]?: {
|
[SearchFilterOperator.STRING]?: {
|
||||||
[key: string]: string
|
[key: string]: string
|
||||||
|
@ -61,6 +64,11 @@ export interface SearchFilters {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SearchFilterKey = keyof Omit<
|
||||||
|
SearchFilters,
|
||||||
|
"allOr" | "onEmptyFilter" | "fuzzyOr"
|
||||||
|
>
|
||||||
|
|
||||||
export type SearchQueryFields = Omit<SearchFilters, "allOr" | "onEmptyFilter">
|
export type SearchQueryFields = Omit<SearchFilters, "allOr" | "onEmptyFilter">
|
||||||
|
|
||||||
export interface SortJson {
|
export interface SortJson {
|
||||||
|
@ -117,6 +125,11 @@ export interface QueryJson {
|
||||||
tableAliases?: Record<string, string>
|
tableAliases?: Record<string, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface QueryOptions {
|
||||||
|
disableReturning?: boolean
|
||||||
|
disableBindings?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export type SqlQueryBinding = Knex.Value[]
|
export type SqlQueryBinding = Knex.Value[]
|
||||||
|
|
||||||
export interface SqlQuery {
|
export interface SqlQuery {
|
||||||
|
@ -128,3 +141,11 @@ export enum EmptyFilterOption {
|
||||||
RETURN_ALL = "all",
|
RETURN_ALL = "all",
|
||||||
RETURN_NONE = "none",
|
RETURN_NONE = "none",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum SqlClient {
|
||||||
|
MS_SQL = "mssql",
|
||||||
|
POSTGRES = "pg",
|
||||||
|
MY_SQL = "mysql2",
|
||||||
|
ORACLE = "oracledb",
|
||||||
|
SQL_LITE = "sqlite3",
|
||||||
|
}
|
||||||
|
|
|
@ -70,7 +70,8 @@
|
||||||
"pouchdb-all-dbs": "1.1.1",
|
"pouchdb-all-dbs": "1.1.1",
|
||||||
"server-destroy": "1.0.1",
|
"server-destroy": "1.0.1",
|
||||||
"undici": "^6.0.1",
|
"undici": "^6.0.1",
|
||||||
"undici-types": "^6.0.1"
|
"undici-types": "^6.0.1",
|
||||||
|
"knex": "2.4.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@swc/core": "1.3.71",
|
"@swc/core": "1.3.71",
|
||||||
|
|
|
@ -4,8 +4,13 @@ const compress = require("koa-compress")
|
||||||
|
|
||||||
import zlib from "zlib"
|
import zlib from "zlib"
|
||||||
import { routes } from "./routes"
|
import { routes } from "./routes"
|
||||||
import { middleware as pro } from "@budibase/pro"
|
import { middleware as pro, sdk } from "@budibase/pro"
|
||||||
import { auth, middleware } from "@budibase/backend-core"
|
import { auth, middleware } from "@budibase/backend-core"
|
||||||
|
import env from "../environment"
|
||||||
|
|
||||||
|
if (env.SQS_SEARCH_ENABLE) {
|
||||||
|
sdk.auditLogs.useSQLSearch()
|
||||||
|
}
|
||||||
|
|
||||||
const PUBLIC_ENDPOINTS = [
|
const PUBLIC_ENDPOINTS = [
|
||||||
// deprecated single tenant sso callback
|
// deprecated single tenant sso callback
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { mocks, structures } from "@budibase/backend-core/tests"
|
import { mocks, structures } from "@budibase/backend-core/tests"
|
||||||
import { context, events } from "@budibase/backend-core"
|
import { context, events } from "@budibase/backend-core"
|
||||||
import { Event, IdentityType } from "@budibase/types"
|
import { Event, IdentityType } from "@budibase/types"
|
||||||
|
import { auditLogs } from "@budibase/pro"
|
||||||
import { TestConfiguration } from "../../../../tests"
|
import { TestConfiguration } from "../../../../tests"
|
||||||
|
|
||||||
mocks.licenses.useAuditLogs()
|
mocks.licenses.useAuditLogs()
|
||||||
|
@ -12,10 +13,13 @@ const BASE_IDENTITY = {
|
||||||
const USER_AUDIT_LOG_COUNT = 3
|
const USER_AUDIT_LOG_COUNT = 3
|
||||||
const APP_ID = "app_1"
|
const APP_ID = "app_1"
|
||||||
|
|
||||||
describe("/api/global/auditlogs", () => {
|
describe.each(["lucene", "sql"])("/api/global/auditlogs (%s)", method => {
|
||||||
const config = new TestConfiguration()
|
const config = new TestConfiguration()
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
|
if (method === "sql") {
|
||||||
|
auditLogs.useSQLSearch()
|
||||||
|
}
|
||||||
await config.beforeAll()
|
await config.beforeAll()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@ const environment = {
|
||||||
DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL,
|
DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL,
|
||||||
SMTP_FALLBACK_ENABLED: process.env.SMTP_FALLBACK_ENABLED,
|
SMTP_FALLBACK_ENABLED: process.env.SMTP_FALLBACK_ENABLED,
|
||||||
DISABLE_DEVELOPER_LICENSE: process.env.DISABLE_DEVELOPER_LICENSE,
|
DISABLE_DEVELOPER_LICENSE: process.env.DISABLE_DEVELOPER_LICENSE,
|
||||||
|
SQS_SEARCH_ENABLE: process.env.SQS_SEARCH_ENABLE,
|
||||||
// smtp
|
// smtp
|
||||||
SMTP_USER: process.env.SMTP_USER,
|
SMTP_USER: process.env.SMTP_USER,
|
||||||
SMTP_PASSWORD: process.env.SMTP_PASSWORD,
|
SMTP_PASSWORD: process.env.SMTP_PASSWORD,
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
yarn build:apps
|
||||||
|
version=$(./scripts/getCurrentVersion.sh)
|
||||||
|
docker build -f hosting/single/Dockerfile -t budibase:sqs --build-arg BUDIBASE_VERSION=$version --build-arg TARGETBUILD=single --build-arg BASEIMG=budibase/couchdb:v3.3.3-sqs .
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
yarn build --scope @budibase/server --scope @budibase/worker
|
yarn build:apps
|
||||||
version=$(./scripts/getCurrentVersion.sh)
|
version=$(./scripts/getCurrentVersion.sh)
|
||||||
docker build -f hosting/single/Dockerfile -t budibase:latest --build-arg BUDIBASE_VERSION=$version --build-arg TARGETBUILD=single .
|
docker build -f hosting/single/Dockerfile -t budibase:latest --build-arg BUDIBASE_VERSION=$version --build-arg TARGETBUILD=single .
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
const start = Date.now()
|
const start = Date.now()
|
||||||
|
|
||||||
const fs = require("fs")
|
const fs = require("fs")
|
||||||
const { cp, readdir, copyFile, mkdir } = require('node:fs/promises');
|
const { cp, readdir, copyFile, mkdir } = require("node:fs/promises")
|
||||||
const path = require("path")
|
const path = require("path")
|
||||||
|
|
||||||
const { build } = require("esbuild")
|
const { build } = require("esbuild")
|
||||||
const { compile } = require('svelte/compiler')
|
const { compile } = require("svelte/compiler")
|
||||||
|
|
||||||
const {
|
const {
|
||||||
default: TsconfigPathsPlugin,
|
default: TsconfigPathsPlugin,
|
||||||
|
@ -15,13 +15,13 @@ const {
|
||||||
const { nodeExternalsPlugin } = require("esbuild-node-externals")
|
const { nodeExternalsPlugin } = require("esbuild-node-externals")
|
||||||
|
|
||||||
const svelteCompilePlugin = {
|
const svelteCompilePlugin = {
|
||||||
name: 'svelteCompile',
|
name: "svelteCompile",
|
||||||
setup(build) {
|
setup(build) {
|
||||||
// Compiles `.svelte` files into JS classes so that they can be directly imported into our
|
// Compiles `.svelte` files into JS classes so that they can be directly imported into our
|
||||||
// Typescript packages
|
// Typescript packages
|
||||||
build.onLoad({ filter: /\.svelte$/ }, async (args) => {
|
build.onLoad({ filter: /\.svelte$/ }, async args => {
|
||||||
const source = await fs.promises.readFile(args.path, 'utf8')
|
const source = await fs.promises.readFile(args.path, "utf8")
|
||||||
const dir = path.dirname(args.path);
|
const dir = path.dirname(args.path)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { js } = compile(source, { css: "injected", generate: "ssr" })
|
const { js } = compile(source, { css: "injected", generate: "ssr" })
|
||||||
|
@ -31,15 +31,15 @@ const svelteCompilePlugin = {
|
||||||
contents: js.code,
|
contents: js.code,
|
||||||
// The loader this is passed to, basically how the above provided content is "treated",
|
// The loader this is passed to, basically how the above provided content is "treated",
|
||||||
// the contents provided above will be transpiled and bundled like any other JS file.
|
// the contents provided above will be transpiled and bundled like any other JS file.
|
||||||
loader: 'js',
|
loader: "js",
|
||||||
// Where to resolve any imports present in the loaded file
|
// Where to resolve any imports present in the loaded file
|
||||||
resolveDir: dir
|
resolveDir: dir,
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return { errors: [JSON.stringify(e)] }
|
return { errors: [JSON.stringify(e)] }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var { argv } = require("yargs")
|
var { argv } = require("yargs")
|
||||||
|
@ -75,7 +75,7 @@ async function runBuild(entry, outfile) {
|
||||||
svelteCompilePlugin,
|
svelteCompilePlugin,
|
||||||
TsconfigPathsPlugin({ tsconfig: tsconfigPathPluginContent }),
|
TsconfigPathsPlugin({ tsconfig: tsconfigPathPluginContent }),
|
||||||
nodeExternalsPlugin({
|
nodeExternalsPlugin({
|
||||||
allowList: ["@budibase/frontend-core", "svelte"]
|
allowList: ["@budibase/frontend-core", "svelte"],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
preserveSymlinks: true,
|
preserveSymlinks: true,
|
||||||
|
@ -90,25 +90,39 @@ async function runBuild(entry, outfile) {
|
||||||
"bcryptjs",
|
"bcryptjs",
|
||||||
"graphql/*",
|
"graphql/*",
|
||||||
"bson",
|
"bson",
|
||||||
|
"better-sqlite3",
|
||||||
|
"sqlite3",
|
||||||
|
"mysql",
|
||||||
|
"mysql2",
|
||||||
|
"oracle",
|
||||||
|
"pg",
|
||||||
|
"pg-query-stream",
|
||||||
|
"pg-native",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
await mkdir('dist', { recursive: true });
|
await mkdir("dist", { recursive: true })
|
||||||
|
|
||||||
const hbsFiles = (async () => {
|
const hbsFiles = (async () => {
|
||||||
const dir = await readdir('./', { recursive: true });
|
const dir = await readdir("./", { recursive: true })
|
||||||
const files = dir.filter(entry => entry.endsWith('.hbs') || entry.endsWith('ivm.bundle.js'));
|
const files = dir.filter(
|
||||||
const fileCopyPromises = files.map(file => copyFile(file, `dist/${path.basename(file)}`))
|
entry => entry.endsWith(".hbs") || entry.endsWith("ivm.bundle.js")
|
||||||
|
)
|
||||||
|
const fileCopyPromises = files.map(file =>
|
||||||
|
copyFile(file, `dist/${path.basename(file)}`)
|
||||||
|
)
|
||||||
|
|
||||||
await Promise.all(fileCopyPromises)
|
await Promise.all(fileCopyPromises)
|
||||||
})()
|
})()
|
||||||
|
|
||||||
const oldClientVersions = (async () => {
|
const oldClientVersions = (async () => {
|
||||||
try {
|
try {
|
||||||
await cp('./build/oldClientVersions', './dist/oldClientVersions', { recursive: true });
|
await cp("./build/oldClientVersions", "./dist/oldClientVersions", {
|
||||||
|
recursive: true,
|
||||||
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.code !== "EEXIST" && e.code !== "ENOENT") {
|
if (e.code !== "EEXIST" && e.code !== "ENOENT") {
|
||||||
throw e;
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
|
|
@ -6,7 +6,6 @@ if [ ! -d "./packages/pro/src" ]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
yarn build --scope @budibase/server --scope @budibase/worker
|
yarn build:apps
|
||||||
docker-compose -f hosting/docker-compose.build.yaml -f hosting/docker-compose.dev.yaml --env-file hosting/.env up --build --scale proxy-service=0
|
docker compose -f hosting/docker-compose.build.yaml -f hosting/docker-compose.dev.yaml --env-file hosting/.env up --build --scale proxy-service=0
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue