1
0
Fork 0
mirror of synced 2024-06-29 03:20:34 +12:00

Merge pull request #3301 from Budibase/oracle-datasource

Oracle datasource
This commit is contained in:
Rory Powell 2021-11-23 13:42:50 +00:00 committed by GitHub
commit ee726f9c06
24 changed files with 1931 additions and 45 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View file

@ -0,0 +1,16 @@
<script>
export let width = "18"
export let height = "18"
import OracleLogo from "assets/oracle.png"
</script>
<div class>
<img {height} {width} src={OracleLogo} alt="oracle logo" />
</div>
<style>
img {
padding-top: 1px;
}
</style>

View file

@ -10,6 +10,7 @@ import MySQL from "./MySQL.svelte"
import ArangoDB from "./ArangoDB.svelte"
import Rest from "./Rest.svelte"
import Budibase from "./Budibase.svelte"
import Oracle from "./Oracle.svelte"
export default {
BUDIBASE: Budibase,
@ -24,4 +25,5 @@ export default {
MYSQL: MySQL,
ARANGODB: ArangoDB,
REST: Rest,
ORACLE: Oracle,
}

View file

@ -27,6 +27,7 @@ export const IntegrationNames = {
SQL_SERVER: "SQL Server",
AIRTABLE: "Airtable",
ARANGODB: "ArangoDB",
ORACLE: "Oracle",
}
// fields on the user table that cannot be edited

View file

@ -920,7 +920,7 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/bbui@^0.9.139", "@budibase/bbui@^0.9.185-alpha.9", "@budibase/bbui@^0.9.187":
"@budibase/bbui@^0.9.139":
version "0.9.187"
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.187.tgz#84f0a37301cfa41f50eaa335243ac08923d9e34f"
integrity sha512-Qy24x99NloRAoG78NMdzoJuX3Gbf+eZdHeYTAeUchljB4o2W2L/Ous8qYBzqigYtVcChjzteSTAZ2jCLq458Vg==
@ -969,14 +969,63 @@
svelte-flatpickr "^3.2.3"
svelte-portal "^1.0.0"
"@budibase/client@^0.9.185-alpha.9":
version "0.9.187"
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.9.187.tgz#8ab8a8edc81e65dc5bac15bcbc5058666d0a2c64"
integrity sha512-eSNikZWkYxqy0d0zGBY6m8lDlVMNDhr7DFlqyFZOha03Abu1lS+YGKRJUb0TSkx7y4Qmb4hmvU0wj074ToxPJQ==
"@budibase/bbui@^0.9.185-alpha.12", "@budibase/bbui@^0.9.188":
version "0.9.188"
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.188.tgz#82c108172fbf81a84378e0ef4ca7cba61ea8d0ba"
integrity sha512-KevJxHdASITX9RzLvm+b2K3VMwqYFTumvrlpStAP6UIoyPkls0xaAc2KiJJ7Kkq48UkkBtAbOYaMxsFbAaTsbQ==
dependencies:
"@budibase/bbui" "^0.9.187"
"@adobe/spectrum-css-workflow-icons" "^1.2.1"
"@spectrum-css/actionbutton" "^1.0.1"
"@spectrum-css/actiongroup" "^1.0.1"
"@spectrum-css/avatar" "^3.0.2"
"@spectrum-css/button" "^3.0.1"
"@spectrum-css/buttongroup" "^3.0.2"
"@spectrum-css/checkbox" "^3.0.2"
"@spectrum-css/dialog" "^3.0.1"
"@spectrum-css/divider" "^1.0.3"
"@spectrum-css/dropzone" "^3.0.2"
"@spectrum-css/fieldgroup" "^3.0.2"
"@spectrum-css/fieldlabel" "^3.0.1"
"@spectrum-css/icon" "^3.0.1"
"@spectrum-css/illustratedmessage" "^3.0.2"
"@spectrum-css/inputgroup" "^3.0.2"
"@spectrum-css/label" "^2.0.10"
"@spectrum-css/link" "^3.1.1"
"@spectrum-css/menu" "^3.0.1"
"@spectrum-css/modal" "^3.0.1"
"@spectrum-css/pagination" "^3.0.3"
"@spectrum-css/picker" "^1.0.1"
"@spectrum-css/popover" "^3.0.1"
"@spectrum-css/progressbar" "^1.0.2"
"@spectrum-css/progresscircle" "^1.0.2"
"@spectrum-css/radio" "^3.0.2"
"@spectrum-css/search" "^3.0.2"
"@spectrum-css/sidenav" "^3.0.2"
"@spectrum-css/statuslight" "^3.0.2"
"@spectrum-css/stepper" "^3.0.3"
"@spectrum-css/switch" "^1.0.2"
"@spectrum-css/table" "^3.0.1"
"@spectrum-css/tabs" "^3.0.1"
"@spectrum-css/tags" "^3.0.2"
"@spectrum-css/textfield" "^3.0.1"
"@spectrum-css/toast" "^3.0.1"
"@spectrum-css/tooltip" "^3.0.3"
"@spectrum-css/treeview" "^3.0.2"
"@spectrum-css/typography" "^3.0.1"
"@spectrum-css/underlay" "^2.0.9"
"@spectrum-css/vars" "^3.0.1"
dayjs "^1.10.4"
svelte-flatpickr "^3.2.3"
svelte-portal "^1.0.0"
"@budibase/client@^0.9.185-alpha.12":
version "0.9.188"
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.9.188.tgz#c5bf6f3bccdb370b236b9e69e0118334ad3eccfd"
integrity sha512-yP2WLWb2yQAwPBxVzpNGjSHpATMZMzcxl2gK6vw662F7YC8xGFHNfZZqEwPvrVwnx+d7LFZR/kJxJOvvk7YCVw==
dependencies:
"@budibase/bbui" "^0.9.188"
"@budibase/standard-components" "^0.9.139"
"@budibase/string-templates" "^0.9.187"
"@budibase/string-templates" "^0.9.188"
regexparam "^1.3.0"
shortid "^2.2.15"
svelte-spa-router "^3.0.5"
@ -1031,10 +1080,10 @@
svelte-apexcharts "^1.0.2"
svelte-flatpickr "^3.1.0"
"@budibase/string-templates@^0.9.185-alpha.9", "@budibase/string-templates@^0.9.187":
version "0.9.187"
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.187.tgz#e6c7b3b3e9c93014731b0b4e01f482fad6d2b12f"
integrity sha512-230yCwmKv6gG0Bi5xUteAKJN7BgYpwepJFEqmF8TrxDaV+qJO55H9br918FbTQ9W9g9Vv0HKU0xNdSfLTrlqhQ==
"@budibase/string-templates@^0.9.185-alpha.12", "@budibase/string-templates@^0.9.188":
version "0.9.188"
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.188.tgz#f37836ed23dbd2217cb157030ada7cd59f6a2165"
integrity sha512-O/bL0I5OJO9W2OizIe9vBHowCLwwASPBrsGiAIB8L0x6AivYMq8j1mvNRwLXZjpHTjv86bU/LyG/3CP837oDsg==
dependencies:
"@budibase/handlebars-helpers" "^0.11.7"
dayjs "^1.10.4"

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,6 @@
FROM node:14-alpine
FROM node:14-slim
RUN apt-get update
LABEL com.centurylinklabs.watchtower.lifecycle.pre-check="scripts/watchtower-hooks/pre-check.sh"
LABEL com.centurylinklabs.watchtower.lifecycle.pre-update="scripts/watchtower-hooks/pre-update.sh"
@ -16,6 +18,10 @@ COPY . ./
RUN yarn
RUN yarn build
# Install client for oracle datasource
RUN apt-get install unzip libaio1
RUN /bin/bash -e scripts/integrations/oracle/instantclient/linux/x86-64/install.sh
EXPOSE 4001
# have to add node environment production after install

View file

@ -0,0 +1,31 @@
module OracleDbMock {
// mock execute
const execute = jest.fn(() => ({
rows: [
{
a: "string",
b: 1,
},
],
}))
const close = jest.fn()
// mock connection
function Connection() {}
Connection.prototype.execute = execute
Connection.prototype.close = close
// mock oracledb
const oracleDb: any = {}
oracleDb.getConnection = jest.fn(() => {
// @ts-ignore
return new Connection()
})
// expose mocks
oracleDb.executeMock = execute
oracleDb.closeMock = close
module.exports = oracleDb
}

View file

@ -135,6 +135,7 @@
"@types/koa": "^2.13.3",
"@types/koa-router": "^7.4.2",
"@types/node": "^15.12.4",
"@types/oracledb": "^5.2.1",
"@typescript-eslint/parser": "4.28.0",
"babel-jest": "^27.0.2",
"copyfiles": "^2.4.1",
@ -150,5 +151,8 @@
"typescript": "^4.3.5",
"update-dotenv": "^1.1.1"
},
"optionalDependencies": {
"oracledb": "^5.3.0"
},
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"
}
}

View file

@ -0,0 +1,19 @@
# For more information see:
# https://container-registry.oracle.com/
# - Database > Express
version: "3.8"
services:
db:
container_name: oracle-xe
platform: linux/x86_64
image: container-registry.oracle.com/database/express:18.4.0-xe
environment:
ORACLE_PWD: oracle
ports:
- 1521:1521
- 5500:5500
volumes:
- oracle_data:/opt/oracle/oradata
volumes:
oracle_data:

View file

@ -0,0 +1,23 @@
#!/bin/bash
# Must be root to continue
if [[ $(id -u) -ne 0 ]] ; then echo "Please run as root" ; exit 1 ; fi
# Allow for re-runs
rm -rf /opt/oracle
echo "Installing oracle instant client"
# copy and unzip package
mkdir -p /opt/oracle
cp scripts/integrations/oracle/instantclient/linux/x86-64/basiclite-21.4.zip /opt/oracle
cd /opt/oracle
unzip -qq basiclite-21.4.zip -d .
rm *.zip
mv instantclient* instantclient
# update runtime link path
sh -c "echo /opt/oracle/instantclient > /etc/ld.so.conf.d/oracle-instantclient.conf"
ldconfig /etc/ld.so.conf.d
echo "Installation complete"

View file

@ -0,0 +1,90 @@
# Installation
## Database
**Important**
- Oracle database is supported only on **x86-64 architecture**
- Oracle database is **not supported on Mac ARM architecture** (either via docker or linux virtualization)
To install oracle express edition simply run `docker-compose up`
- A single instance pluggable database (PDB) will be created named `xepdb`
- The default password is configured in the compose file as `oracle`
- The `system`, `sys` and `pdbadmin` users all share this password
## Instant Client
Before oracle can be connected to from nodejs, the oracle client must be installed.
For more information see https://www.oracle.com/database/technologies/instant-client/downloads.html
**Important**
- Oracle client is supported only on **x86-64 architecture**
- Oracle client is **not supported on Mac ARM architecture**
### Linux
Run the provided install script for linux from the `server` root path:
```bash
sudo /bin/bash -e scripts/integrations/oracle/instantclient/linux/x86-64/install.sh
```
For more information see: https://www.oracle.com/database/technologies/instant-client/linux-x86-64-downloads.html#ic_x64_inst
### Mac
**This has not yet been tested**
See: https://www.oracle.com/database/technologies/instant-client/macos-intel-x86-downloads.html#ic_osx_inst
# Management
To connect to oracle sql command line:
```bash
docker exec -it oracle-xe sqlplus -l system/oracle@localhost/xepdb1
```
To create a new schema (where a user is the same as a schema in oracle) named `sales`:
```sql
define USERNAME = sales
create user &USERNAME;
alter user &USERNAME
default tablespace users
temporary tablespace temp
quota unlimited on users;
grant create session,
create view,
create sequence,
create procedure,
create table,
create trigger,
create type,
create materialized view
to &USERNAME;
```
To set the password for the sales schema use:
```sql
define USERNAME = sales
define PASSWORD = sales
alter user &USERNAME identified by &PASSWORD;
```
As before the database schema can now be connected to using:
```bash
docker exec -it oracle-xe sqlplus -l sales/sales@localhost:1521/xepdb1
```
## HR Schema
The `HR` schema is populated with dummy data by default in oracle for testing purposes.
To connect to the HR schema first update the user password and unlock the account by performing
```sql
ALTER USER hr ACCOUNT UNLOCK;
ALTER USER hr IDENTIFIED BY hr
```
You should now be able to connect to the hr schema using the credentials hr/hr

View file

@ -11,7 +11,7 @@ services:
ports:
- "5432:5432"
volumes:
#- pg_data:/var/lib/postgresql/data/
- pg_data:/var/lib/postgresql/data/
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
pgadmin:
@ -24,5 +24,5 @@ services:
ports:
- "5050:80"
#volumes:
# pg_data:
volumes:
pg_data:

View file

@ -46,6 +46,7 @@ export enum SourceNames {
MYSQL = "MYSQL",
ARANGODB = "ARANGODB",
REST = "REST",
ORACLE = "ORACLE",
}
export enum IncludeRelationships {

View file

@ -1,6 +1,6 @@
export interface IntegrationBase {
create?(query: any): Promise<[any]>
read?(query: any): Promise<[any]>
update?(query: any): Promise<[any]>
delete?(query: any): Promise<[any]>
create?(query: any): Promise<any[]>
read?(query: any): Promise<any[]>
update?(query: any): Promise<any[]>
delete?(query: any): Promise<any[]>
}

View file

@ -416,6 +416,8 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
id = results?.[0].id
} else if (sqlClient === SqlClients.MY_SQL) {
id = results?.insertId
} else if (sqlClient === SqlClients.ORACLE) {
id = response.outBinds[0][0]
}
row = processFn(
await this.getReturningRow(queryFn, this.checkLookupKeys(id, json))
@ -428,4 +430,5 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
}
}
export default SqlQueryBuilder
module.exports = SqlQueryBuilder

View file

@ -39,6 +39,13 @@ const INTEGRATIONS = {
[SourceNames.REST]: rest.integration,
}
// optionally add oracle integration if the oracle binary can be installed
if (!(process.arch === 'arm64' && process.platform === 'darwin')) {
const oracle = require("./oracle")
DEFINITIONS[SourceNames.ORACLE] = oracle.schema
INTEGRATIONS[SourceNames.ORACLE] = oracle.integration
}
module.exports = {
definitions: DEFINITIONS,
integrations: INTEGRATIONS,

View file

@ -0,0 +1,445 @@
import {
Integration,
DatasourceFieldTypes,
QueryTypes,
SqlQuery,
QueryJson,
Operation,
} from "../definitions/datasource"
import {
finaliseExternalTables,
getSqlQuery,
buildExternalTableId,
convertSqlType,
SqlClients,
} from "./utils"
import oracledb, {
ExecuteOptions,
Result,
Connection,
ConnectionAttributes,
BindParameters,
} from "oracledb"
import Sql from "./base/sql"
import { Table } from "../definitions/common"
import { DatasourcePlus } from "./base/datasourcePlus"
import { FieldTypes } from "../constants"
module OracleModule {
oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT
interface OracleConfig {
host: string
port: number
database: string
user: string
password: string
}
const SCHEMA: Integration = {
docs: "https://github.com/oracle/node-oracledb",
plus: true,
friendlyName: "Oracle",
description:
"Oracle Database is an object-relational database management system developed by Oracle Corporation",
datasource: {
host: {
type: DatasourceFieldTypes.STRING,
default: "localhost",
required: true,
},
port: {
type: DatasourceFieldTypes.NUMBER,
required: true,
default: 1521,
},
database: {
type: DatasourceFieldTypes.STRING,
required: true,
},
user: {
type: DatasourceFieldTypes.STRING,
required: true,
},
password: {
type: DatasourceFieldTypes.PASSWORD,
required: true,
},
},
query: {
create: {
type: QueryTypes.SQL,
},
read: {
type: QueryTypes.SQL,
},
update: {
type: QueryTypes.SQL,
},
delete: {
type: QueryTypes.SQL,
},
},
}
const UNSUPPORTED_TYPES = ["BLOB", "CLOB", "NCLOB"]
/**
* Raw query response
*/
interface ColumnsResponse {
TABLE_NAME: string
COLUMN_NAME: string
DATA_TYPE: string
DATA_DEFAULT: string | null
COLUMN_ID: number
CONSTRAINT_NAME: string | null
CONSTRAINT_TYPE: string | null
R_CONSTRAINT_NAME: string | null
SEARCH_CONDITION: string | null
}
/**
* An oracle constraint
*/
interface OracleConstraint {
name: string
type: string
relatedConstraintName: string | null
searchCondition: string | null
}
/**
* An oracle column and it's related constraints
*/
interface OracleColumn {
name: string
type: string
default: string | null
id: number
constraints: { [key: string]: OracleConstraint }
}
/**
* An oracle table and it's related columns
*/
interface OracleTable {
name: string
columns: { [key: string]: OracleColumn }
}
const OracleContraintTypes = {
PRIMARY: "P",
NOT_NULL_OR_CHECK: "C",
FOREIGN_KEY: "R",
UNIQUE: "U",
}
class OracleIntegration extends Sql implements DatasourcePlus {
private readonly config: OracleConfig
public tables: Record<string, Table> = {}
public schemaErrors: Record<string, string> = {}
private readonly COLUMNS_SQL = `
SELECT
tabs.table_name,
cols.column_name,
cols.data_type,
cols.data_default,
cols.column_id,
cons.constraint_name,
cons.constraint_type,
cons.r_constraint_name,
cons.search_condition
FROM
user_tables tabs
JOIN
user_tab_columns cols
ON tabs.table_name = cols.table_name
LEFT JOIN
user_cons_columns col_cons
ON cols.column_name = col_cons.column_name
AND cols.table_name = col_cons.table_name
LEFT JOIN
user_constraints cons
ON col_cons.constraint_name = cons.constraint_name
AND cons.table_name = cols.table_name
WHERE
(cons.status = 'ENABLED'
OR cons.status IS NULL)
`
constructor(config: OracleConfig) {
super(SqlClients.ORACLE)
this.config = config
}
/**
* Map the flat tabular columns and constraints data into a nested object
*/
private mapColumns(result: Result<ColumnsResponse>): {
[key: string]: OracleTable
} {
const oracleTables: { [key: string]: OracleTable } = {}
if (result.rows) {
result.rows.forEach(row => {
const tableName = row.TABLE_NAME
const columnName = row.COLUMN_NAME
const dataType = row.DATA_TYPE
const dataDefault = row.DATA_DEFAULT
const columnId = row.COLUMN_ID
const constraintName = row.CONSTRAINT_NAME
const constraintType = row.CONSTRAINT_TYPE
const relatedConstraintName = row.R_CONSTRAINT_NAME
const searchCondition = row.SEARCH_CONDITION
let table = oracleTables[tableName]
if (!table) {
table = {
name: tableName,
columns: {},
}
oracleTables[tableName] = table
}
let column = table.columns[columnName]
if (!column) {
column = {
name: columnName,
type: dataType,
default: dataDefault,
id: columnId,
constraints: {},
}
table.columns[columnName] = column
}
if (constraintName && constraintType) {
let constraint = column.constraints[constraintName]
if (!constraint) {
constraint = {
name: constraintName,
type: constraintType,
relatedConstraintName: relatedConstraintName,
searchCondition: searchCondition,
}
}
column.constraints[constraintName] = constraint
}
})
}
return oracleTables
}
private isSupportedColumn(column: OracleColumn) {
if (UNSUPPORTED_TYPES.includes(column.type)) {
return false
}
return true
}
private isAutoColumn(column: OracleColumn) {
if (column.default && column.default.toLowerCase().includes("nextval")) {
return true
}
return false
}
/**
* No native boolean in oracle. Best we can do is to check if a manual 1 or 0 number constraint has been set up
* This matches the default behaviour for generating DDL used in knex.
*/
private isBooleanType(column: OracleColumn): boolean {
if (
column.type.toLowerCase() === "number" &&
Object.values(column.constraints).filter(c => {
if (
c.type === OracleContraintTypes.NOT_NULL_OR_CHECK &&
c.searchCondition
) {
const condition = c.searchCondition
.replace(/\s/g, "") // remove spaces
.replace(/[']+/g, "") // remove quotes
if (
condition.includes("in(0,1)") ||
condition.includes("in(1,0)")
) {
return true
}
}
return false
}).length > 0
) {
return true
}
return false
}
private internalConvertType(column: OracleColumn): string {
if (this.isBooleanType(column)) {
return FieldTypes.BOOLEAN
}
return convertSqlType(column.type)
}
/**
* Fetches the tables from the oracle table and assigns them to the datasource.
* @param {*} datasourceId - datasourceId to fetch
* @param entities - the tables that are to be built
*/
async buildSchema(datasourceId: string, entities: Record<string, Table>) {
const columnsResponse = await this.internalQuery<ColumnsResponse>({
sql: this.COLUMNS_SQL,
})
const oracleTables = this.mapColumns(columnsResponse)
const tables: { [key: string]: Table } = {}
// iterate each table
Object.values(oracleTables).forEach(oracleTable => {
let table = tables[oracleTable.name]
if (!table) {
table = {
_id: buildExternalTableId(datasourceId, oracleTable.name),
primary: [],
name: oracleTable.name,
schema: {},
}
tables[oracleTable.name] = table
}
// iterate each column on the table
Object.values(oracleTable.columns)
// remove columns that we can't read / save
.filter(oracleColumn => this.isSupportedColumn(oracleColumn))
// match the order of the columns in the db
.sort((c1, c2) => c1.id - c2.id)
.forEach(oracleColumn => {
const columnName = oracleColumn.name
let fieldSchema = table.schema[columnName]
if (!fieldSchema) {
fieldSchema = {
autocolumn: this.isAutoColumn(oracleColumn),
name: columnName,
type: this.internalConvertType(oracleColumn),
}
table.schema[columnName] = fieldSchema
}
// iterate each constraint on the column
Object.values(oracleColumn.constraints).forEach(
oracleConstraint => {
if (oracleConstraint.type === OracleContraintTypes.PRIMARY) {
table.primary!.push(columnName)
}
}
)
})
})
const final = finaliseExternalTables(tables, entities)
this.tables = final.tables
this.schemaErrors = final.errors
}
/**
* Knex default returning behaviour does not work with oracle
* Manually add the behaviour for the return column
*/
private addReturning(query: SqlQuery, bindings: BindParameters, returnColumn: string) {
if (bindings instanceof Array) {
bindings.push({ dir: oracledb.BIND_OUT })
query.sql = query.sql + ` returning \"${returnColumn}\" into :${bindings.length}`
}
}
private async internalQuery<T>(query: SqlQuery, returnColum?: string, operation?: string): Promise<Result<T>> {
let connection
try {
connection = await this.getConnection()
const options: ExecuteOptions = { autoCommit: true }
const bindings: BindParameters = query.bindings || []
if (returnColum && (operation === Operation.CREATE || operation === Operation.UPDATE)) {
this.addReturning(query, bindings, returnColum)
}
const result: Result<T> = await connection.execute<T>(
query.sql,
bindings,
options
)
return result
} finally {
if (connection) {
try {
await connection.close()
} catch (err) {
console.error(err)
}
}
}
}
private getConnection = async (): Promise<Connection> => {
//connectString : "(DESCRIPTION =(ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521))(CONNECT_DATA =(SID= ORCL)))"
const connectString = `${this.config.host}:${this.config.port || 1521}/${
this.config.database
}`
const attributes: ConnectionAttributes = {
user: this.config.user,
password: this.config.user,
connectString,
}
return oracledb.getConnection(attributes)
}
async create(query: SqlQuery | string): Promise<any[]> {
const response = await this.internalQuery<any>(getSqlQuery(query))
return response.rows && response.rows.length
? response.rows
: [{ created: true }]
}
async read(query: SqlQuery | string): Promise<any[]> {
const response = await this.internalQuery<any>(getSqlQuery(query))
return response.rows ? response.rows : []
}
async update(query: SqlQuery | string): Promise<any[]> {
const response = await this.internalQuery(getSqlQuery(query))
return response.rows && response.rows.length
? response.rows
: [{ updated: true }]
}
async delete(query: SqlQuery | string): Promise<any[]> {
const response = await this.internalQuery(getSqlQuery(query))
return response.rows && response.rows.length
? response.rows
: [{ deleted: true }]
}
async query(json: QueryJson) {
const primaryKeys = json.meta!.table!.primary
const primaryKey = primaryKeys ? primaryKeys[0] : undefined
const queryFn = (query: any, operation: string) => this.internalQuery(query, primaryKey, operation)
const processFn = (response: any) => response.rows ? response.rows : []
const output = await this.queryWithReturning(json, queryFn, processFn)
return output
}
}
module.exports = {
schema: SCHEMA,
integration: OracleIntegration,
}
}

View file

@ -0,0 +1,94 @@
const oracledb = require("oracledb")
const OracleIntegration = require("../oracle")
jest.mock("oracledb")
class TestConfiguration {
constructor(config = {}) {
this.integration = new OracleIntegration.integration(config)
}
}
const options = { autoCommit: true }
describe("Oracle Integration", () => {
let config
beforeEach(() => {
jest.clearAllMocks()
config = new TestConfiguration()
})
afterEach(() => {
expect(oracledb.closeMock).toHaveBeenCalled()
expect(oracledb.closeMock).toHaveBeenCalledTimes(1)
})
it("calls the create method with the correct params", async () => {
const sql = "insert into users (name, age) values ('Joe', 123);"
await config.integration.create({
sql
})
expect(oracledb.executeMock).toHaveBeenCalledWith(sql, [], options)
expect(oracledb.executeMock).toHaveBeenCalledTimes(1)
})
it("calls the read method with the correct params", async () => {
const sql = "select * from users;"
await config.integration.read({
sql
})
expect(oracledb.executeMock).toHaveBeenCalledWith(sql, [], options)
expect(oracledb.executeMock).toHaveBeenCalledTimes(1)
})
it("calls the update method with the correct params", async () => {
const sql = "update table users set name = 'test';"
const response = await config.integration.update({
sql
})
expect(oracledb.executeMock).toHaveBeenCalledWith(sql, [], options)
expect(oracledb.executeMock).toHaveBeenCalledTimes(1)
})
it("calls the delete method with the correct params", async () => {
const sql = "delete from users where name = 'todelete';"
await config.integration.delete({
sql
})
expect(oracledb.executeMock).toHaveBeenCalledWith(sql, [], options)
expect(oracledb.executeMock).toHaveBeenCalledTimes(1)
})
describe("no rows returned", () => {
beforeEach(() => {
oracledb.executeMock.mockImplementation(() => ({ rows: [] }))
})
it("returns the correct response when the create response has no rows", async () => {
const sql = "insert into users (name, age) values ('Joe', 123);"
const response = await config.integration.create({
sql
})
expect(response).toEqual([{ created: true }])
expect(oracledb.executeMock).toHaveBeenCalledTimes(1)
})
it("returns the correct response when the update response has no rows", async () => {
const sql = "update table users set name = 'test';"
const response = await config.integration.update({
sql
})
expect(response).toEqual([{ updated: true }])
expect(oracledb.executeMock).toHaveBeenCalledTimes(1)
})
it("returns the correct response when the delete response has no rows", async () => {
const sql = "delete from users where name = 'todelete';"
const response = await config.integration.delete({
sql
})
expect(response).toEqual([{ deleted: true }])
expect(oracledb.executeMock).toHaveBeenCalledTimes(1)
})
})
})

View file

@ -32,12 +32,17 @@ const SQL_TYPE_MAP = {
fixed: FieldTypes.NUMBER,
datetime: FieldTypes.DATETIME,
tinyint: FieldTypes.BOOLEAN,
long: FieldTypes.LONGFORM,
number: FieldTypes.NUMBER,
binary_float: FieldTypes.NUMBER,
binary_double: FieldTypes.NUMBER,
}
export enum SqlClients {
MS_SQL = "mssql",
POSTGRES = "pg",
MY_SQL = "mysql",
ORACLE = "oracledb"
}
export function isExternalTable(tableId: string) {
@ -162,7 +167,7 @@ export function finaliseExternalTables(
entities: { [key: string]: any }
) {
const invalidColumns = Object.values(InvalidColumns)
const finalTables: { [key: string]: any } = {}
let finalTables: { [key: string]: any } = {}
const errors: { [key: string]: string } = {}
for (let [name, table] of Object.entries(tables)) {
const schemaFields = Object.keys(table.schema)
@ -177,5 +182,9 @@ export function finaliseExternalTables(
// make sure all previous props have been added back
finalTables[name] = copyExistingPropsOver(name, table, entities)
}
// sort the tables by name
finalTables = Object.entries(finalTables)
.sort(([a], [b]) => a.localeCompare(b))
.reduce((r, [k, v]) => ({ ...r, [k]: v }), {})
return { tables: finalTables, errors }
}

View file

@ -951,10 +951,10 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/auth@^0.9.185-alpha.9":
version "0.9.187"
resolved "https://registry.yarnpkg.com/@budibase/auth/-/auth-0.9.187.tgz#f09d242a79d1f541d047e7779a7fb9fe4e0c13ef"
integrity sha512-UwKR4MSY061B9Mke7FBmb5+lVTZ/2xe2v+qPUtVLCe+LBCNFC+GUOLr3nc42eqfrMYdI4TChArIU0YJc1KLZpw==
"@budibase/auth@^0.9.185-alpha.12":
version "0.9.188"
resolved "https://registry.yarnpkg.com/@budibase/auth/-/auth-0.9.188.tgz#82df2fc6e6e3100679a19ced8bab6fe6625f8744"
integrity sha512-U/7MfxbSrsQGGFyTdAicPDafxGeR8zsuh0I17ZujcIiZUoi1eWbLz4/ODITlTj9zxiXojMf+/u68YAF1ABdxDw==
dependencies:
"@techpass/passport-openidconnect" "^0.3.0"
aws-sdk "^2.901.0"
@ -975,7 +975,7 @@
uuid "^8.3.2"
zlib "^1.0.5"
"@budibase/bbui@^0.9.139", "@budibase/bbui@^0.9.187":
"@budibase/bbui@^0.9.139":
version "0.9.187"
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.187.tgz#84f0a37301cfa41f50eaa335243ac08923d9e34f"
integrity sha512-Qy24x99NloRAoG78NMdzoJuX3Gbf+eZdHeYTAeUchljB4o2W2L/Ous8qYBzqigYtVcChjzteSTAZ2jCLq458Vg==
@ -1024,14 +1024,63 @@
svelte-flatpickr "^3.2.3"
svelte-portal "^1.0.0"
"@budibase/client@^0.9.185-alpha.9":
version "0.9.187"
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.9.187.tgz#8ab8a8edc81e65dc5bac15bcbc5058666d0a2c64"
integrity sha512-eSNikZWkYxqy0d0zGBY6m8lDlVMNDhr7DFlqyFZOha03Abu1lS+YGKRJUb0TSkx7y4Qmb4hmvU0wj074ToxPJQ==
"@budibase/bbui@^0.9.188":
version "0.9.188"
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.188.tgz#82c108172fbf81a84378e0ef4ca7cba61ea8d0ba"
integrity sha512-KevJxHdASITX9RzLvm+b2K3VMwqYFTumvrlpStAP6UIoyPkls0xaAc2KiJJ7Kkq48UkkBtAbOYaMxsFbAaTsbQ==
dependencies:
"@budibase/bbui" "^0.9.187"
"@adobe/spectrum-css-workflow-icons" "^1.2.1"
"@spectrum-css/actionbutton" "^1.0.1"
"@spectrum-css/actiongroup" "^1.0.1"
"@spectrum-css/avatar" "^3.0.2"
"@spectrum-css/button" "^3.0.1"
"@spectrum-css/buttongroup" "^3.0.2"
"@spectrum-css/checkbox" "^3.0.2"
"@spectrum-css/dialog" "^3.0.1"
"@spectrum-css/divider" "^1.0.3"
"@spectrum-css/dropzone" "^3.0.2"
"@spectrum-css/fieldgroup" "^3.0.2"
"@spectrum-css/fieldlabel" "^3.0.1"
"@spectrum-css/icon" "^3.0.1"
"@spectrum-css/illustratedmessage" "^3.0.2"
"@spectrum-css/inputgroup" "^3.0.2"
"@spectrum-css/label" "^2.0.10"
"@spectrum-css/link" "^3.1.1"
"@spectrum-css/menu" "^3.0.1"
"@spectrum-css/modal" "^3.0.1"
"@spectrum-css/pagination" "^3.0.3"
"@spectrum-css/picker" "^1.0.1"
"@spectrum-css/popover" "^3.0.1"
"@spectrum-css/progressbar" "^1.0.2"
"@spectrum-css/progresscircle" "^1.0.2"
"@spectrum-css/radio" "^3.0.2"
"@spectrum-css/search" "^3.0.2"
"@spectrum-css/sidenav" "^3.0.2"
"@spectrum-css/statuslight" "^3.0.2"
"@spectrum-css/stepper" "^3.0.3"
"@spectrum-css/switch" "^1.0.2"
"@spectrum-css/table" "^3.0.1"
"@spectrum-css/tabs" "^3.0.1"
"@spectrum-css/tags" "^3.0.2"
"@spectrum-css/textfield" "^3.0.1"
"@spectrum-css/toast" "^3.0.1"
"@spectrum-css/tooltip" "^3.0.3"
"@spectrum-css/treeview" "^3.0.2"
"@spectrum-css/typography" "^3.0.1"
"@spectrum-css/underlay" "^2.0.9"
"@spectrum-css/vars" "^3.0.1"
dayjs "^1.10.4"
svelte-flatpickr "^3.2.3"
svelte-portal "^1.0.0"
"@budibase/client@^0.9.185-alpha.12":
version "0.9.188"
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.9.188.tgz#c5bf6f3bccdb370b236b9e69e0118334ad3eccfd"
integrity sha512-yP2WLWb2yQAwPBxVzpNGjSHpATMZMzcxl2gK6vw662F7YC8xGFHNfZZqEwPvrVwnx+d7LFZR/kJxJOvvk7YCVw==
dependencies:
"@budibase/bbui" "^0.9.188"
"@budibase/standard-components" "^0.9.139"
"@budibase/string-templates" "^0.9.187"
"@budibase/string-templates" "^0.9.188"
regexparam "^1.3.0"
shortid "^2.2.15"
svelte-spa-router "^3.0.5"
@ -1081,10 +1130,10 @@
svelte-apexcharts "^1.0.2"
svelte-flatpickr "^3.1.0"
"@budibase/string-templates@^0.9.185-alpha.9", "@budibase/string-templates@^0.9.187":
version "0.9.187"
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.187.tgz#e6c7b3b3e9c93014731b0b4e01f482fad6d2b12f"
integrity sha512-230yCwmKv6gG0Bi5xUteAKJN7BgYpwepJFEqmF8TrxDaV+qJO55H9br918FbTQ9W9g9Vv0HKU0xNdSfLTrlqhQ==
"@budibase/string-templates@^0.9.185-alpha.12", "@budibase/string-templates@^0.9.188":
version "0.9.188"
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.188.tgz#f37836ed23dbd2217cb157030ada7cd59f6a2165"
integrity sha512-O/bL0I5OJO9W2OizIe9vBHowCLwwASPBrsGiAIB8L0x6AivYMq8j1mvNRwLXZjpHTjv86bU/LyG/3CP837oDsg==
dependencies:
"@budibase/handlebars-helpers" "^0.11.7"
dayjs "^1.10.4"
@ -2384,6 +2433,14 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-15.14.9.tgz#bc43c990c3c9be7281868bbc7b8fdd6e2b57adfa"
integrity sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==
"@types/oracledb@^5.2.1":
version "5.2.1"
resolved "https://registry.yarnpkg.com/@types/oracledb/-/oracledb-5.2.1.tgz#b0c64d1ab68f1be6dc153a310ce0e840b8f333df"
integrity sha512-xtN24H9bpGB11ZiswZulAKYJ9xcWrF5BOAGFemcfeZkLmw8qAzVm+TAWT20VVLst6kh9VNxinY239S8EKgRBbA==
dependencies:
"@types/node" "*"
dotenv "^8.2.0"
"@types/prettier@^2.1.5":
version "2.4.2"
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.2.tgz#4c62fae93eb479660c3bd93f9d24d561597a8281"
@ -2919,7 +2976,7 @@ autolinker@~0.28.0:
dependencies:
gulp-header "^1.7.1"
aws-sdk@^2.767.0, aws-sdk@^2.901.0:
aws-sdk@^2.767.0:
version "2.1030.0"
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1030.0.tgz#24a856af3d2b8b37c14a8f59974993661c66fd82"
integrity sha512-to0STOb8DsSGuSsUb/WCbg/UFnMGfIYavnJH5ZlRCHzvCFjTyR+vfE8ku+qIZvfFM4+5MNTQC/Oxfun2X/TuyA==
@ -2934,6 +2991,21 @@ aws-sdk@^2.767.0, aws-sdk@^2.901.0:
uuid "3.3.2"
xml2js "0.4.19"
aws-sdk@^2.901.0:
version "2.1033.0"
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1033.0.tgz#8bd0084142b15cc2ca427483c36c86b27abc046b"
integrity sha512-cgcjiuR82bhfBWTffqt6e9+Cn/UgeC6QPQTrlJy3GxwPxChthyrt/h5pekj2l4PLFvETsG10Y6CqQysJEMsncw==
dependencies:
buffer "4.9.2"
events "1.1.1"
ieee754 "1.1.13"
jmespath "0.15.0"
querystring "0.2.0"
sax "1.2.1"
url "0.10.3"
uuid "3.3.2"
xml2js "0.4.19"
aws-sign2@~0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
@ -4311,6 +4383,11 @@ dotenv@8.2.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
dotenv@^8.2.0:
version "8.6.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b"
integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==
double-ended-queue@2.1.0-0:
version "2.1.0-0"
resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c"
@ -8766,6 +8843,11 @@ optionator@^0.8.1, optionator@^0.8.3:
type-check "~0.3.2"
word-wrap "~1.2.3"
oracledb@^5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/oracledb/-/oracledb-5.3.0.tgz#a15e6cd16757d8711a2c006a28bd7ecd3b8466f7"
integrity sha512-HMJzQ6lCf287ztvvehTEmjCWA21FQ3RMvM+mgoqd4i8pkREuqFWO+y3ovsGR9moJUg4T0xjcwS8rl4mggWPxmg==
os-locale@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a"

View file

@ -3746,7 +3746,7 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^3.0.0"
inherits "^2.0.1"
rollup-plugin-node-builtins@^4.3.2:
rollup-plugin-node-builtins@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/rollup-plugin-node-builtins/-/rollup-plugin-node-builtins-2.1.2.tgz#24a1fed4a43257b6b64371d8abc6ce1ab14597e9"
integrity sha1-JKH+1KQyV7a2Q3HYq8bOGrFFl+k=

View file

@ -295,7 +295,7 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/auth@^0.9.185-alpha.9":
"@budibase/auth@^0.9.185-alpha.12":
version "0.9.188"
resolved "https://registry.yarnpkg.com/@budibase/auth/-/auth-0.9.188.tgz#82df2fc6e6e3100679a19ced8bab6fe6625f8744"
integrity sha512-U/7MfxbSrsQGGFyTdAicPDafxGeR8zsuh0I17ZujcIiZUoi1eWbLz4/ODITlTj9zxiXojMf+/u68YAF1ABdxDw==
@ -346,7 +346,7 @@
to-gfm-code-block "^0.1.1"
year "^0.2.1"
"@budibase/string-templates@^0.9.185-alpha.9":
"@budibase/string-templates@^0.9.185-alpha.12":
version "0.9.188"
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.188.tgz#f37836ed23dbd2217cb157030ada7cd59f6a2165"
integrity sha512-O/bL0I5OJO9W2OizIe9vBHowCLwwASPBrsGiAIB8L0x6AivYMq8j1mvNRwLXZjpHTjv86bU/LyG/3CP837oDsg==