-
Notifications
You must be signed in to change notification settings - Fork 101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: oracle connector #310
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
node_modules | ||
out | ||
local | ||
src/**/*.js | ||
*.tgz |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
local | ||
resources | ||
src/*.js | ||
src/*.test.ts | ||
jest.config.js | ||
tsconfig.json | ||
*.tgz |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,75 @@ | ||||||
# Oracle connector | ||||||
|
||||||
This library allows to connect to [Oracle](https://www.oracle.com/database), extract its schema and more... | ||||||
|
||||||
It lists all schemas, tables, columns, relations and types and format them in a JSON Schema. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Grammar Improvement: Verb agreement. The phrase "and format them in a JSON Schema" should be adjusted for proper verb agreement, perhaps to "and formats them into a JSON Schema." - It lists all schemas, tables, columns, relations and types and format them in a JSON Schema.
+ It lists all schemas, tables, columns, relations, and types and formats them into a JSON Schema. Committable suggestion
Suggested change
|
||||||
|
||||||
This library is made by [Azimutt](https://azimutt.app) to allow people to explore their Oracle database. | ||||||
It's accessible through the [Desktop app](../../desktop) (soon), the [CLI](https://www.npmjs.com/package/azimutt) or even the website using the [gateway](../../gateway) server. | ||||||
|
||||||
**Feel free to use it and even submit PR to improve it:** | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Style Improvement: Consider alternative phrasing. The phrase "Feel free to use it" could be replaced with a more professional alternative like "You are encouraged to use it." - **Feel free to use it and even submit PR to improve it:**
+ **You are encouraged to use it and even submit PRs to improve it:** Committable suggestion
Suggested change
ToolsLanguageTool
|
||||||
|
||||||
## Publish | ||||||
|
||||||
- update `package.json` version | ||||||
- update lib versions (`pnpm -w run update` + manual) | ||||||
- test with `pnpm run dry-publish` and check `azimutt-connector-oracle-x.y.z.tgz` content | ||||||
- launch `pnpm publish --access public` | ||||||
|
||||||
View it on [npm](https://www.npmjs.com/package/@azimutt/connector-oracle). | ||||||
|
||||||
## Dev | ||||||
|
||||||
If you need to develop on multiple libs at the same time (ex: want to update a connector and try it through the CLI), depend on local libs but publish & revert before commit. | ||||||
|
||||||
- Depend on a local lib: `pnpm add <lib>`, ex: `pnpm add @azimutt/models` | ||||||
- "Publish" lib locally by building it: `pnpm run build` | ||||||
|
||||||
## Oracle Setup | ||||||
|
||||||
### Run in Docker | ||||||
|
||||||
You can use the free version of Oracle Database | ||||||
|
||||||
```bash | ||||||
docker pull container-registry.oracle.com/database/free:latest | ||||||
``` | ||||||
|
||||||
To launch a container, the needed configuration is the `ORACLE_PWD` of the `SYS` user. You can also map the default 1521 port to your computer. | ||||||
|
||||||
```bash | ||||||
docker run -d --name oracle -p 1521:1521 -e ORACLE_PWD=oracle container-registry.oracle.com/database/free:latest | ||||||
``` | ||||||
|
||||||
To connect, you can use a jdbc driver with the URL `jdbc:oracle:thin:<user>/<password>@//localhost:1521/FREE` | ||||||
|
||||||
### Setup a user | ||||||
|
||||||
Create a user | ||||||
|
||||||
```sql | ||||||
CREATE USER "C##AZIMUTT" IDENTIFIED BY "azimutt"; | ||||||
``` | ||||||
|
||||||
Grand permissions | ||||||
|
||||||
```sql | ||||||
GRANT CONNECT, RESOURCE, DBA TO "C##AZIMUTT"; | ||||||
``` | ||||||
|
||||||
Update user quota on `Users` tablespace | ||||||
|
||||||
```sql | ||||||
ALTER USER "C##AZIMUTT" QUOTA UNLIMITED ON USERS; | ||||||
``` | ||||||
|
||||||
### Create a table | ||||||
|
||||||
```sql | ||||||
CREATE TABLE "C##AZIMUTT"."USERS"( | ||||||
user_id NUMBER GENERATED BY DEFAULT AS IDENTITY, | ||||||
first_name VARCHAR2(50) NOT NULL, | ||||||
last_name VARCHAR2(50) NOT NULL, | ||||||
PRIMARY KEY(user_id) | ||||||
); | ||||||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
module.exports = { | ||
transform: {'^.+\\.ts?$': 'ts-jest'}, | ||
testEnvironment: 'node', | ||
testRegex: '/src/.+\\.test?\\.(ts|tsx)$', | ||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
{ | ||
"name": "@azimutt/connector-oracle", | ||
"version": "0.1.0", | ||
"description": "Connect to Oracle, extract schema, run analysis and queries", | ||
"keywords": [], | ||
"homepage": "https://azimutt.app", | ||
"author": { | ||
"name": "Anthony Ly", | ||
"email": "anthonyly.dev@gmail.com", | ||
"url": "https://anthonyly.dev" | ||
}, | ||
"license": "MIT", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/azimuttapp/azimutt.git", | ||
"directory": "libs/connector-oracle" | ||
}, | ||
"main": "./out/index.js", | ||
"types": "./out/index.d.ts", | ||
"scripts": { | ||
"test": "jest", | ||
"build": "rm -rf ./out && tsc", | ||
"build:docker": "npx tsc", | ||
"dry-publish": "pnpm run build && pnpm test && pnpm pack" | ||
}, | ||
"dependencies": { | ||
"@azimutt/models": "workspace:^", | ||
"@azimutt/utils": "workspace:^", | ||
"oracledb": "^6.5.1" | ||
}, | ||
"devDependencies": { | ||
"@jest/globals": "^29.7.0", | ||
"@types/jest": "^29.5.12", | ||
"@types/node": "^20.14.5", | ||
"@types/oracledb": "^6.5.1", | ||
"jest": "^29.7.0", | ||
"ts-jest": "^29.1.3", | ||
"typescript": "^5.4.5" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { describe, test } from "@jest/globals" | ||
import { parseDatabaseUrl } from "@azimutt/models" | ||
import { connect } from "./connect" | ||
import { execQuery } from "./query" | ||
import { application, logger } from "./constants.test" | ||
|
||
// Use this test to troubleshoot database connection errors. | ||
// If you don't succeed with the first one (Azimutt `connect`), try with the second one (raw node lib) and once you found a way, tell us how to fix ;) | ||
// Of course, you can contact us (issues or contact@azimutt.app) to do it together. | ||
// More documentation available at: https://azimutt.notion.site/Database-connection-troubleshooting-c4c19ed28c7040ef9aaaeec96ce6ba8d | ||
describe("connect", () => { | ||
// TODO 1: replace this with your own connection string, but don't commit it! | ||
const url = "jdbc:oracle:thin:sys/oracle@//localhost:1521/FREE" | ||
|
||
// TODO 2: write a valid query for your database | ||
const query = "SELECT * FROM C##AZIMUTT.USERS" | ||
const parameters: any[] = [] | ||
|
||
// TODO 3: unskip this test first and run it (`npm run test -- src/connect.test.ts`) | ||
test.skip("Azimutt should connect", async () => { | ||
const parsedUrl = parseDatabaseUrl(url) | ||
const results = await connect( | ||
application, | ||
parsedUrl, | ||
execQuery(query, parameters), | ||
{ logger, logQueries: true } | ||
) | ||
console.log("results", results) | ||
}) | ||
Comment on lines
+7
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tip Codebase Verification Clarify TODO comments and consider enabling the test. The test in
Additionally, evaluate whether this test can be enabled by default with environmental checks.
Analysis chainClarify TODO comments and consider enabling the test. The test is skipped and contains several TODO comments. These are helpful but could be more specific in guiding the user on what needs to be done. Consider providing more detailed instructions or examples. Also, evaluate whether this test can be enabled by default with environmental checks. Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: Check for guidance on enabling tests and handling TODOs.
# Test: Search for TODO comments and their handling in the codebase.
rg "TODO" --type typescript
Length of output: 62 Script: #!/bin/bash
# Description: Check for guidance on enabling tests and handling TODOs.
# Test: Search for TODO comments and their handling in TypeScript files.
rg "TODO" --glob "*.ts"
Length of output: 15781 |
||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import { | ||
Connection, | ||
ConnectionAttributes, | ||
getConnection, | ||
SYSDBA, | ||
} from "oracledb" | ||
import { AnyError, errorToString } from "@azimutt/utils" | ||
import { | ||
AttributeValue, | ||
ConnectorDefaultOpts, | ||
DatabaseUrlParsed, | ||
logQueryIfNeeded, | ||
queryError, | ||
} from "@azimutt/models" | ||
|
||
export async function connect<T>( | ||
application: string, | ||
url: DatabaseUrlParsed, | ||
exec: (c: Conn) => Promise<T>, | ||
opts: ConnectorDefaultOpts | ||
): Promise<T> { | ||
const client = await createConnection(buildConfig(application, url)).catch( | ||
(err) => Promise.reject(connectionError(err)) | ||
) | ||
let queryCpt = 1 | ||
const conn: Conn = { | ||
url, | ||
query<T extends QueryResultRow>( | ||
sql: string, | ||
parameters: [] = [], | ||
name?: string | ||
): Promise<T[]> { | ||
return logQueryIfNeeded( | ||
queryCpt++, | ||
name, | ||
sql, | ||
parameters, | ||
(sql, parameters) => { | ||
return client.execute<T>(sql, parameters).then( | ||
(res) => res.rows ?? [], | ||
(err) => Promise.reject(queryError(name, sql, err)) | ||
) | ||
}, | ||
(r) => r?.length ?? 0, | ||
opts | ||
) | ||
}, | ||
queryArrayMode( | ||
sql: string, | ||
parameters: any[] = [], | ||
name?: string | ||
): Promise<QueryResultArrayMode> { | ||
return logQueryIfNeeded( | ||
queryCpt++, | ||
name, | ||
sql, | ||
parameters, | ||
(sql, parameters) => { | ||
return client.execute(sql, parameters).then( | ||
(res) => { | ||
const { metaData, rows } = res | ||
const fields = metaData?.map((meta) => ({ | ||
name: meta.name, | ||
})) | ||
return { fields: fields ?? [], rows: (rows as any[]) ?? [] } | ||
}, | ||
(err) => Promise.reject(queryError(name, sql, err)) | ||
) | ||
}, | ||
(r) => r.rows.length, | ||
opts | ||
) | ||
}, | ||
} | ||
return exec(conn).then( | ||
(res) => client.close().then((_) => res), | ||
(err) => client.close().then((_) => Promise.reject(err)) | ||
) | ||
} | ||
|
||
export interface Conn { | ||
url: DatabaseUrlParsed | ||
|
||
query<T extends QueryResultRow>( | ||
sql: string, | ||
parameters?: any[], | ||
name?: string | ||
): Promise<T[]> | ||
|
||
queryArrayMode( | ||
sql: string, | ||
parameters?: any[], | ||
name?: string | ||
): Promise<QueryResultArrayMode> | ||
} | ||
|
||
export type QueryResultValue = AttributeValue | ||
export type QueryResultRow = QueryResultValue[] | ||
export type QueryResultField = { | ||
name: string | ||
} | ||
export type QueryResultRowArray = QueryResultValue[] | ||
export type QueryResultArrayMode = { | ||
fields: QueryResultField[] | ||
rows: QueryResultRowArray[] | ||
} | ||
|
||
async function createConnection( | ||
config: ConnectionAttributes | ||
): Promise<Connection> { | ||
const client = await getConnection(config) | ||
return client | ||
} | ||
|
||
function buildConfig( | ||
application: string, | ||
url: DatabaseUrlParsed | ||
): ConnectionAttributes { | ||
return { | ||
connectionString: `${url.host}:${url.port}/${url.db}`, | ||
user: url.user, | ||
password: url.pass || undefined, | ||
privilege: SYSDBA, | ||
} | ||
} | ||
|
||
function connectionError(err: AnyError): AnyError { | ||
const msg = errorToString(err) | ||
return err | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,14 @@ | ||||||||||||||||||||||||||||||
import {expect, test} from "@jest/globals"; | ||||||||||||||||||||||||||||||
import {Logger} from "@azimutt/utils"; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
export const logger: Logger = { | ||||||||||||||||||||||||||||||
debug: (text: string): void => console.debug(text), | ||||||||||||||||||||||||||||||
log: (text: string): void => console.log(text), | ||||||||||||||||||||||||||||||
warn: (text: string): void => console.warn(text), | ||||||||||||||||||||||||||||||
error: (text: string): void => console.error(text) | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
export const application = 'azimutt-tests' | ||||||||||||||||||||||||||||||
Comment on lines
+4
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove exports from test file Exporting from test files can lead to unintended dependencies and should be avoided. Consider using a common setup file for shared test utilities instead. - export const logger = {
- debug: (text: string): void => console.debug(text),
- log: (text: string): void => console.log(text),
- warn: (text: string): void => console.warn(text),
- error: (text: string): void => console.error(text)
- }
- export const application = 'azimutt-tests' Committable suggestion
Suggested change
ToolsBiome
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
test('dummy', () => { | ||||||||||||||||||||||||||||||
expect(application).toEqual('azimutt-tests') | ||||||||||||||||||||||||||||||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { describe, expect, test } from "@jest/globals" | ||
import { buildSqlColumn, buildSqlTable } from "./helpers" | ||
|
||
describe("helpers", () => { | ||
test("buildSqlTable", () => { | ||
expect(buildSqlTable({ entity: "events" })).toEqual(`"events"`) | ||
expect(buildSqlTable({ schema: "", entity: "events" })).toEqual(`"events"`) | ||
expect(buildSqlTable({ schema: "public", entity: "events" })).toEqual( | ||
`"public"."events"` | ||
) | ||
}) | ||
test("buildSqlColumn", () => { | ||
expect(buildSqlColumn(["name"])).toEqual(`"name"`) | ||
expect(buildSqlColumn(["data", "email"])).toEqual(`"data"->'email'`) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { AttributePath, EntityRef, SqlFragment } from "@azimutt/models" | ||
|
||
export function buildSqlTable(ref: EntityRef): SqlFragment { | ||
const sqlSchema = ref.schema ? `"${ref.schema}".` : "" | ||
return `${sqlSchema}"${ref.entity}"` | ||
} | ||
|
||
export function buildSqlColumn(path: AttributePath): SqlFragment { | ||
const [head, ...tail] = path | ||
return `"${head}"${tail.map((t) => `->'${t}'`).join("")}` | ||
Comment on lines
+8
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tip Codebase Verification Potential SQL Injection Risk in The
Please ensure that Analysis chainReview of This function handles the building of SQL column references, including nested JSON attributes using the Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: Verify that AttributePath values are sanitized before being passed to buildSqlColumn.
# Test: Search for usages of buildSqlColumn and check for input sanitization.
rg --type typescript $'buildSqlColumn'
Length of output: 72 Script: #!/bin/bash
# Description: Verify that AttributePath values are sanitized before being passed to buildSqlColumn.
# Test: Search for usages of buildSqlColumn and check for input sanitization.
rg 'buildSqlColumn' --glob '**/*.ts' --glob '**/*.tsx'
Length of output: 5785 Script: #!/bin/bash
# Description: Extract lines around the calls to buildSqlColumn to check for input sanitization.
# Extract lines around the calls to buildSqlColumn in relevant files.
rg 'buildSqlColumn' --glob '**/*.ts' --glob '**/*.tsx' -A 3 -B 3
Length of output: 27106 |
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Grammar Improvement: Adjust phrasing for clarity.
Consider revising "This library allows to connect to Oracle" to "This library allows you to connect to Oracle" for grammatical correctness.
Committable suggestion
Tools
LanguageTool