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 { |
|||
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[]> |
|||
} |
|||
|
|||
@ -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