diff --git a/packages/server/package.json b/packages/server/package.json index 8551667e93..a18add2a8a 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -106,6 +106,7 @@ "mysql2": "^2.3.1", "node-fetch": "2.6.0", "open": "7.3.0", + "oracledb": "^5.3.0", "pg": "8.5.1", "pino-pretty": "4.0.0", "posthog-node": "^1.1.4", @@ -133,6 +134,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", diff --git a/packages/server/scripts/integrations/oracle-express/oracle.md b/packages/server/scripts/integrations/oracle-express/oracle.md new file mode 100644 index 0000000000..c412b358c2 --- /dev/null +++ b/packages/server/scripts/integrations/oracle-express/oracle.md @@ -0,0 +1,66 @@ +### Installation & Management + +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 + +To connect to oracle sql command line: + +```bash +docker exec -it oracle-xe sqlplus -l system/oracle@localhost/xepdb1 +``` + +To create a new schema (user = schema in oracle) + +```sql +define USERNAME = rpowell + +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 + +```sql +define USERNAME = rpowell +define PASSWORD = rpowell + +alter user &USERNAME identified by &PASSWORD; +``` + +As before the database schema can now be connected to +```bash +docker exec -it oracle-xe sqlplus -l rpowell/rpowell@localhost:1521/xepdb1 +``` + +### Oracle Instant Client +Before oracle can be connected to from nodejs, the oracle client must be installed. + + + +### 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 + diff --git a/packages/server/scripts/integrations/oracle-express/reset.sh b/packages/server/scripts/integrations/oracle-express/reset.sh deleted file mode 100755 index 32778bd11f..0000000000 --- a/packages/server/scripts/integrations/oracle-express/reset.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -docker-compose down -docker volume prune -f diff --git a/packages/server/scripts/integrations/postgres/docker-compose.yml b/packages/server/scripts/integrations/postgres/docker-compose.yml index 4dfcb0e1ad..2323025e5e 100644 --- a/packages/server/scripts/integrations/postgres/docker-compose.yml +++ b/packages/server/scripts/integrations/postgres/docker-compose.yml @@ -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: diff --git a/packages/server/src/definitions/datasource.ts b/packages/server/src/definitions/datasource.ts index a82e50b140..7316d3d2be 100644 --- a/packages/server/src/definitions/datasource.ts +++ b/packages/server/src/definitions/datasource.ts @@ -45,6 +45,7 @@ export enum SourceNames { MYSQL = "MYSQL", ARANGODB = "ARANGODB", REST = "REST", + ORACLE = "ORACLE", } export enum IncludeRelationships { diff --git a/packages/server/src/integrations/base/sql.ts b/packages/server/src/integrations/base/sql.ts index 738b44afcc..a51f57ee47 100644 --- a/packages/server/src/integrations/base/sql.ts +++ b/packages/server/src/integrations/base/sql.ts @@ -290,4 +290,5 @@ class SqlQueryBuilder extends SqlTableQueryBuilder { } } +export default SqlQueryBuilder module.exports = SqlQueryBuilder diff --git a/packages/server/src/integrations/index.ts b/packages/server/src/integrations/index.ts index 40b40d0753..6e98867329 100644 --- a/packages/server/src/integrations/index.ts +++ b/packages/server/src/integrations/index.ts @@ -9,6 +9,7 @@ const airtable = require("./airtable") const mysql = require("./mysql") const arangodb = require("./arangodb") const rest = require("./rest") +const oracle = require("./oracle") const { SourceNames } = require("../definitions/datasource") const DEFINITIONS = { @@ -23,6 +24,7 @@ const DEFINITIONS = { [SourceNames.MYSQL]: mysql.schema, [SourceNames.ARANGODB]: arangodb.schema, [SourceNames.REST]: rest.schema, + [SourceNames.ORACLE]: oracle.schema, } const INTEGRATIONS = { @@ -37,6 +39,7 @@ const INTEGRATIONS = { [SourceNames.MYSQL]: mysql.integration, [SourceNames.ARANGODB]: arangodb.integration, [SourceNames.REST]: rest.integration, + [SourceNames.ORACLE]: oracle.integration, } module.exports = { diff --git a/packages/server/src/integrations/oracle.ts b/packages/server/src/integrations/oracle.ts index efd9ad1310..2603d08b86 100644 --- a/packages/server/src/integrations/oracle.ts +++ b/packages/server/src/integrations/oracle.ts @@ -8,20 +8,29 @@ import { import { Table } from "../definitions/common" import { getSqlQuery } from "./utils" import { DatasourcePlus } from "./base/datasourcePlus" +import oracledb, { Result } from "oracledb" +import { Connection } from "oracledb" +import Sql from "./base/sql" +import { FieldTypes } from "../constants" +import { + buildExternalTableId, + convertType, + finaliseExternalTables +} from "./utils" module OracleModule { - // TODO: oracle js lib - // const connection = require("oracle") - const Sql = require("./base/sql") - const { FieldTypes } = require("../constants") - const { - buildExternalTableId, - convertType, - finaliseExternalTables, - } = require("./utils") + + oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT; interface OracleConfig { - // TODO: Connection config + host: string + port: number + database: string + user: string + password: string + // ssl?: boolean + // ca?: string + // rejectUnauthorized?: boolean } const SCHEMA: Integration = { @@ -30,10 +39,57 @@ module OracleModule { friendlyName: "Oracle", description: "description", datasource: { - // TODO: datasource config + 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, + }, + // ssl: { + // type: DatasourceFieldTypes.BOOLEAN, + // default: false, + // required: false, + // }, + // rejectUnauthorized: { + // type: DatasourceFieldTypes.BOOLEAN, + // default: false, + // required: false, + // }, + // ca: { + // type: DatasourceFieldTypes.LONGFORM, + // default: false, + // required: false, + // }, }, query: { - // TODO: query config + create: { + type: QueryTypes.SQL, + }, + read: { + type: QueryTypes.SQL, + }, + update: { + type: QueryTypes.SQL, + }, + delete: { + type: QueryTypes.SQL, + }, }, } @@ -41,11 +97,27 @@ module OracleModule { // TODO: type map } - async function internalQuery(client: any, query: SqlQuery) { - // TODO: Use oracle lib to run query - const rows = [] - - return rows + const internalQuery = async (connection: Connection, query: SqlQuery): Promise | null>=> { + try { + const result: Result = await connection.execute( + `SELECT manager_id, department_id, department_name + FROM departments + WHERE manager_id = :id`, + [103], // bind value for :id + ); + return result + } catch (err) { + console.error(err); + return null + } finally { + if (connection) { + try { + await connection.close(); + } catch (err) { + console.error(err); + } + } + } } class OracleIntegration extends Sql implements DatasourcePlus { @@ -56,8 +128,18 @@ module OracleModule { constructor(config: OracleConfig) { super("oracle") - this.config = config - //todo init client + this.config = config + } + + getConnection = async (): Promise => { + //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 config = { + user: this.config.user, + password: this.config.user, + connectString + } + return oracledb.getConnection(config); } /** @@ -112,8 +194,13 @@ module OracleModule { async query(json: QueryJson) { const operation = this._operation(json).toLowerCase() const input = this._query(json) - const response = await internalQuery(this.client, input) - return response.rows.length ? response.rows : [{ [operation]: true }] + const connection = await this.getConnection() + const result = await internalQuery(connection, input) + if (result && result.rows && result.rows.length) { + return result.rows + } else { + return [{ [operation]: true }] + } } } diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index 4d5fc64903..e5f0a8b2d2 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -2262,6 +2262,14 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.66.tgz#dd035d409df322acc83dff62a602f12a5783bbb3" integrity sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw== +"@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.3.2" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.3.2.tgz#fc8c2825e4ed2142473b4a81064e6e081463d1b3" @@ -4134,6 +4142,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" @@ -8261,6 +8274,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"