From 3a20d5bb21990b5872b068054deece3a1c68bf96 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Thu, 17 May 2018 14:58:28 +0100 Subject: [PATCH] oracle: new connector * Implemented connector to Oracle based on node-oracledb. * Implemented a pool of clients to ensure all the request from a connector share the same client. For more details, see `backend/persistent/datastores/pool.js`. * Upgraded to electron@2.0 and node@8, so that: - both node and electron have ABI 57 (for which node-oracledb provides binaries) - unable to use electron@1.8 due to issue resulting in a empty window at some startups * Added Dockerfile and npm scripts to build and launch a container with an Oracle Express database setup for testing. * Ensure connector failures are logged. * Added npm script `test-unit-oracle` for testing. See `CONTRIBUTING.md` for the requirements to run these tests. * Updated documentation in `CONTRIBUTING.md`. Closes #322 --- .travis.yml | 2 +- CONTRIBUTING.md | 153 +++++++++++++----- .../Settings/Preview/TableTree.react.js | 2 + .../Settings/Preview/code-editor.jsx | 1 + app/components/Settings/Tabs/Tab.react.js | 2 + app/constants/constants.js | 26 +++ app/images/oracle-logo.png | Bin 0 -> 16717 bytes appveyor.yml | 2 +- backend/persistent/datastores/Datastores.js | 27 ++-- backend/persistent/datastores/oracle.js | 98 +++++++++++ backend/persistent/datastores/pool.js | 43 +++++ circle.yml | 2 +- package.json | 11 +- test/backend/datastores.oracle.spec.js | 68 ++++++++ test/docker/oracle/Dockerfile | 10 ++ test/docker/oracle/README.md | 39 +++++ test/docker/oracle/setup.ctl | 11 ++ test/docker/oracle/setup.sh | 7 + test/docker/oracle/setup.sql | 5 + webpack.config.base.js | 1 + yarn.lock | 18 ++- 21 files changed, 468 insertions(+), 60 deletions(-) create mode 100644 app/images/oracle-logo.png create mode 100644 backend/persistent/datastores/oracle.js create mode 100644 backend/persistent/datastores/pool.js create mode 100644 test/backend/datastores.oracle.spec.js create mode 100644 test/docker/oracle/Dockerfile create mode 100644 test/docker/oracle/README.md create mode 100644 test/docker/oracle/setup.ctl create mode 100755 test/docker/oracle/setup.sh create mode 100644 test/docker/oracle/setup.sql diff --git a/.travis.yml b/.travis.yml index 105057840..b2aee6d07 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ matrix: - os: osx osx_image: xcode9.0 language: node_js - node_js: "6" + node_js: "8" env: - ELECTRON_CACHE=$HOME/.cache/electron - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5c4697dd3..9ce31921c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,46 +4,48 @@ Note that this section targets contributors and those who wish to set up and run If you're interested in using the distributed App, [download the latest release.](https://github.com/plotly/falcon-sql-client/releases) + ## Prerequisites -It is recommended to use node v6.12 with the latest electron-builder -## Install +Falcon development requires node v8 and yarn v1. Some connectors (e.g. the +Oracle connector) have additional requirements (see further below the section on +testing). -Start by cloning the repo via git: -```bash -git clone https://github.com/plotly/falcon-sql-client falcon-sql-client -``` - -And then install dependencies with **yarn**. +## Install -```bash +```sh +$ git clone https://github.com/plotly/falcon-sql-client falcon-sql-client $ cd falcon-sql-client $ yarn install +``` + + +## Build and Run the Electron App + +First, build the native dependencies against Electron: +```sh $ yarn run rebuild:modules:electron ``` -*Note: See package.json for the version of node that is required* +Then: -## Run as an Electron App -Run the app with -```bash +```sh $ yarn run build $ yarn run start ``` -## Run as a Server -Build and run the app: -```bash -$ yarn install -$ yarn run heroku-postbuild -$ yarn run start-headless -``` +## Build and Run the Web App -Build (after it was already built for electron desktop) and run the app: -```bash +If, last time, Falcon was built as an Electron app, then the native modules need +rebuilding against Node: +```sh $ yarn run rebuild:modules:node +``` + +To build and run the web app: +```sh $ yarn run heroku-postbuild $ yarn run start-headless ``` @@ -62,7 +64,8 @@ CORS_ALLOWED_ORIGINS: The database connector runs as a server by default as part of [Plotly On-Premise](https://plot.ly/products/on-premise). On Plotly On-Premise, every user who has access to the on-premise server also has access to the database connector, no extra installation or SSL configuration is necessary. If you would like to try out Plotly On-Premise at your company, please [get in touch with our team](https://plotly.typeform.com/to/seG7Vb), we'd love to help you out. -## Run as a docker image + +## Run as a Docker Container Build and run the docker image: ``` @@ -74,8 +77,11 @@ The web app will be accessible in your browser at `http://localhost:9494`. See the [Dockerfile](https://github.com/plotly/falcon-sql-client/blob/master/Dockerfile) for more information. + ## Developing +*([TODO] This section needs updating)* + Run watchers for the electron main process, the web bundle (the front-end), and the headless-bundle: ```bash $ yarn run watch-main @@ -89,7 +95,7 @@ $ yarn run watch-web $ yarn run watch-headless ``` -Then, view the the app in the electron window with: +Then, view the app in the electron window with: ```bash $ yarn run dev @@ -106,20 +112,79 @@ $ yarn start and in your web browser by visiting http://localhost:9494 + ## Testing -There are unit tests for the nodejs backend and integration tests to test the flow of the app. +Falcon is tested in three ways: -Run unit tests: -```bash -$ yarn run test-unit-all +- backend tests: stored under `test/backend` and run by `yarn run test-unit-all` +- frontend tests: stored under `test/app` and run by `yarn run test-jest` +- integration tests: stored in `test/integration_test.js` and run by `yarn run test-e2e` + +In some cases, we also provide `Dockerfile`s to build containers with a sample +database for testing. These can be found under `test/docker`. + + +### IBM DB2 Test Database + +In folder `test/docker/ibmdb2`, we provide a `Dockerfile` to setup an IBM DB2 +Express database for testing. + +To build the docker image, run `yarn run docker:db2:build`. + +And to start the docker container, run `yarn run docker:db2:start`. + +More details can be found in `test/docker/ibmdb2/README.md`. + + +### Oracle Test Database + +In folder `test/docker/oracle`, we provide a `Dockerfile` to setup an Oracle +Express database for testing. + +To build the docker image, run `yarn run docker:oracle:build`. + +And to start the docker container, run `yarn run docker:oracle:start`. + +More details can be found in `test/docker/oracle/README.md`. + + +#### Installation of Oracle Client Libraries + +Unlike IBM DB2's case and as of this writing, the Oracle bindings for Node.js, +[oracledb](https://www.npmjs.com/package/oracledb), are incomplete and users are +required to create an account on +[Oracle](https://login.oracle.com/mysso/signon.jsp) before downloading the +missing Oracle Client libraries. + +The installation procedure is very well documented +[here](https://github.com/oracle/node-oracledb/blob/master/INSTALL.md#instructions). + +The procedure for Ubuntu: + +1. Install requirements: `sudo apt-get -qq update && sudo apt-get --no-install-recommends -qq install alien bc libaio1` +2. Create an account on [Oracle](https://login.oracle.com/mysso/signon.jsp) +3. Download the Oracle Instant Client from [here](http://download.oracle.com/otn/linux/oracle11g/xe/oracle-xe-11.2.0-1.0.x86_64.rpm.zip) +4. Unzip `rpm` package: `unzip oracle-xe-11.2.0-1.0.x86_64.rpm.zip` +5. Convert `rpm` package into `deb`: `alien oracle-xe-11.2.0-1.0.x86_64.rpm` +6. Install `deb` package: `sudo dpkg -i oracle-instantclient12.2-basiclite_12.2.0.1.0-2_amd64.deb` + + +#### Running the Unit Tests for Oracle Connector + +First, open a terminal and start the container that runs test Oracle database: +```sh +$ yarn run docker:oracle:start ``` +and wait until the message `Ready` is shown. -Run integration tests: -```bash -$ yarn run test-e2e +Then, open another terminal and run: +```sh +$ export LD_LIBRARY_PATH=/usr/lib/oracle/12.2/client64/lib:$LD_LIBRARY_PATH +$ yarn run test-unit-oracle ``` + ## Builds and Releases - Update package.json with the new semver version @@ -129,13 +194,25 @@ $ yarn run test-e2e Builds are uploaded to https://github.com/plotly/falcon-sql-client/releases. + ## Troubleshooting -The Falcon Configuration information is installed in the user's home directory. -For example Unix and Mac (~/.plotly/connector) and for Windows (%userprofile%\.plotly\connector\). If you have tried the install -process and the app is still not running, this may be related to some corrupted -configuration files. You can try removing the existing configuration files and then -restarting the build process -```bash +Falcon keeps stores its configuration and logs in a folder under the user's home +directory: +- `%USERPROFILE%\.plotly\connector` (in Windows) +- `~/.plotly/connector` (in Mac and Linux) + +While developing a new connector, a common issue is that Falcon's configuration +gets corrupted. This often leads to a failure at start up. Until we address this +issue in more user-friendly manner (issue #342), the solution is to delete +Falcon's configuration folder. + +In Windows: +```sh +rmdir /s %USERPROFILE%\.plotly\connector +``` + +In Mac and Linux: +```sh rm -rf ~/.plotly/connector/ -``` \ No newline at end of file +``` diff --git a/app/components/Settings/Preview/TableTree.react.js b/app/components/Settings/Preview/TableTree.react.js index 3c2e2e213..9c39d29a0 100644 --- a/app/components/Settings/Preview/TableTree.react.js +++ b/app/components/Settings/Preview/TableTree.react.js @@ -35,6 +35,8 @@ class TableTree extends Component { return getPathNames(connectionObject.url)[2]; case DIALECTS.CSV: return connectionObject.label || connectionObject.id || connectionObject.database; + case DIALECTS.ORACLE: + return connectionObject.connectionString; default: return connectionObject.database; } diff --git a/app/components/Settings/Preview/code-editor.jsx b/app/components/Settings/Preview/code-editor.jsx index 3bab41022..030f18ed6 100644 --- a/app/components/Settings/Preview/code-editor.jsx +++ b/app/components/Settings/Preview/code-editor.jsx @@ -191,6 +191,7 @@ export default class CodeEditor extends React.Component { [DIALECTS.MYSQL]: 'text/x-mysql', [DIALECTS.SQLITE]: 'text/x-sqlite', [DIALECTS.MARIADB]: 'text/x-mariadb', + [DIALECTS.ORACLE]: 'text/x-plsql', [DIALECTS.POSTGRES]: 'text/x-pgsql', [DIALECTS.REDSHIFT]: 'text/x-pgsql', [DIALECTS.MSSQL]: 'text/x-mssql' diff --git a/app/components/Settings/Tabs/Tab.react.js b/app/components/Settings/Tabs/Tab.react.js index b907f4179..4bbefab00 100644 --- a/app/components/Settings/Tabs/Tab.react.js +++ b/app/components/Settings/Tabs/Tab.react.js @@ -38,6 +38,8 @@ export default class ConnectionTab extends Component { label = `Elasticsearch (${connectionObject.host})`; } else if (connectionObject.dialect === DIALECTS.ATHENA) { label = `Athena (${connectionObject.database})`; + } else if (connectionObject.dialect === DIALECTS.ORACLE) { + label = `${connectionObject.connectionString}`; } else if (connectionObject.dialect === DIALECTS.SQLITE) { label = connectionObject.storage; } else if (connectionObject.dialect === DIALECTS.DATA_WORLD) { diff --git a/app/constants/constants.js b/app/constants/constants.js index c24d8c3e7..2ec7bc479 100644 --- a/app/constants/constants.js +++ b/app/constants/constants.js @@ -5,6 +5,7 @@ import {concat} from 'ramda'; export const DIALECTS = { MYSQL: 'mysql', MARIADB: 'mariadb', + ORACLE: 'oracle', POSTGRES: 'postgres', REDSHIFT: 'redshift', ELASTICSEARCH: 'elasticsearch', @@ -23,6 +24,7 @@ export const DIALECTS = { export const SQL_DIALECTS_USING_EDITOR = [ 'mysql', 'mariadb', + 'oracle', 'postgres', 'redshift', 'mssql', @@ -139,6 +141,22 @@ export const CONNECTION_CONFIG = { } ] ), + [DIALECTS.ORACLE]: [ + {'label': 'Username', 'value': 'username', 'type': 'text'}, + {'label': 'Password', 'value': 'password', 'type': 'password'}, + { + 'label': 'Connection', + 'value': 'connectionString', + 'type': 'text', + 'description': ` + An Easy Connect string, + a Net Service Name from a local 'tnsnames.ora' file or an external naming service, + an SID of a local Oracle database instance, + or leave empty to connect to the local default database. + See https://oracle.github.io/node-oracledb/doc/api.html#connectionstrings for examples. + ` + } + ], [DIALECTS.POSTGRES]: commonSqlOptions, [DIALECTS.REDSHIFT]: commonSqlOptions, [DIALECTS.SQLITE]: [ @@ -245,6 +263,7 @@ export const LOGOS = { [DIALECTS.CSV]: 'images/csv-logo.png', [DIALECTS.IBM_DB2]: 'images/ibmdb2-logo.png', [DIALECTS.REDSHIFT]: 'images/redshift-logo.png', + [DIALECTS.ORACLE]: 'images/oracle-logo.png', [DIALECTS.POSTGRES]: 'images/postgres-logo.png', [DIALECTS.ELASTICSEARCH]: 'images/elastic-logo.png', [DIALECTS.MYSQL]: 'images/mysql-logo.png', @@ -263,6 +282,8 @@ export function PREVIEW_QUERY(connection, table, elasticsearchIndex) { return 'SELECT TOP 1000 * FROM ?'; case DIALECTS.IBM_DB2: return `SELECT * FROM ${table} FETCH FIRST 1000 ROWS ONLY`; + case DIALECTS.ORACLE: + return `SELECT * FROM ${table} WHERE ROWNUM <= 1000`; case DIALECTS.APACHE_IMPALA: case DIALECTS.APACHE_SPARK: case DIALECTS.MYSQL: @@ -382,6 +403,11 @@ export const SAMPLE_DBS = { host: 'db2.test.plotly.host', dialect: DIALECTS.IBM_DB2 }, + [DIALECTS.ORACLE]: { + username: 'XDB', + password: 'xdb', + connectionString: 'localhost/XE' + }, [DIALECTS.POSTGRES]: { username: 'masteruser', password: 'connecttoplotly', diff --git a/app/images/oracle-logo.png b/app/images/oracle-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a901ac940d6218f903768d0d10eb89bd5c2af87d GIT binary patch literal 16717 zcmeHvg;!Ny)a|)+cXxvVqI7qO(nv~5cZW2R(x4(8f`Bv%NQ0n&NT;-LX}Ac;rQvOU zZ@houjq#1~IdmM49?#il?{(ImYtFfh)6!7E$DzT2APE1ls-g}Ap@84Q!mu#FVV-HO z6CBXIg4sp(%t5Po2OkiLXrl8n4rgsa(ezb`-^t($)3$&tZeG}5L))X=ECAqB{JnNvydlF zE$+wpL1=2EH2tNesOt_Y?wRV_s{IXN_?RIbP2%y^b9yEz9c)K~&N z-nqJ_Q>G)qrdNg;FL!O^PbW$>h}7<(v#_wdCga}1h>eZqx~77_fh86e22LXW_vC-) z;QwbFSjmaVDk}ad)UUxv8+cPg`u0BT*{8En;&Uag61`D%B4TY(vdMDP1}mkXGrv(W zn2>*aq`ehG{;)zUEJavW5EF=GCXD}1ekMH*Lr2$I4kc``#-8+Bma8(wZLmb0{E^s| zCnXzllOHM~Ldm6zQDiI>`ynZ!OdECeY>in%6jo#=#l%2}Se8>%e8Z7R4bfp9rE4N%(yWxNsm7bdK%) zZPycU?Gw6mg9426mMdC77Q1F;kl&^D7S_i!1Pxn9g(0o%*z!ozZoflqO(-KaD zvr)l*nNoa88$z6HvI^JkuZ6`aLiUoGg=L($11ccP`O~94S^!H!#r(KG5%Rd)XE&wo z+?H&zhS2WpZ^1mc?ubnfCL4Uoi=!b;J+Raco{iO#YgOLhl)AS_8%IE2pkIa4LJ!9K zCXy8rCCK5-la(8Mr{%SYv8b*FS79tH@OU9w)5+k?t*EHTPT>z}Ws9(mm6N7jU)mwi zTKDHuujZFizWy7(KBWUMfm1Gv8kQx)7)Qa&By;t!UpOI^Rk@(R#iT4h1gC`@JjV*1 z0{ZX1rmdBMA=MNQ6pMVE`bT64|NC1ObP^~>WJ$J2m(!vziOKaqq9H(lgkD@W2BbPLDu55nGTx4F3E z;G}Ggp?V&{3najN%xTV{gwcr%U&9&Sirg;mFUI#OQOX6M+l=OdyI(W&!eocCV|vLA zprrJq`6$g0X_8!VIbL%HZ%!})&^Qq^`I=nS8$ow|a=4RWp0YK~>%=KdLac3jC<5*M zULy#-J&Lbs8X$8VAn{{UEH5x&W+{q27J}HoBs$&%$+)F@z|Rid^8z@wguzF+Y8686 zdvj?nTGPH^a9(6Yp_nU9lt?2tS&gqu;8m!Q4R`j4;(W>w_gksf&yeMP;=q@$D8X zywWUjcBQ*k+)rbFh!MK47dBNzYc(R1oYUY$9C{mUxS5BG^+$8LQ+qk|fFTTwg$!B) zi^PxS`2%ml=JbWn{_7m2c%uA=F#Stpn9Wwc<$Ue11mWR5-4uaP=}NERN4iZ|!|{Cb zAxCjFryz>%CyYS_(={rdT~IpvDXZg*a?nq*Ue2dsqJPkq5vWj+kwCA^713ec3+5#8 zpZVEbQP#^HvU1@vCvmZ{uPp>2Hn$Gy*~TI30U1L|)5sjR4v(14iT<4>da>mYT=10! zm)E>|5OsXE)-lNe9{0KqVk%XCMJM!AuNl0z6`^FYe=RQD$^LYQV;>fUu9>aDgw9sfWYNFX!WlO8%dAjQRMf-U zhgRCI_$Mz;I9_@fQLOevH#=P@{OUMT>&*+EwpSN>5q+wFLt{}kebi7#}PlFSAiE2nv#~46nEE2 zVnTLsRS=^1K9dz|X$%4^>Mt1tx679czHUgx=(UXL7H&^?$W$lyD$IXDmDjEp z%VLm%wc)>8@j~D=Dq)wYDog|tzYN=%;-c|~s&Ho8X+f5-btEPxre`{VQZg{@eo?WC zP!45v-^?#3Oyb1Pwx7!DRXG;F1G&23P3qlaFV(~#QMV54y*V5BMx9+gy7Rnh)o7U> zHuCzE_FhTHEyK}-X5u73UUL~ukj3hc{rMj86Ft_3Z$A(dm__=$tPSwcTc=_IV&bt5 z9UN#UhOuB{NWk;OWs*d~BzB2E9a3F{`rdj11ODEkQ!$vfu>)|~N9pT${VI#x;Ko#A zOB?e*-rhekyOeV+s5cIv=SKf&dVcf}fE6}g@zO6JycU~rNp5M4>_1#Cg zuOG=}&d@)E?&-zsI1_C@OEitmDJ$r_b#tlq7U#k|J(ms>48EVQGt#&b&+#|E#j$PK za;Hi_0OGX5+u?Mx#?z)ijXc)4Vc|gc`l8mjG17j4s(%3c!d#)HIPwB%(jRbK0KWH& zo4~S@-SCs$Lh#h1V^|5({b%6mX!oB*lo^w+Mv_`I*u%!!#1z|K7K)xKb3Ln%+CZ-| z2D?Ma(xTNPkO%QSCbeJs@W&F06{)G_h)2ip# zVje7DEf6{j^b-H#H{e~4OLY2uNwwlW5%yy&Zy2L-b|%A(>sZ!z_h6_*foGPC;y8I1 zCkFkeOJ286^9kVV+vJtcsgdWKJuPq&Kk4VPj2dcW&?H}HL4mQ*KIQ!iUC!PQ!iDN; zWORX*$nM8Qw(9Wr;z9RFMG4^jsjAY4k##PmYq}rbOVlu_$w*=ZimaJarXiRhI2I#DG^Cb%f^krmB3~nZiZdWXOGST;FZi>4yGtiuv5n zVOC(`d8)(T8YwOWN7E*Wzr=nU1RYr$(o4`(8Id)3 zvcqdF-IWs~jO*EWPk9nVcerM+FS+Tk8Tr_iF5iI3eHUQh}yg@0C9*>u&D>n!!`l)d?x*`V}SZY#%}ZugiuHKUKm zpCEeG?_IZ>V}s~Rr4uC@e!|C${5Mhi@&u=)`ud~18k1nC}2wydrxn8eEdi9f-5J(Tp8?1mga#~2TeR54!XBXSFl?YfRWwJf{4-k=wlQ!9w0jKnf;HK zGmM)E;^O&~ixe>ZmgIT|G6l3pzuW`(4)bQCGoROFMe^z)PLc${@QY+vs~_f~w&31U zqV??!m*{r!N|X7>phCTyIa`Zg%v{^=854Sbs4PEJn4gUHgnF+rJhU_G5i2(#re`#s z$MXB>n$uI|kDa~}zFh&U*dPXZ(!j1$YB}MZ&KMjS@s5{S=8S1eKcdc$FYWe%l4O5Q zj@OjNTSRGaP~j*^LJL4$_-vh=U;5PKcnO#N6wWZICNy9@x_>2ia}cU3=!yq+Ik!_$ zYv>{WmH8*KO62~@uJuDwn5xSh+R2azMhkW_+|-ZxcavLL$RWXYQ{h^KxKGO7GNWTp zbb*ALx)}pWu0#*DAlH`0xLTFbtB6LIKawYUKhPW+Z1G8?UNVh+h`EJ{MZjARiVA$3 z%G^5fTi(jw!S1cuu_hw_En*0@XLy?04q6`sF>GC=gv%-*nTS@OqDnxwHJqqQxkwpY z!laTYFZV%Nbty<-vZga@n;R?X6

v4vsH^w)of>fMJ!QXBGc6cKB73=;IZ4r{$Vb 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 literal 0 HcmV?d00001 diff --git a/appveyor.yml b/appveyor.yml index e5be54013..c781c3aee 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,7 +2,7 @@ version: 0.4.{build} environment: matrix: - - nodejs_version: 6 + - nodejs_version: 8 POSTGRES_PATH: C:\Program Files\PostgreSQL\9.6 platform: diff --git a/backend/persistent/datastores/Datastores.js b/backend/persistent/datastores/Datastores.js index d7bef60c1..e615de4da 100644 --- a/backend/persistent/datastores/Datastores.js +++ b/backend/persistent/datastores/Datastores.js @@ -1,3 +1,9 @@ +import Logger from '../../logger'; +function logError(error) { + Logger.log(`${error.constructor}: ${error.message}`); + throw error; +} + import * as Sql from './Sql'; import * as Elasticsearch from './Elasticsearch'; import * as S3 from './S3'; @@ -10,6 +16,7 @@ import * as DatastoreMock from './datastoremock'; import * as Athena from './athena'; const CSV = require('./csv'); +const Oracle = require('./oracle.js'); /* * Switchboard to all of the different types of connections @@ -56,6 +63,8 @@ function getDatastoreClient(connection) { return DataWorld; } else if (dialect === 'athena') { return Athena; + } else if (dialect === 'oracle') { + return Oracle; } return Sql; } @@ -71,7 +80,7 @@ function getDatastoreClient(connection) { * } */ export function query(queryStatement, connection) { - return getDatastoreClient(connection).query(queryStatement, connection); + return getDatastoreClient(connection).query(queryStatement, connection).catch(logError); } /** @@ -80,7 +89,7 @@ export function query(queryStatement, connection) { * @returns {Promise} that resolves when the connection succeeds */ export function connect(connection) { - return getDatastoreClient(connection).connect(connection); + return getDatastoreClient(connection).connect(connection).catch(logError); } /** @@ -91,7 +100,7 @@ export function connect(connection) { export function disconnect(connection) { const client = getDatastoreClient(connection); return (client.disconnect) ? - client.disconnect(connection) : + client.disconnect(connection).catch(logError) : Promise.resolve(connection); } @@ -107,7 +116,7 @@ export function disconnect(connection) { * } */ export function schemas(connection) { - return getDatastoreClient(connection).schemas(connection); + return getDatastoreClient(connection).schemas(connection).catch(logError); } /** @@ -118,7 +127,7 @@ export function schemas(connection) { * for elasticsearch, this means return the available "documents" per an "index" */ export function tables(connection) { - return getDatastoreClient(connection).tables(connection); + return getDatastoreClient(connection).tables(connection).catch(logError); } /* @@ -131,7 +140,7 @@ export function tables(connection) { // TODO - I think specificity is better here, just name this to "keys" // and if we ever add local file stuff, add a new function like "files". export function files(connection) { - return getDatastoreClient(connection).files(connection); + return getDatastoreClient(connection).files(connection).catch(logError); } @@ -141,7 +150,7 @@ export function files(connection) { * Return a list of configured Apache Drill storage plugins */ export function storage(connection) { - return getDatastoreClient(connection).storage(connection); + return getDatastoreClient(connection).storage(connection).catch(logError); } /* @@ -152,9 +161,9 @@ export function storage(connection) { * that plugin. */ export function listS3Files(connection) { - return getDatastoreClient(connection).listS3Files(connection); + return getDatastoreClient(connection).listS3Files(connection).catch(logError); } export function elasticsearchMappings(connection) { - return getDatastoreClient(connection).elasticsearchMappings(connection); + return getDatastoreClient(connection).elasticsearchMappings(connection).catch(logError); } diff --git a/backend/persistent/datastores/oracle.js b/backend/persistent/datastores/oracle.js new file mode 100644 index 000000000..a3776944f --- /dev/null +++ b/backend/persistent/datastores/oracle.js @@ -0,0 +1,98 @@ +module.exports = { + connect: connect, + tables: tables, + schemas: schemas, + query: query, + disconnect: disconnect +}; + +let oracledb; +try { + oracledb = require('oracledb'); +} catch (err) { + oracledb = err; +} + +const Pool = require('./pool.js'); +const pool = new Pool(newClient, sameConnection); + +function newClient(connection) { + if (oracledb instanceof Error) { + throw new Error(oracledb.message); + } + + return oracledb.getConnection({ + user: connection.username, + password: connection.password, + connectionString: connection.connectionString + }); +} + +function sameConnection(connection1, connection2) { + return ( + connection1.username === connection2.username && + connection1.password === connection2.password && + connection1.connectionString === connection2.connectionString + ); +} + +function connect(connection) { + return pool.getClient(connection); +} + +function disconnect(connection) { + return pool.remove(connection) + .then(client => client && client.close()); +} + +function tables(connection) { + const sqlQuery = ` + SELECT * FROM user_all_tables + WHERE + table_name NOT LIKE '%$%' AND + (table_type IS NULL OR table_type <> 'XMLTYPE') AND + (num_rows IS NULL OR num_rows > 0) AND + secondary = 'N' + `; + + return pool.getClient(connection) + .then(client => client.execute(sqlQuery)) + .then(result => { + return result.rows.map(row => row[0]); + }); +} + +function schemas(connection) { + const sqlQuery = ` + SELECT + c.table_name, + c.column_name, + c.data_type + FROM + user_tab_columns c, + user_all_tables t + WHERE + c.table_name = t.table_name AND + t.table_name NOT LIKE '%$%' AND + (t.table_type IS NULL OR t.table_type <> 'XMLTYPE') AND + (t.num_rows IS NULL OR t.num_rows > 0) AND + t.secondary = 'N' + `; + + return query(sqlQuery, connection); +} + +function query(queryString, connection) { + return pool.getClient(connection) + .then(client => client.execute(queryString)) + .then(result => { + const columnnames = result.metaData.map(column => column.name); + + // convert buffers into an hexadecimal string + const rows = result.rows.map( + row => row.map(value => (value instanceof Buffer) ? value.toString('hex') : value) + ); + + return {columnnames, rows}; + }); +} diff --git a/backend/persistent/datastores/pool.js b/backend/persistent/datastores/pool.js new file mode 100644 index 000000000..8d420d387 --- /dev/null +++ b/backend/persistent/datastores/pool.js @@ -0,0 +1,43 @@ +export default class Pool { + /** + * Pool keeps a list of clients indexed by connection objects + * + * @param {function} newClient Function that takes a connection object and creates a new client + * @param {function} sameConnection Function that returns whether two connection objects are the same + */ + constructor(newClient, sameConnection) { + this.newClient = newClient; + this.sameConnection = sameConnection; + this._pool = []; + } + + /** + * Get client indexed by connection (if no client found, a new client is created using newClient) + * @param {object} connection Connection object + * @returns {*} client for connection + */ + getClient(connection) { + for (let i = this._pool.length - 1; i >= 0; i--) { + if (this.sameConnection(connection, this._pool[i][0])) { + return this._pool[i][1]; + } + } + + const client = this.newClient(connection); + this._pool.push([connection, client]); + return client; + } + + /** + * Remove connection from pool + * @param {object} connection Connection object + * @returns {*} removed client (or undefined, if no client was found) + */ + remove(connection) { + for (let i = this._pool.length - 1; i >= 0; i--) { + if (this.sameConnection(connection, this._pool[i][0])) { + return this._pool.splice(i, 1)[0][1]; + } + } + } +} diff --git a/circle.yml b/circle.yml index 233ce3031..e56764871 100644 --- a/circle.yml +++ b/circle.yml @@ -2,7 +2,7 @@ version: 2 jobs: build: docker: - - image: circleci/node:6.13.0-browsers + - image: circleci/node:8-browsers - image: quay.io/plotly/falcon-test-spark - image: quay.io/plotly/falcon-test-db2 environment: diff --git a/package.json b/package.json index 0b70c86b5..7e3a15af5 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,8 @@ "docker:db2:start": "docker run --rm -ti -p 50000:50000 pdc-db2", "docker:falcon:build": "docker build -t falcon-sql-client:local .", "docker:falcon:start": "docker run -ti --rm -p 9494:9494 -e PLOTLY_CONNECTOR_AUTH_ENABLED=$PLOTLY_CONNECTOR_AUTH_ENABLED -e PLOTLY_CONNECTOR_ALLOWED_USERS=$PLOTLY_CONNECTOR_ALLOWED_USERS falcon-sql-client:local", + "docker:oracle:build": "docker build test/docker/oracle -t falcon-test-oracle --no-cache", + "docker:oracle:start": "docker run --rm -ti -p 1521:1521 falcon-test-oracle", "rebuild:modules:electron": "cross-env FSEVENTS_BUILD_FROM_SOURCE=true node scripts/rebuild-modules.js --electron", "rebuild:modules:node": "cross-env FSEVENTS_BUILD_FROM_SOURCE=true node scripts/rebuild-modules.js", "fix:module:ibmdb": "node scripts/fix-module-ibmdb.js", @@ -39,6 +41,8 @@ "test-unit-livy": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 electron-mocha --full-trace --timeout 90000 --compilers js:babel-register test/backend/datastores.livy.spec.js", "test-unit-athena": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 electron-mocha --full-trace --timeout 90000 --compilers js:babel-register test/backend/datastores.athena.spec.js", "test-unit-oauth2": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 electron-mocha --full-trace --timeout 90000 --compilers js:babel-register test/backend/routes.oauth2.spec.js", + "test-unit-oracle": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 electron-mocha --full-trace --timeout 90000 --compilers js:babel-register test/backend/datastores.oracle.spec.js", + "test-unit-oracle:node": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 mocha --full-trace --timeout 90000 --compilers js:babel-register test/backend/datastores.oracle.spec.js", "test-unit-plotly": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 electron-mocha --full-trace --timeout 90000 --compilers js:babel-register test/backend/PlotlyAPI.spec.js", "test-unit-scheduler": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 electron-mocha --full-trace --timeout 90000 --compilers js:babel-register test/backend/QueryScheduler.spec.js", "test-unit-routes": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 electron-mocha --full-trace --timeout 90000 --compilers js:babel-register test/backend/routes.spec.js", @@ -165,7 +169,7 @@ "css-loader": "^0.28.7", "del": "^3.0.0", "devtron": "^1.3.0", - "electron": "^1.7.8", + "electron": "2.0", "electron-builder": "^19.46.4", "electron-debug": "^1.4.0", "electron-mocha": "^4.0.3", @@ -234,6 +238,7 @@ "font-awesome": "^4.6.1", "ibm_db": "^2.3.0", "mysql": "^2.15.0", + "oracledb": "^2.2.0", "papaparse": "^4.3.7", "pg": "^4.5.5", "pg-hstore": "^2.3.2", @@ -244,11 +249,11 @@ "tedious": "^2.1.4" }, "engines": { - "node": "6", + "node": "8", "yarn": "1" }, "devEngines": { - "node": "6", + "node": "8", "yarn": "1" } } diff --git a/test/backend/datastores.oracle.spec.js b/test/backend/datastores.oracle.spec.js new file mode 100644 index 000000000..387479e80 --- /dev/null +++ b/test/backend/datastores.oracle.spec.js @@ -0,0 +1,68 @@ +import {assert} from 'chai'; + +import {DIALECTS} from '../../app/constants/constants.js'; + +import { + connect, + disconnect, + query, + schemas, + tables +} from '../../backend/persistent/datastores/Datastores.js'; + +const connection = { + dialect: DIALECTS.ORACLE, + username: 'XDB', + password: 'xdb', + connectionString: 'localhost/XE' +}; + +// Skip tests if there is no working installation of oracledb +let oracledb; +try { + oracledb = require('oracledb'); +} catch (err) { + if (!process.env.CIRCLECI) { + console.log('Skipping `datastores.oracle.spec.js`:', err); // eslint-disable-line + } +} + +((oracledb) ? describe : xdescribe)('Oracle:', function () { + it('connect succeeds', function() { + return connect(connection); + }); + + it('tables returns list of tables', function() { + return tables(connection).then(result => { + assert.include(result, 'CONSUMPTION2010', result); + }); + }); + + it('schemas returns schemas for all tables', function() { + return schemas(connection).then(results => { + assert.deepInclude(results.rows, ['CONSUMPTION2010', 'ALCOHOL', 'NUMBER']); + assert.deepInclude(results.rows, ['CONSUMPTION2010', 'LOCATION', 'VARCHAR2']); + assert.deepEqual(results.columnnames, ['TABLE_NAME', 'COLUMN_NAME', 'DATA_TYPE']); + }); + }); + + it('query returns rows and column names', function() { + return query( + 'SELECT * FROM CONSUMPTION2010 WHERE ROWNUM <= 5', + connection + ).then(results => { + assert.deepEqual(results.rows, [ + ['Belarus', 17.5], + ['Moldova', 16.8], + ['Lithuania', 15.4], + ['Russia', 15.1], + ['Romania', 14.4] + ]); + assert.deepEqual(results.columnnames, ['LOCATION', 'ALCOHOL']); + }); + }); + + it('disconnect succeeds', function() { + return disconnect(connection); + }); +}); diff --git a/test/docker/oracle/Dockerfile b/test/docker/oracle/Dockerfile new file mode 100644 index 000000000..bd07ea8c1 --- /dev/null +++ b/test/docker/oracle/Dockerfile @@ -0,0 +1,10 @@ +FROM wnameless/oracle-xe-11g:16.04 + +EXPOSE 1521 + +ADD https://raw.githubusercontent.com/plotly/datasets/master/2010_alcohol_consumption_by_country.csv /2010_alcohol_consumption_by_country.csv +COPY setup.sql / +COPY setup.ctl / +COPY setup.sh /docker-entrypoint-initdb.d/ + +ENV ORACLE_ENABLE_XDB true diff --git a/test/docker/oracle/README.md b/test/docker/oracle/README.md new file mode 100644 index 000000000..d55b68f4d --- /dev/null +++ b/test/docker/oracle/README.md @@ -0,0 +1,39 @@ +The Dockerfile in this folder builds a Docker image that starts an instance of +Oracle Database 11g Express Edition with the logins `SYSTEM/oracle` and +`XDB/xdb`, and the sample database `consumption2010`. + + +# License + +This Dockerfile uses +[wnameless/oracle-xe-11g](https://hub.docker.com/r/wnameless/oracle-xe-11g/) as +a base image. + +Please, note that Oracle Database Express Edition is [licensed under the Oracle +Technology Network Developer License +Terms](http://www.oracle.com/technetwork/licenses/database-11g-express-license-459621.html). + + +# Usage + + +## Build + +Run the command below in the folder where the Dockerfile is located: + +```sh +docker build . -t falcon-test-oracle + +``` + + +## Run + +Run the command below inside a terminal, to start an instance listening on port +1521: + +```sh +docker run --rm -ti -p 1521:1521 falcon-test-oracle +``` + +To stop the container, just press `CTRL-C`. diff --git a/test/docker/oracle/setup.ctl b/test/docker/oracle/setup.ctl new file mode 100644 index 000000000..2d6ea4d2d --- /dev/null +++ b/test/docker/oracle/setup.ctl @@ -0,0 +1,11 @@ +OPTIONS(SKIP=1) + +LOAD DATA + INFILE "/2010_alcohol_consumption_by_country.csv" + + REPLACE + INTO TABLE consumption2010 + + FIELDS TERMINATED BY "," + OPTIONALLY ENCLOSED BY '"' + (location, alcohol) diff --git a/test/docker/oracle/setup.sh b/test/docker/oracle/setup.sh new file mode 100755 index 000000000..76e3b211e --- /dev/null +++ b/test/docker/oracle/setup.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -e + +sqlplus XDB/xdb @/setup.sql +sqlldr userid=XDB/xdb control=/setup.ctl + +echo Ready diff --git a/test/docker/oracle/setup.sql b/test/docker/oracle/setup.sql new file mode 100644 index 000000000..234fc440c --- /dev/null +++ b/test/docker/oracle/setup.sql @@ -0,0 +1,5 @@ +CREATE TABLE consumption2010 ( + location varchar2(50), + alcohol number +); +QUIT diff --git a/webpack.config.base.js b/webpack.config.base.js index 471e7da12..f42560b58 100644 --- a/webpack.config.base.js +++ b/webpack.config.base.js @@ -35,6 +35,7 @@ export default { 'font-awesome': 'font-awesome', 'ibm_db': 'commonjs ibm_db', 'mysql': 'mysql', + 'oracledb': 'commonjs oracledb', 'pg': 'pg', 'pg-hstore': 'pg-hstore', 'restify': 'commonjs restify', diff --git a/yarn.lock b/yarn.lock index 663ffa2cf..e26697118 100644 --- a/yarn.lock +++ b/yarn.lock @@ -168,9 +168,9 @@ version "9.4.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-9.4.6.tgz#d8176d864ee48753d053783e4e463aec86b8d82e" -"@types/node@^7.0.18": - version "7.0.43" - resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.43.tgz#a187e08495a075f200ca946079c914e1a5fe962c" +"@types/node@^8.0.24": + version "8.10.14" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.14.tgz#a24767cfa22023f1bf7e751c0ead56a14c07ed45" "JSV@>= 4.0.x": version "4.0.2" @@ -3329,11 +3329,11 @@ electron-window@^0.8.0: dependencies: is-electron-renderer "^2.0.0" -electron@^1.7.8: - version "1.7.8" - resolved "https://registry.yarnpkg.com/electron/-/electron-1.7.8.tgz#27b791a6895171a7d52991b99442cdbd10a3539d" +electron@2.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/electron/-/electron-2.0.1.tgz#d9defcc187862143b9027378be78490eddbfabf4" dependencies: - "@types/node" "^7.0.18" + "@types/node" "^8.0.24" electron-download "^3.0.1" extract-zip "^1.0.3" @@ -7239,6 +7239,10 @@ options@>=0.0.5: version "0.0.6" resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f" +oracledb@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/oracledb/-/oracledb-2.2.0.tgz#424222d00721b1656181a0efa8b452193d113ba2" + orbit-camera-controller@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/orbit-camera-controller/-/orbit-camera-controller-4.0.0.tgz#6e2b36f0e7878663c330f50da9b7ce686c277005"