First pass end-to-end working
This commit is contained in:
parent
2caa530ff0
commit
0cedd1d57b
|
@ -3,10 +3,14 @@ import {
|
||||||
DatasourceFieldTypes,
|
DatasourceFieldTypes,
|
||||||
QueryTypes,
|
QueryTypes,
|
||||||
SqlQuery,
|
SqlQuery,
|
||||||
|
QueryJson
|
||||||
} from "../definitions/datasource"
|
} from "../definitions/datasource"
|
||||||
import { getSqlQuery } from "./utils"
|
import { finaliseExternalTables, getSqlQuery, buildExternalTableId, convertType } from "./utils"
|
||||||
import oracledb, { ExecuteOptions, Result, Connection, ConnectionAttributes } from "oracledb"
|
import oracledb, { ExecuteOptions, Result, Connection, ConnectionAttributes, BindParameters } from "oracledb"
|
||||||
import Sql from "./base/sql"
|
import Sql from "./base/sql"
|
||||||
|
import { Table } from "../definitions/common"
|
||||||
|
import { DatasourcePlus } from "./base/datasourcePlus"
|
||||||
|
import { FieldTypes } from "../constants"
|
||||||
|
|
||||||
module OracleModule {
|
module OracleModule {
|
||||||
|
|
||||||
|
@ -22,6 +26,7 @@ module OracleModule {
|
||||||
|
|
||||||
const SCHEMA: Integration = {
|
const SCHEMA: Integration = {
|
||||||
docs: "https://github.com/oracle/node-oracledb",
|
docs: "https://github.com/oracle/node-oracledb",
|
||||||
|
plus: true,
|
||||||
friendlyName: "Oracle",
|
friendlyName: "Oracle",
|
||||||
description: "Oracle Database is an object-relational database management system developed by Oracle Corporation",
|
description: "Oracle Database is an object-relational database management system developed by Oracle Corporation",
|
||||||
datasource: {
|
datasource: {
|
||||||
|
@ -63,22 +68,237 @@ module OracleModule {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
class OracleIntegration extends Sql {
|
|
||||||
|
const TYPE_MAP = {
|
||||||
|
text: FieldTypes.LONGFORM,
|
||||||
|
blob: FieldTypes.LONGFORM,
|
||||||
|
enum: FieldTypes.STRING,
|
||||||
|
varchar: FieldTypes.STRING,
|
||||||
|
float: FieldTypes.NUMBER,
|
||||||
|
int: FieldTypes.NUMBER,
|
||||||
|
numeric: FieldTypes.NUMBER,
|
||||||
|
bigint: FieldTypes.NUMBER,
|
||||||
|
mediumint: FieldTypes.NUMBER,
|
||||||
|
decimal: FieldTypes.NUMBER,
|
||||||
|
dec: FieldTypes.NUMBER,
|
||||||
|
double: FieldTypes.NUMBER,
|
||||||
|
real: FieldTypes.NUMBER,
|
||||||
|
fixed: FieldTypes.NUMBER,
|
||||||
|
smallint: FieldTypes.NUMBER,
|
||||||
|
timestamp: FieldTypes.DATETIME,
|
||||||
|
date: FieldTypes.DATETIME,
|
||||||
|
datetime: FieldTypes.DATETIME,
|
||||||
|
time: FieldTypes.DATETIME,
|
||||||
|
tinyint: FieldTypes.BOOLEAN,
|
||||||
|
json: DatasourceFieldTypes.JSON,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raw query response
|
||||||
|
*/
|
||||||
|
interface ColumnsResponse {
|
||||||
|
TABLE_NAME: string
|
||||||
|
COLUMN_NAME: string
|
||||||
|
DATA_TYPE: string
|
||||||
|
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
|
||||||
|
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
|
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.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) {
|
constructor(config: OracleConfig) {
|
||||||
super("oracle")
|
super("oracledb")
|
||||||
this.config = config
|
this.config = config
|
||||||
}
|
}
|
||||||
|
|
||||||
private query = async (query: SqlQuery): Promise<Result<any>> => {
|
/**
|
||||||
|
* 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 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,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
// 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: false,
|
||||||
|
name: columnName,
|
||||||
|
type: convertType(oracleColumn.type, TYPE_MAP),
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async internalQuery<T>(query: SqlQuery): Promise<Result<T>> {
|
||||||
let connection
|
let connection
|
||||||
try {
|
try {
|
||||||
connection = await this.getConnection()
|
connection = await this.getConnection()
|
||||||
|
|
||||||
const options : ExecuteOptions = { autoCommit: true }
|
const options: ExecuteOptions = { autoCommit: true }
|
||||||
const result: Result<any> = await connection.execute(query.sql, [], options)
|
const bindings: BindParameters = query.bindings || []
|
||||||
|
const result: Result<T> = await connection.execute<T>(query.sql, bindings, options)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -104,24 +324,39 @@ module OracleModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(query: SqlQuery | string) {
|
async create(query: SqlQuery | string) {
|
||||||
const response = await this.query(getSqlQuery(query))
|
const response = await this.internalQuery(getSqlQuery(query))
|
||||||
return response.rows && response.rows.length ? response.rows : [{ created: true }]
|
return response.rows && response.rows.length ? response.rows : [{ created: true }]
|
||||||
}
|
}
|
||||||
|
|
||||||
async read(query: SqlQuery | string) {
|
async read(query: SqlQuery | string) {
|
||||||
const response = await this.query(getSqlQuery(query))
|
const response = await this.internalQuery(getSqlQuery(query))
|
||||||
return response.rows
|
return response.rows
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(query: SqlQuery | string) {
|
async update(query: SqlQuery | string) {
|
||||||
const response = await this.query(getSqlQuery(query))
|
const response = await this.internalQuery(getSqlQuery(query))
|
||||||
return response.rows && response.rows.length ? response.rows : [{ updated: true }]
|
return response.rows && response.rows.length ? response.rows : [{ updated: true }]
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(query: SqlQuery | string) {
|
async delete(query: SqlQuery | string) {
|
||||||
const response = await this.query(getSqlQuery(query))
|
const response = await this.internalQuery(getSqlQuery(query))
|
||||||
return response.rows && response.rows.length ? response.rows : [{ deleted: true }]
|
return response.rows && response.rows.length ? response.rows : [{ deleted: true }]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async query(json: QueryJson) {
|
||||||
|
const operation = this._operation(json).toLowerCase()
|
||||||
|
const input = this._query(json)
|
||||||
|
if (Array.isArray(input)) {
|
||||||
|
const responses = []
|
||||||
|
for (let query of input) {
|
||||||
|
responses.push(await this.internalQuery(query))
|
||||||
|
}
|
||||||
|
return responses
|
||||||
|
} else {
|
||||||
|
const response = await this.internalQuery(input)
|
||||||
|
return response.rows && response.rows.length ? response.rows : [{ [operation]: true }]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
@ -125,7 +125,7 @@ function copyExistingPropsOver(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function finaliseExternalTables(tables: { [key: string]: any }, entities: { [key: string]: any }) {
|
export function finaliseExternalTables(tables: { [key: string]: any }, entities: { [key: string]: any }) {
|
||||||
const finalTables: { [key: string]: any } = {}
|
let finalTables: { [key: string]: any } = {}
|
||||||
const errors: { [key: string]: string } = {}
|
const errors: { [key: string]: string } = {}
|
||||||
for (let [name, table] of Object.entries(tables)) {
|
for (let [name, table] of Object.entries(tables)) {
|
||||||
// make sure every table has a key
|
// make sure every table has a key
|
||||||
|
@ -136,5 +136,9 @@ export function finaliseExternalTables(tables: { [key: string]: any }, entities:
|
||||||
// make sure all previous props have been added back
|
// make sure all previous props have been added back
|
||||||
finalTables[name] = copyExistingPropsOver(name, table, entities)
|
finalTables[name] = copyExistingPropsOver(name, table, entities)
|
||||||
}
|
}
|
||||||
|
// sort the tables by name
|
||||||
|
finalTables = Object.entries(finalTables)
|
||||||
|
.sort(([a,],[b,]) => a.localeCompare(b))
|
||||||
|
.reduce((r, [k, v]) => ({ ...r, [k]: v }), {});
|
||||||
return { tables: finalTables, errors }
|
return { tables: finalTables, errors }
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue