wip
This commit is contained in:
parent
20bad903cc
commit
50d1972127
|
@ -109,6 +109,26 @@ function parseFilters(filters: SearchFilters | undefined): SearchFilters {
|
||||||
return filters
|
return filters
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OracleDB can't use character-large-objects (CLOBs) in WHERE clauses,
|
||||||
|
// so when we use them we need to wrap them in to_char(). This function
|
||||||
|
// converts a field name to the appropriate identifier.
|
||||||
|
function convertClobs(client: SqlClient, table: Table, field: string): string {
|
||||||
|
const parts = field.split(".")
|
||||||
|
const col = parts.pop()!
|
||||||
|
const schema = table.schema[col]
|
||||||
|
let identifier = quotedIdentifier(client, field)
|
||||||
|
if (
|
||||||
|
schema.type === FieldType.STRING ||
|
||||||
|
schema.type === FieldType.LONGFORM ||
|
||||||
|
schema.type === FieldType.BB_REFERENCE_SINGLE ||
|
||||||
|
schema.type === FieldType.OPTIONS ||
|
||||||
|
schema.type === FieldType.BARCODEQR
|
||||||
|
) {
|
||||||
|
identifier = `to_char(${identifier})`
|
||||||
|
}
|
||||||
|
return identifier
|
||||||
|
}
|
||||||
|
|
||||||
function generateSelectStatement(
|
function generateSelectStatement(
|
||||||
json: QueryJson,
|
json: QueryJson,
|
||||||
knex: Knex
|
knex: Knex
|
||||||
|
@ -372,7 +392,15 @@ class InternalBuilder {
|
||||||
iterate(
|
iterate(
|
||||||
filters.oneOf,
|
filters.oneOf,
|
||||||
(key: string, array) => {
|
(key: string, array) => {
|
||||||
query = query[fnc](key, Array.isArray(array) ? array : [array])
|
if (this.client === SqlClient.ORACLE) {
|
||||||
|
key = convertClobs(this.client, table, key)
|
||||||
|
query = query.whereRaw(
|
||||||
|
`${key} IN (?)`,
|
||||||
|
Array.isArray(array) ? array : [array]
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
query = query[fnc](key, Array.isArray(array) ? array : [array])
|
||||||
|
}
|
||||||
},
|
},
|
||||||
(key: string[], array) => {
|
(key: string[], array) => {
|
||||||
query = query[fnc](key, Array.isArray(array) ? array : [array])
|
query = query[fnc](key, Array.isArray(array) ? array : [array])
|
||||||
|
@ -436,8 +464,9 @@ class InternalBuilder {
|
||||||
[value]
|
[value]
|
||||||
)
|
)
|
||||||
} else if (this.client === SqlClient.ORACLE) {
|
} else if (this.client === SqlClient.ORACLE) {
|
||||||
|
const identifier = convertClobs(this.client, table, key)
|
||||||
query = query[fnc](
|
query = query[fnc](
|
||||||
`COALESCE(${quotedIdentifier(this.client, key)}, -1) = ?`,
|
`(${identifier} IS NOT NULL AND ${identifier} = ?)`,
|
||||||
[value]
|
[value]
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -460,8 +489,9 @@ class InternalBuilder {
|
||||||
[value]
|
[value]
|
||||||
)
|
)
|
||||||
} else if (this.client === SqlClient.ORACLE) {
|
} else if (this.client === SqlClient.ORACLE) {
|
||||||
|
const identifier = convertClobs(this.client, table, key)
|
||||||
query = query[fnc](
|
query = query[fnc](
|
||||||
`COALESCE(${quotedIdentifier(this.client, key)}, -1) != ?`,
|
`(${identifier} IS NOT NULL AND ${identifier} != ?)`,
|
||||||
[value]
|
[value]
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -707,8 +737,11 @@ class InternalBuilder {
|
||||||
}
|
}
|
||||||
const ret = query.insert(parsedBody).onConflict(primary).merge()
|
const ret = query.insert(parsedBody).onConflict(primary).merge()
|
||||||
return ret
|
return ret
|
||||||
} else if (this.client === SqlClient.MS_SQL) {
|
} else if (
|
||||||
// No upsert or onConflict support in MSSQL yet, see:
|
this.client === SqlClient.MS_SQL ||
|
||||||
|
this.client === SqlClient.ORACLE
|
||||||
|
) {
|
||||||
|
// No upsert or onConflict support in MSSQL/Oracle yet, see:
|
||||||
// https://github.com/knex/knex/pull/6050
|
// https://github.com/knex/knex/pull/6050
|
||||||
return query.insert(parsedBody)
|
return query.insert(parsedBody)
|
||||||
}
|
}
|
||||||
|
@ -867,7 +900,7 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
|
||||||
const config: Knex.Config = {
|
const config: Knex.Config = {
|
||||||
client: sqlClient,
|
client: sqlClient,
|
||||||
}
|
}
|
||||||
if (sqlClient === SqlClient.SQL_LITE) {
|
if (sqlClient === SqlClient.SQL_LITE || sqlClient === SqlClient.ORACLE) {
|
||||||
config.useNullAsDefault = true
|
config.useNullAsDefault = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
const executeMock = jest.fn(() => ({
|
|
||||||
rows: [
|
|
||||||
{
|
|
||||||
a: "string",
|
|
||||||
b: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}))
|
|
||||||
|
|
||||||
const closeMock = jest.fn()
|
|
||||||
|
|
||||||
class Connection {
|
|
||||||
execute = executeMock
|
|
||||||
close = closeMock
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
getConnection: jest.fn(() => new Connection()),
|
|
||||||
executeMock,
|
|
||||||
closeMock,
|
|
||||||
}
|
|
|
@ -40,13 +40,13 @@ import { structures } from "@budibase/backend-core/tests"
|
||||||
import { DEFAULT_EMPLOYEE_TABLE_SCHEMA } from "../../../db/defaultData/datasource_bb_default"
|
import { DEFAULT_EMPLOYEE_TABLE_SCHEMA } from "../../../db/defaultData/datasource_bb_default"
|
||||||
|
|
||||||
describe.each([
|
describe.each([
|
||||||
//["in-memory", undefined],
|
// ["in-memory", undefined],
|
||||||
//["lucene", undefined],
|
// ["lucene", undefined],
|
||||||
//["sqs", undefined],
|
// ["sqs", undefined],
|
||||||
//[DatabaseName.POSTGRES, getDatasource(DatabaseName.POSTGRES)],
|
// [DatabaseName.POSTGRES, getDatasource(DatabaseName.POSTGRES)],
|
||||||
//[DatabaseName.MYSQL, getDatasource(DatabaseName.MYSQL)],
|
// [DatabaseName.MYSQL, getDatasource(DatabaseName.MYSQL)],
|
||||||
//[DatabaseName.SQL_SERVER, getDatasource(DatabaseName.SQL_SERVER)],
|
// [DatabaseName.SQL_SERVER, getDatasource(DatabaseName.SQL_SERVER)],
|
||||||
//[DatabaseName.MARIADB, getDatasource(DatabaseName.MARIADB)],
|
// [DatabaseName.MARIADB, getDatasource(DatabaseName.MARIADB)],
|
||||||
[DatabaseName.ORACLE, getDatasource(DatabaseName.ORACLE)],
|
[DatabaseName.ORACLE, getDatasource(DatabaseName.ORACLE)],
|
||||||
])("search (%s)", (name, dsProvider) => {
|
])("search (%s)", (name, dsProvider) => {
|
||||||
const isSqs = name === "sqs"
|
const isSqs = name === "sqs"
|
||||||
|
@ -292,7 +292,7 @@ describe.each([
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("equal", () => {
|
describe("equal", () => {
|
||||||
it.only("successfully finds true row", async () => {
|
it("successfully finds true row", async () => {
|
||||||
await expectQuery({ equal: { isTrue: true } }).toMatchExactly([
|
await expectQuery({ equal: { isTrue: true } }).toMatchExactly([
|
||||||
{ isTrue: true },
|
{ isTrue: true },
|
||||||
])
|
])
|
||||||
|
@ -1577,12 +1577,15 @@ describe.each([
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("bigints", () => {
|
describe.only("bigints", () => {
|
||||||
const SMALL = "1"
|
const SMALL = "1"
|
||||||
const MEDIUM = "10000000"
|
const MEDIUM = "10000000"
|
||||||
|
|
||||||
// Our bigints are int64s in most datasources.
|
// Our bigints are int64s in most datasources.
|
||||||
const BIG = "9223372036854775807"
|
let BIG = "9223372036854775807"
|
||||||
|
if (name === DatabaseName.ORACLE) {
|
||||||
|
// BIG = "9223372036854775808"
|
||||||
|
}
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
table = await createTable({
|
table = await createTable({
|
||||||
|
@ -2415,25 +2418,25 @@ describe.each([
|
||||||
|
|
||||||
describe.each([
|
describe.each([
|
||||||
"名前", // Japanese for "name"
|
"名前", // Japanese for "name"
|
||||||
"Benutzer-ID", // German for "user ID", includes a hyphen
|
// "Benutzer-ID", // German for "user ID", includes a hyphen
|
||||||
"numéro", // French for "number", includes an accent
|
// "numéro", // French for "number", includes an accent
|
||||||
"år", // Swedish for "year", includes a ring above
|
// "år", // Swedish for "year", includes a ring above
|
||||||
"naïve", // English word borrowed from French, includes an umlaut
|
// "naïve", // English word borrowed from French, includes an umlaut
|
||||||
"الاسم", // Arabic for "name"
|
// "الاسم", // Arabic for "name"
|
||||||
"оплата", // Russian for "payment"
|
// "оплата", // Russian for "payment"
|
||||||
"पता", // Hindi for "address"
|
// "पता", // Hindi for "address"
|
||||||
"用戶名", // Chinese for "username"
|
// "用戶名", // Chinese for "username"
|
||||||
"çalışma_zamanı", // Turkish for "runtime", includes an underscore and a cedilla
|
// "çalışma_zamanı", // Turkish for "runtime", includes an underscore and a cedilla
|
||||||
"preço", // Portuguese for "price", includes a cedilla
|
// "preço", // Portuguese for "price", includes a cedilla
|
||||||
"사용자명", // Korean for "username"
|
// "사용자명", // Korean for "username"
|
||||||
"usuario_ñoño", // Spanish, uses an underscore and includes "ñ"
|
// "usuario_ñoño", // Spanish, uses an underscore and includes "ñ"
|
||||||
"файл", // Bulgarian for "file"
|
// "файл", // Bulgarian for "file"
|
||||||
"δεδομένα", // Greek for "data"
|
// "δεδομένα", // Greek for "data"
|
||||||
"geändert_am", // German for "modified on", includes an umlaut
|
// "geändert_am", // German for "modified on", includes an umlaut
|
||||||
"ব্যবহারকারীর_নাম", // Bengali for "user name", includes an underscore
|
// "ব্যবহারকারীর_নাম", // Bengali for "user name", includes an underscore
|
||||||
"São_Paulo", // Portuguese, includes an underscore and a tilde
|
// "São_Paulo", // Portuguese, includes an underscore and a tilde
|
||||||
"età", // Italian for "age", includes an accent
|
// "età", // Italian for "age", includes an accent
|
||||||
"ชื่อผู้ใช้", // Thai for "username"
|
// "ชื่อผู้ใช้", // Thai for "username"
|
||||||
])("non-ascii column name: %s", name => {
|
])("non-ascii column name: %s", name => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
table = await createTable({
|
table = await createTable({
|
||||||
|
|
|
@ -360,11 +360,20 @@ class OracleIntegration extends Sql implements DatasourcePlus {
|
||||||
this.index = 1
|
this.index = 1
|
||||||
connection = await this.getConnection()
|
connection = await this.getConnection()
|
||||||
|
|
||||||
const options: ExecuteOptions = { autoCommit: true }
|
const options: ExecuteOptions = {
|
||||||
|
autoCommit: true,
|
||||||
|
fetchTypeHandler: function (metaData) {
|
||||||
|
if (metaData.dbType === oracledb.CLOB) {
|
||||||
|
return { type: oracledb.STRING }
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
},
|
||||||
|
}
|
||||||
const bindings: BindParameters = query.bindings || []
|
const bindings: BindParameters = query.bindings || []
|
||||||
|
|
||||||
this.log(query.sql, bindings)
|
this.log(query.sql, bindings)
|
||||||
return await connection.execute<T>(query.sql, bindings, options)
|
const result = await connection.execute(query.sql, bindings, options)
|
||||||
|
return result as Result<T>
|
||||||
} finally {
|
} finally {
|
||||||
if (connection) {
|
if (connection) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -8,7 +8,7 @@ let ports: Promise<testContainerUtils.Port[]>
|
||||||
|
|
||||||
export async function getDatasource(): Promise<Datasource> {
|
export async function getDatasource(): Promise<Datasource> {
|
||||||
if (!ports) {
|
if (!ports) {
|
||||||
let image = "oracle/database:19.3.0.0-ee"
|
let image = "oracle/database:19.3.0.0-ee-slim-faststart"
|
||||||
if (process.arch.startsWith("arm")) {
|
if (process.arch.startsWith("arm")) {
|
||||||
image = "samhuang78/oracle-database:19.3.0-ee-slim-faststart"
|
image = "samhuang78/oracle-database:19.3.0-ee-slim-faststart"
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ export async function getDatasource(): Promise<Datasource> {
|
||||||
new GenericContainer(image)
|
new GenericContainer(image)
|
||||||
.withExposedPorts(1521)
|
.withExposedPorts(1521)
|
||||||
.withEnvironment({ ORACLE_PASSWORD: "password" })
|
.withEnvironment({ ORACLE_PASSWORD: "password" })
|
||||||
.withWaitStrategy(Wait.forHealthCheck().withStartupTimeout(10000))
|
.withWaitStrategy(Wait.forHealthCheck().withStartupTimeout(60000))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,23 +26,25 @@ export async function getDatasource(): Promise<Datasource> {
|
||||||
throw new Error("Oracle port not found")
|
throw new Error("Oracle port not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const host = "127.0.0.1"
|
||||||
|
const user = "SYSTEM"
|
||||||
|
const password = "password"
|
||||||
|
|
||||||
const datasource: Datasource = {
|
const datasource: Datasource = {
|
||||||
type: "datasource_plus",
|
type: "datasource_plus",
|
||||||
source: SourceName.ORACLE,
|
source: SourceName.ORACLE,
|
||||||
plus: true,
|
plus: true,
|
||||||
config: {
|
config: { host, port, user, password, database: "FREEPDB1" },
|
||||||
host: "127.0.0.1",
|
|
||||||
port,
|
|
||||||
database: "postgres",
|
|
||||||
user: "SYS",
|
|
||||||
password: "password",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const database = generator.guid().replaceAll("-", "")
|
const newUser = "a" + generator.guid().replaceAll("-", "")
|
||||||
const client = await knexClient(datasource)
|
const client = await knexClient(datasource)
|
||||||
await client.raw(`CREATE DATABASE "${database}"`)
|
await client.raw(`CREATE USER ${newUser} IDENTIFIED BY password`)
|
||||||
datasource.config!.database = database
|
await client.raw(
|
||||||
|
`GRANT CONNECT, RESOURCE, CREATE VIEW, CREATE SESSION TO ${newUser}`
|
||||||
|
)
|
||||||
|
await client.raw(`GRANT UNLIMITED TABLESPACE TO ${newUser}`)
|
||||||
|
datasource.config!.user = newUser
|
||||||
|
|
||||||
return datasource
|
return datasource
|
||||||
}
|
}
|
||||||
|
@ -55,8 +57,17 @@ export async function knexClient(ds: Datasource) {
|
||||||
throw new Error("Datasource source is not Oracle")
|
throw new Error("Datasource source is not Oracle")
|
||||||
}
|
}
|
||||||
|
|
||||||
return knex({
|
const db = ds.config.database || "FREEPDB1"
|
||||||
|
const connectString = `${ds.config.host}:${ds.config.port}/${db}`
|
||||||
|
|
||||||
|
const c = knex({
|
||||||
client: "oracledb",
|
client: "oracledb",
|
||||||
connection: ds.config,
|
connection: {
|
||||||
|
connectString,
|
||||||
|
user: ds.config.user,
|
||||||
|
password: ds.config.password,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
|
@ -315,6 +315,13 @@ export async function outputProcessing<T extends Row[] | Row>(
|
||||||
column.subtype
|
column.subtype
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else if (column.type === FieldType.BIGINT) {
|
||||||
|
for (const row of enriched) {
|
||||||
|
if (row[property] == null) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
row[property] = row[property].toString()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue