forked from tsai/budibase
committed by
GitHub
24 changed files with 1931 additions and 45 deletions
|
After Width: | Height: | Size: 8.3 KiB |
@ -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> |
||||
File diff suppressed because it is too large
@ -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 |
||||
|
} |
||||
@ -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: |
||||
Binary file not shown.
@ -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" |
||||
@ -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 |
||||
|
|
||||
@ -1,6 +1,6 @@ |
|||||
export interface IntegrationBase { |
export interface IntegrationBase { |
||||
create?(query: any): Promise<[any]> |
create?(query: any): Promise<any[]> |
||||
read?(query: any): Promise<[any]> |
read?(query: any): Promise<any[]> |
||||
update?(query: any): Promise<[any]> |
update?(query: any): Promise<any[]> |
||||
delete?(query: any): Promise<[any]> |
delete?(query: any): Promise<any[]> |
||||
} |
} |
||||
|
|||||
@ -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, |
||||
|
} |
||||
|
} |
||||
@ -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) |
||||
|
}) |
||||
|
}) |
||||
|
}) |
||||
Loading…
Reference in new issue