hana-cli
Version:
HANA Developer Command Line Interface
795 lines (730 loc) • 27.9 kB
JavaScript
/*eslint no-console: 0, no-unused-vars: 0, no-shadow: 0, new-cap: 0*/
// @ts-check
/**
* @module dbInspect - Database Object Dynamic Inspection and Metadata processing
*/
import * as base from "./base.js"
const bundle = base.bundle
// Version cache to avoid repeated database queries
let cachedVersion = null
// Calculation view cache keyed by schema and view id
let cachedCalculationView = new Map()
export function resetHANAVersionCache() {
cachedVersion = null
}
/**
* Return the HANA DB Version
* @param {object} db - Database Connection
* @returns {Promise<object>}
*/
export async function getHANAVersion(db, options = {}) {
base.debug(bundle.getText("debug.call", ["getHANAVersion"]))
// Return cached version if available
const { forceRefresh = false } = options
if (cachedVersion && !forceRefresh) {
return cachedVersion
}
const statement = await db.preparePromisified(
`SELECT *
FROM M_DATABASE`)
const object = await db.statementExecPromisified(statement, [])
if (object.length < 1) {
throw new Error(bundle.getText("errMDB"))
}
object[0].versionMajor = object[0].VERSION.charAt(0)
cachedVersion = object[0]
base.debug(bundle.getText("debug.hanaVersion", [JSON.stringify(object)]))
return cachedVersion
}
/**
* Check if a view is a Calculation View
* @param {object} db - Database Connection
* @param {string} schema - Schema
* @param {string} viewId - View Unique ID
* @returns {Promise<boolean>}
*/
export async function isCalculationView(db, schema, viewId, options = {}) {
base.debug(bundle.getText("debug.callWithParams", ["isCalculationView", `${schema} ${viewId}`]))
const cacheKey = `${schema}::${viewId}`
const { forceRefresh = false } = options
if (!forceRefresh && cachedCalculationView.has(cacheKey)) {
return cachedCalculationView.get(cacheKey)
}
const vers = await getHANAVersion(db, options)
if (vers.versionMajor < 2) {
cachedCalculationView.set(cacheKey, false)
return false
}
// Try lookup by QUALIFIED_NAME first, then fallback to VIEW_NAME
const qualifiedStatementString = `SELECT CUBE_ID, SCHEMA_NAME, QUALIFIED_NAME, VIEW_NAME, CUBE_TYPE, IS_HDI_OBJECT
FROM _SYS_BI.BIMC_REPORTABLE_VIEWS
WHERE SCHEMA_NAME LIKE ?
AND QUALIFIED_NAME = ?`
const qualifiedStatement = await db.preparePromisified(qualifiedStatementString)
let object = await db.statementExecPromisified(qualifiedStatement, [schema, viewId])
if (object.length < 1) {
const viewNameStatementString = `SELECT CUBE_ID, SCHEMA_NAME, QUALIFIED_NAME, VIEW_NAME, CUBE_TYPE, IS_HDI_OBJECT
FROM _SYS_BI.BIMC_REPORTABLE_VIEWS
WHERE SCHEMA_NAME LIKE ?
AND VIEW_NAME = ?`
const viewNameStatement = await db.preparePromisified(viewNameStatementString)
object = await db.statementExecPromisified(viewNameStatement, [schema, viewId])
}
const isCalcView = object.length >= 1
cachedCalculationView.set(cacheKey, isCalcView)
return isCalcView
}
/**
* Get DB View details
* @param {object} db - Database Connection
* @param {string} scheam - Schema
* @param {string} viewId - View Unique ID
* @returns {Promise<object>}
*/
export async function getView(db, scheam, viewId) {
base.debug(bundle.getText("debug.callWithParams", ["getView", `${scheam} ${viewId}`]))
//Select View
let statementString = ``
const vers = await getHANAVersion(db)
if (vers.versionMajor < 2) {
statementString = `SELECT SCHEMA_NAME, VIEW_NAME, VIEW_OID, COMMENTS, IS_COLUMN_VIEW, VIEW_TYPE, HAS_STRUCTURED_PRIVILEGE_CHECK, HAS_CACHE
FROM VIEWS
WHERE SCHEMA_NAME LIKE ?
AND VIEW_NAME = ?`
} else {
statementString = `SELECT SCHEMA_NAME, VIEW_NAME, VIEW_OID, COMMENTS, IS_COLUMN_VIEW, VIEW_TYPE, HAS_STRUCTURED_PRIVILEGE_CHECK, HAS_PARAMETERS, HAS_CACHE, CREATE_TIME
FROM VIEWS
WHERE SCHEMA_NAME LIKE ?
AND VIEW_NAME = ?`
}
const statement = await db.preparePromisified(statementString)
const object = await db.statementExecPromisified(statement, [scheam, viewId])
if (object.length < 1) {
throw new Error(bundle.getText("errInput"))
}
return object
}
/**
* Get DB Object Definition
* @param {object} db - Database Connection
* @param {string} schema - Schema
* @param {*} Id - Object ID
* @returns {Promise<string>}
*/
export async function getDef(db, schema, Id) {
base.debug(bundle.getText("debug.callWithParams", ["getDef", `${schema} ${Id}`]))
//Select View
const inputParams = {
SCHEMA: `"${schema}"`,
OBJECT: `"${Id}"`
}
const sp = await db.loadProcedurePromisified("SYS", "GET_OBJECT_DEFINITION")
const object = await db.callProcedurePromisified(sp, inputParams)
if (object.length < 1) {
throw new Error(bundle.getText("errObj"))
}
const output = object.results[0].OBJECT_CREATION_STATEMENT.toString().replace(/,/g, ",\n")
return output
}
/**
* Get View Fields and Metadata
* @param {object} db - Database Connection
* @param {string} schema - Schema
* @param {string} viewId - View Unique ID
* @param {string} viewOid - View Unique ID
* @returns {Promise<object>}
*/
export async function getCalcViewFields(db, schema, viewId, viewOid) {
base.debug(bundle.getText("debug.callWithParams", ["getCalcViewFields", `${schema} ${viewId}`]))
//Select Fields
const statement = await db.preparePromisified(
`SELECT SCHEMA_NAME, QUALIFIED_NAME AS VIEW_NAME, NULL AS VIEW_OID, COLUMN_NAME,
"ORDER" AS POSITION, DESC_TYPE_D AS DATA_TYPE_NAME, 0 AS OFFSET, 0 AS LENGTH, SCALE,
IS_NULLABLE, NULL AS DEFAULT_VALUE, NULL AS COLUMN_ID, COLUMN_CAPTION AS COMMENTS, KEY_COLUMN_NAME
FROM _SYS_BI.BIMC_DIMENSION_VIEW
WHERE SCHEMA_NAME LIKE ?
AND QUALIFIED_NAME = ? ORDER BY POSITION`)
let fields = await db.statementExecPromisified(statement, [schema, viewId])
if (fields.length < 1) {
const statementString = `SELECT CUBE_ID, SCHEMA_NAME, QUALIFIED_NAME, VIEW_NAME, CUBE_TYPE, IS_HDI_OBJECT
FROM _SYS_BI.BIMC_REPORTABLE_VIEWS
WHERE SCHEMA_NAME LIKE ?
AND VIEW_NAME = ?`
const statementLookup = await db.preparePromisified(statementString)
const object = await db.statementExecPromisified(statementLookup, [schema, viewId])
if (object.length >= 1) {
const lookupStatement = await db.preparePromisified(
`SELECT SCHEMA_NAME, QUALIFIED_NAME AS VIEW_NAME, NULL AS VIEW_OID, COLUMN_NAME,
"ORDER" AS POSITION, DESC_TYPE_D AS DATA_TYPE_NAME, 0 AS OFFSET, 0 AS LENGTH, SCALE,
IS_NULLABLE, NULL AS DEFAULT_VALUE, NULL AS COLUMN_ID, COLUMN_CAPTION AS COMMENTS, KEY_COLUMN_NAME
FROM _SYS_BI.BIMC_DIMENSION_VIEW
WHERE SCHEMA_NAME LIKE ?
AND QUALIFIED_NAME = ? ORDER BY POSITION`)
fields = await db.statementExecPromisified(lookupStatement, [schema, object[0].QUALIFIED_NAME])
}
}
// Batch lookup all field details in single query instead of N+1 queries
if (fields.length > 0) {
const columnNames = fields.map(f => `'${f.COLUMN_NAME}'`).join(', ')
const batchStatement = await db.preparePromisified(
`SELECT COLUMN_NAME, OFFSET, LENGTH, DEFAULT_VALUE, DATA_TYPE_NAME
FROM VIEW_COLUMNS
WHERE VIEW_OID = ? AND COLUMN_NAME IN (${columnNames})`)
const sqlFields = await db.statementExecPromisified(batchStatement, [viewOid])
// Create lookup map for O(1) access instead of array search
const sqlFieldMap = new Map(sqlFields.map(f => [f.COLUMN_NAME, f]))
for (let field of fields) {
const sqlField = sqlFieldMap.get(field.COLUMN_NAME)
if (sqlField) {
field.OFFSET = sqlField.OFFSET
field.LENGTH = sqlField.LENGTH
field.DEFAULT_VALUE = sqlField.DEFAULT_VALUE
field.DATA_TYPE_NAME = sqlField.DATA_TYPE_NAME
}
}
}
return fields
}
/**
* Get View Fields and Metadata
* @param {object} db - Database Connection
* @param {string} viewOid - View Unique ID
* @returns {Promise<object>}
*/
export async function getViewFields(db, viewOid) {
base.debug(bundle.getText("debug.callWithParams", ["getViewFields", viewOid]))
//Select Fields
const statement = await db.preparePromisified(
`SELECT SCHEMA_NAME, VIEW_NAME, VIEW_OID, COLUMN_NAME, POSITION, DATA_TYPE_NAME, OFFSET, LENGTH, SCALE, IS_NULLABLE, DEFAULT_VALUE, COLUMN_ID, COMMENTS, NULL as KEY_COLUMN_NAME
FROM VIEW_COLUMNS
WHERE VIEW_OID = ? ORDER BY POSITION`)
const fields = await db.statementExecPromisified(statement, [viewOid])
return fields
}
/**
* Get View Parameters and Metadata
* @param {object} db - Database Connection
* @param {string} schema - Schema
* @param {string} viewId - View Unique ID
* @param {string} viewOid - View Unique ID
* @returns {Promise<object>}
*/
export async function getCalcViewParameters(db, schema, viewId, viewOid) {
base.debug(bundle.getText("debug.callWithParams", ["getCalcViewParameters", `${schema} ${viewId}`]))
//Select Fields
const statement = await db.preparePromisified(
`SELECT SCHEMA_NAME, QUALIFIED_NAME as "VIEW_NAME", VARIABLE_NAME AS "PARAMETER_NAME",
COLUMN_TYPE, COLUMN_TYPE_D as "DATA_TYPE_NAME",
COLUMN_SQL_TYPE, VALUE_TYPE, MANDATORY, DESCRIPTION,
PLACEHOLDER_NAME, DEFAULT_VALUE, SCHEMA_NAME, QUALIFIED_NAME, IS_INPUT_ENABLED,
VARIABLE_TYPE, VARIABLE_SUB_TYPE
FROM _SYS_BI.BIMC_VARIABLE_VIEW
WHERE SCHEMA_NAME LIKE ?
AND QUALIFIED_NAME = ?`)
let parameters = await db.statementExecPromisified(statement, [schema, viewId])
for (let parameter of parameters) {
// Use regex to extract length from type (more reliable than split)
const lengthMatch = parameter.COLUMN_SQL_TYPE.match(/\(([^)]+)\)/)
parameter.LENGTH = lengthMatch ? lengthMatch[1] : 0
}
return parameters
}
/**
* Get View Parameters and Metadata
* @param {object} db - Database Connection
* @param {string} viewOid - View Unique ID
* @returns {Promise<object>}
*/
export async function getViewParameters(db, viewOid) {
base.debug(bundle.getText("debug.callWithParams", ["getViewParameters", viewOid]))
//Select Fields
const statement = await db.preparePromisified(
`SELECT SCHEMA_NAME, VIEW_NAME, VIEW_OID, PARAMETER_NAME, DATA_TYPE_ID, DATA_TYPE_NAME,
LENGTH, SCALE, POSITION, HAS_DEFAULT_VALUE
FROM VIEW_PARAMETERS
WHERE VIEW_OID = ?`)
let parameters = await db.statementExecPromisified(statement, [viewOid])
return parameters
}
/**
* Get DB Table Details
* @param {object} db - Database Connection
* @param {string} schema - Schema
* @param {string} tableId - Table Unqiue ID
* @returns {Promise<object>}
*/
export async function getTable(db, schema, tableId) {
base.debug(bundle.getText("debug.callWithParams", ["getTable", `${schema} ${tableId}`]))
//Select Table
let statementString = ``
const vers = await getHANAVersion(db)
if (vers.versionMajor < 2) {
statementString = `SELECT SCHEMA_NAME, TABLE_NAME, TABLE_OID, TABLE_TYPE, HAS_PRIMARY_KEY, UNLOAD_PRIORITY, IS_PRELOAD
FROM TABLES
WHERE SCHEMA_NAME LIKE ?
AND TABLE_NAME = ?`
} else {
statementString = `SELECT SCHEMA_NAME, TABLE_NAME, TABLE_OID, TABLE_TYPE, HAS_PRIMARY_KEY, UNLOAD_PRIORITY, IS_PRELOAD, CREATE_TIME
FROM TABLES
WHERE SCHEMA_NAME LIKE ?
AND TABLE_NAME = ?`
}
const statement = await db.preparePromisified(statementString)
const object = await db.statementExecPromisified(statement, [schema, tableId])
if (object.length < 1) {
throw new Error(bundle.getText("errTable"))
}
return object
}
/**
* Get Table Fields and Metadata
* @param {object} db - Database Connection
* @param {string} tableOid - Table Unique ID
* @returns {Promise<object>}
*/
export async function getTableFields(db, tableOid) {
base.debug(bundle.getText("debug.callWithParams", ["getTableFields", tableOid]))
//Select Fields
let statementString = ``
const vers = await getHANAVersion(db)
if (vers.versionMajor < 2) {
statementString =
`SELECT SCHEMA_NAME, TABLE_NAME, TABLE_OID, COLUMN_NAME, POSITION, DATA_TYPE_NAME, OFFSET, LENGTH, SCALE, IS_NULLABLE, DEFAULT_VALUE, COLUMN_ID, COMMENTS
FROM TABLE_COLUMNS
WHERE TABLE_OID = ? ORDER BY POSITION`
} else {
statementString =
`SELECT SCHEMA_NAME, TABLE_NAME, TABLE_OID, COLUMN_NAME, POSITION, DATA_TYPE_NAME, OFFSET, LENGTH, SCALE, IS_NULLABLE, DEFAULT_VALUE, COLUMN_ID, COMMENTS
FROM TABLE_COLUMNS
WHERE TABLE_OID = ? ORDER BY POSITION`
}
const statement = await db.preparePromisified(statementString)
const fields = await db.statementExecPromisified(statement, [tableOid])
return fields
}
/**
* Get Table Constraints
* @typedef {{SCHEMA_NAME: string, TABLE_NAME: string}} objType
* @param {object} db - Database Connection
* @param {Array<objType>} object
* @returns
*/
export async function getConstraints(db, object) {
base.debug(bundle.getText("debug.callWithParams", ["getConstraints", JSON.stringify(object)]))
//Select Constraints
const statement = await db.preparePromisified(
`SELECT * from CONSTRAINTS
WHERE SCHEMA_NAME LIKE ?
AND TABLE_NAME = ?
AND IS_PRIMARY_KEY = ?
ORDER BY POSITION `
);
const constraints = await db.statementExecPromisified(statement, [object[0].SCHEMA_NAME, object[0].TABLE_NAME, "TRUE"])
return constraints
}
/**
* Get Stored Procedure Details
* @param {object} db - Database Connection
* @param {string} schema - Schema
* @param {string} procedure - Procedure name
* @returns {Promise<object>}
*/
export async function getProcedure(db, schema, procedure) {
base.debug(bundle.getText("debug.callWithParams", ["getProcedure", `${schema} ${procedure}`]))
//Select View
let statementString = ``
const vers = await getHANAVersion(db)
if (vers.versionMajor < 2) {
statementString = `SELECT SCHEMA_NAME, PROCEDURE_NAME, PROCEDURE_OID, SQL_SECURITY, DEFAULT_SCHEMA_NAME,
INPUT_PARAMETER_COUNT, OUTPUT_PARAMETER_COUNT, INOUT_PARAMETER_COUNT, RESULT_SET_COUNT,
PROCEDURE_TYPE, READ_ONLY, IS_VALID, IS_HEADER_ONLY, OWNER_NAME
FROM PROCEDURES
WHERE SCHEMA_NAME LIKE ?
AND PROCEDURE_NAME = ?`
} else {
statementString = `SELECT SCHEMA_NAME, PROCEDURE_NAME, PROCEDURE_OID, SQL_SECURITY, DEFAULT_SCHEMA_NAME,
INPUT_PARAMETER_COUNT, OUTPUT_PARAMETER_COUNT, INOUT_PARAMETER_COUNT, RESULT_SET_COUNT,
IS_ENCRYPTED, PROCEDURE_TYPE, READ_ONLY, IS_VALID, IS_HEADER_ONLY, OWNER_NAME, CREATE_TIME
FROM PROCEDURES
WHERE SCHEMA_NAME LIKE ?
AND PROCEDURE_NAME = ?`
}
const statement = await db.preparePromisified(statementString)
const object = await db.statementExecPromisified(statement, [schema, procedure])
if (object.length < 1) {
throw new Error(bundle.getText("errProc"))
}
return object
}
/**
* Get Procedure Parameters
* @param {object} db - Database Connection
* @param {string} procOid - Procedure unique ID
* @returns {Promise<object>}
*/
export async function getProcedurePrams(db, procOid) {
base.debug(bundle.getText("debug.callWithParams", ["getProcedurePrams", procOid]))
//Select Fields
const statement = await db.preparePromisified(
`SELECT PARAMETER_NAME, DATA_TYPE_NAME, LENGTH, SCALE, POSITION, TABLE_TYPE_NAME, PARAMETER_TYPE, HAS_DEFAULT_VALUE, IS_NULLABLE
FROM PROCEDURE_PARAMETERS
WHERE PROCEDURE_OID = ?
ORDER BY POSITION`)
const fields = await db.statementExecPromisified(statement, [procOid])
return fields
}
export async function getProcedurePramCols(db, procOid) {
base.debug(bundle.getText("debug.callWithParams", ["getProcedurePramCols", procOid]))
//Select Fields
const statement = await db.preparePromisified(
`SELECT PARAMETER_NAME, PARAMETER_POSITION, COLUMN_NAME, POSITION, DATA_TYPE_NAME, LENGTH, SCALE, IS_NULLABLE
FROM PROCEDURE_PARAMETER_COLUMNS
WHERE PROCEDURE_OID = ?
ORDER BY PARAMETER_POSITION, POSITION`)
const fields = await db.statementExecPromisified(statement, [procOid])
return fields
}
/**
* Get Function details
* @param {object} db - Database Connection
* @param {string} schema - Schema
* @param {string} functionName - Function Name
* @returns {Promise<object>}
*/
export async function getFunction(db, schema, functionName) {
base.debug(bundle.getText("debug.callWithParams", ["getFunction", `${schema} ${functionName}`]))
//Select Functions
let statementString = ``
const vers = await getHANAVersion(db)
if (vers.versionMajor < 2) {
statementString = `SELECT SCHEMA_NAME, FUNCTION_NAME, FUNCTION_OID, SQL_SECURITY, DEFAULT_SCHEMA_NAME,
INPUT_PARAMETER_COUNT, RETURN_VALUE_COUNT,
FUNCTION_TYPE, FUNCTION_USAGE_TYPE, IS_VALID, IS_HEADER_ONLY, OWNER_NAME
FROM FUNCTIONS
WHERE SCHEMA_NAME LIKE ?
AND FUNCTION_NAME = ?`
} else {
statementString = `SELECT SCHEMA_NAME, FUNCTION_NAME, FUNCTION_OID, SQL_SECURITY, DEFAULT_SCHEMA_NAME,
INPUT_PARAMETER_COUNT, RETURN_VALUE_COUNT,
IS_ENCRYPTED, FUNCTION_TYPE, FUNCTION_USAGE_TYPE, IS_VALID, IS_HEADER_ONLY, OWNER_NAME, CREATE_TIME
FROM FUNCTIONS
WHERE SCHEMA_NAME LIKE ?
AND FUNCTION_NAME = ?`
}
const statement = await db.preparePromisified(statementString)
const object = await db.statementExecPromisified(statement, [schema, functionName])
if (object.length < 1) {
throw new Error(bundle.getText("errFunc"))
}
return object
}
/**
* Get Function Parameters
* @param {object} db - Database Connection
* @param {string} funcOid - Function Unique ID
* @returns {Promise<object>}
*/
export async function getFunctionPrams(db, funcOid) {
base.debug(bundle.getText("debug.callWithParams", ["getFunctionPrams", funcOid]))
//Select Fields
const statement = await db.preparePromisified(
`SELECT PARAMETER_NAME, DATA_TYPE_NAME, LENGTH, SCALE, POSITION, TABLE_TYPE_NAME, PARAMETER_TYPE, HAS_DEFAULT_VALUE, IS_NULLABLE
FROM FUNCTION_PARAMETERS
WHERE FUNCTION_OID = ?
ORDER BY POSITION`)
const fields = await db.statementExecPromisified(statement, [funcOid])
return fields
}
/**
* Get Function Parameter Columns
* @param {object} db - Database Connection
* @param {string} funcOid - Function Unique ID
* @returns {Promise<object>}
*/
export async function getFunctionPramCols(db, funcOid) {
base.debug(bundle.getText("debug.callWithParams", ["getFunctionPramCols", funcOid]))
//Select Fields
const statement = await db.preparePromisified(
`SELECT PARAMETER_NAME, PARAMETER_POSITION, COLUMN_NAME, POSITION, DATA_TYPE_NAME, LENGTH, SCALE, IS_NULLABLE
FROM FUNCTION_PARAMETER_COLUMNS
WHERE FUNCTION_OID = ?
ORDER BY PARAMETER_POSITION, POSITION`)
const fields = await db.statementExecPromisified(statement, [funcOid])
return fields
}
// Data-driven type mapping to eliminate ~300 lines of duplicated switch statements
const typeMap = {
// Common types for both useHanaTypes modes
NVARCHAR: (len) => `String(${len})`,
NCLOB: () => "LargeString",
VARBINARY: (len) => `Binary(${len})`,
BLOB: () => "LargeBinary",
INTEGER: () => "Integer",
BIGINT: () => "Integer64",
DECIMAL: (len, scale) => scale ? `Decimal(${len}, ${scale})` : `Decimal(${len})`,
DOUBLE: () => "Double",
DAYDATE: () => "Date",
DATE: () => "Date",
TIME: () => "Time",
SECONDDATE: () => "String",
TIMESTAMP: () => "Timestamp",
BOOLEAN: () => "Boolean",
REAL_VECTOR: () => "Vector",
// Standard mode only types
VARCHAR: (len) => `String(${len})`,
TINYINT: () => "UInt8",
SMALLINT: () => "Int16"
}
const hanaTypeMap = {
// HANA-specific types when useHanaTypes = true
SMALLINT: () => "hana.SMALLINT",
TINYINT: () => "hana.TINYINT",
SMALLDECIMAL: () => "hana.SMALLDECIMAL",
REAL: () => "hana.REAL",
CLOB: () => "hana.CLOB",
CHAR: (len) => `hana.CHAR(${len})`,
NCHAR: (len) => `hana.NCHAR(${len})`,
BINARY: (len) => `hana.BINARY(${len})`,
VARCHAR: (len) => `hana.VARCHAR(${len})`
}
/**
* Get CDS type string for a field, eliminating massive duplicate switch statements
* @param {string} dataType - HANA data type name
* @param {number} length - Type length parameter
* @param {number} scale - Type scale (for DECIMAL)
* @param {boolean} useHanaTypes - Whether to use HANA-specific type names
* @param {string|number|null} geoSrsId - SRS ID for geometry types
* @returns {string} CDS type definition
*/
function getTypeMapping(dataType, length, scale, useHanaTypes, geoSrsId) {
// Handle geometry types with SRS ID
if (geoSrsId !== null && geoSrsId !== undefined) {
return `hana.${dataType}(${geoSrsId})`
}
// Prefer HANA-specific mappings when enabled, but always fall back to shared/common mappings
const typeFunc = useHanaTypes
? (hanaTypeMap[dataType] || typeMap[dataType])
: typeMap[dataType]
if (typeFunc) {
return typeFunc(length, scale)
}
// Fallback for unsupported types
return `**UNSUPPORTED TYPE - ${dataType}`
}
export let options = {
useHanaTypes: false,
noColons: false,
keepPath: false,
useExists: true
}
let synonyms = new Map()
// @ts-ignore
/* options = {
set useHanaTypes(useHanaTypes) { options.useHanaTypes = useHanaTypes },
set noColons(noColons) { options.noColons = noColons },
set keepPath(keepPath) { options.keepPath = keepPath },
} */
export let results = {
get synonyms() { return Object.fromEntries(synonyms) }
}
/**
* Convert DB Object Metadata to CDS
* @param {object} db - Database Connection
* @param {object} object - DB Object Details
* @param {object} fields - Object Fields
* @param {object} constraints - Object Constraints
* @param {string} type - DB Object type
* @param {string} [schema] - Schema
* @param {string} [parent] - Calling context which impacts formatting
* @param {object} [parameters] - View Parameters
* @returns {Promise<string>}
*/
export async function formatCDS(db, object, fields, constraints, type, schema, parent, parameters) {
base.debug(bundle.getText("debug.callWithParams", ["formatCDS", type]))
let cdstable = ""
let originalName
switch (type) {
case "view":
case "hdbview":
originalName = object[0].VIEW_NAME
break
default:
originalName = object[0].TABLE_NAME
break
}
let newName = originalName
// if noColons option is used a.b.c::d.e will become a.b.c.d.e
if (parent === 'preview') {
options.noColons && (newName = newName.replace(/::/g, "_"))
} else {
options.noColons && (newName = newName.replace(/::/g, "."))
}
// if we keep path a.b.c::d.e will stay as is
// otherwise it will become a_b_c::d_e
options.keepPath || (newName = newName.replace(/\./g, "_"))
if ((type === "view" || type === "table") && (options.useExists)) {
cdstable += "@cds.persistence.exists \n"
if (await isCalculationView(db, schema, originalName)) {
cdstable += "@cds.persistence.calcview \n"
}
}
if (options.useQuoted){
newName && (cdstable += `Entity ![${newName}] `)
}else{
newName && (cdstable += `Entity ${newName} `)
}
if (parameters && parameters.length > 0){
cdstable += `(`
for (let parameter of parameters) {
cdstable += `${parameter.PARAMETER_NAME} : `
// Handle geo types that need SRS ID
const isGeo = (parameter.DATA_TYPE_NAME === "ST_POINT" || parameter.DATA_TYPE_NAME === "ST_GEOMETRY")
const geoSrsId = isGeo ? await getGeoColumns(db, object[0], parameter, type) : null
let typeStr = getTypeMapping(parameter.DATA_TYPE_NAME, parameter.LENGTH, parameter.SCALE, options.useHanaTypes, geoSrsId)
// Override TIMESTAMP for preview context
if (parameter.DATA_TYPE_NAME === "TIMESTAMP" && parent === 'preview') {
typeStr = "String"
}
cdstable += typeStr
cdstable += ", "
}
cdstable = cdstable.slice(0, -2)
cdstable += `)`
}
//closing entity
cdstable += `{\n`
// if modified real table names will be stored in synonyms
if (newName !== originalName) {
synonyms.set(newName, {
target: { object: originalName, schema: object[0].SCHEMA_NAME },
})
} else {
synonyms.set(originalName, {
target: { object: originalName, schema: object[0].SCHEMA_NAME },
})
}
var isKey = "FALSE"
for (let field of fields) {
isKey = "FALSE"
if (type === "table" || type === "hdbtable") {
if (object[0].HAS_PRIMARY_KEY === "TRUE") {
for (let constraint of constraints) {
if (field.COLUMN_NAME === constraint.COLUMN_NAME) {
constraint.COLUMN_NAME = constraint.COLUMN_NAME.replace(/\./g, "_")
cdstable += "key "
isKey = "TRUE"
}
}
}
} else {
if (field.KEY_COLUMN_NAME) {
cdstable += "key "
isKey = "TRUE"
}
}
let xref = {}
xref.before = field.COLUMN_NAME
field.COLUMN_NAME = field.COLUMN_NAME.replace(/\./g, "_")
xref.after = field.COLUMN_NAME
cdstable += "\t"
if (options.useQuoted){
cdstable += `![${field.COLUMN_NAME}]` + ": "
}else{
cdstable += `${field.COLUMN_NAME}` + ": "
}
// Handle geo types that need SRS ID
const isGeo = (field.DATA_TYPE_NAME === "ST_POINT" || field.DATA_TYPE_NAME === "ST_GEOMETRY")
const geoSrsId = isGeo ? await getGeoColumns(db, object[0], field, type) : null
let typeStr = getTypeMapping(field.DATA_TYPE_NAME, field.LENGTH, field.SCALE, options.useHanaTypes, geoSrsId)
// Override TIMESTAMP for preview context
if (field.DATA_TYPE_NAME === "TIMESTAMP" && parent === 'preview') {
typeStr = "String"
}
cdstable += typeStr
xref.dataType = field.DATA_TYPE_NAME
global.__xRef.push(xref)
if (field.DEFAULT_VALUE) {
if(field.DATA_TYPE_NAME === "BOOLEAN"){
switch (field.DEFAULT_VALUE) {
case 0:
cdstable += ` default false`
break
case 1:
cdstable += ` default true`
break
default:
cdstable += ` default false`
}
}else{
cdstable += ` default '${field.DEFAULT_VALUE}'`
}
}
if (field.IS_NULLABLE === "FALSE") {
if (isKey === "FALSE") {
cdstable += " not null"
}
} else {
// if (isKey === "FALSE") {
// cdstable += " null"
// }
}
if (field.COMMENTS) {
cdstable += ` @title: '${field.COLUMN_NAME}: ${field.COMMENTS.replace(/[']/g, "'$&")}' `
} else {
cdstable += ` @title: '${field.COLUMN_NAME}' `
}
cdstable += "; "
cdstable += "\n"
}
cdstable += "}\n"
return cdstable
}
/**
* Get Geo Columns requires special lookup and details
* @param {object} db - Database Connection
* @param {object} object - DB Object Details
* @param {object} field - Object Field
* @param {string} type - View or table
* @returns {Promise<string>} GEO SRS ID
*/
export async function getGeoColumns(db, object, field, type) {
base.debug(bundle.getText("debug.call", ["getGeoColumns"]))
const statementString = `SELECT SRS_ID FROM ST_GEOMETRY_COLUMNS WHERE SCHEMA_NAME = ? AND TABLE_NAME = ? AND COLUMN_NAME = ?`
const statement = await db.preparePromisified(statementString)
let name = ''
if (type === "view") {
name = object.VIEW_NAME
} else {
name = object.TABLE_NAME
}
const geoColumns = await db.statementExecPromisified(statement, [object.SCHEMA_NAME, name, field.COLUMN_NAME])
return geoColumns[0].SRS_ID
}
export function parseSQLOptions(output, cdsSource){
// Define a regular expression to match the extended syntax
const extendedSyntaxRegex = /(?:UNLOAD PRIORITY \d+|AUTO MERGE|NO AUTO MERGE|GROUP TYPE "[^"]*"|GROUP SUBTYPE "[^"]*"|GROUP NAME "[^"]*"|GROUP LEAD|GROUP SUBTYPE "[^"]*"\))/gi
// Find the index of PARTITION statement
let partitionIndex = output.indexOf('PARTITION')
let partitionStatement
// Check if PARTITION is found
if (partitionIndex !== -1) {
// Find the index of the ';' character
let semicolonIndex = output.indexOf(';', partitionIndex)
// Extract the PARTITION statement
partitionStatement = semicolonIndex !== -1 ? output.substring(partitionIndex, semicolonIndex + 1) : output.substring(partitionIndex)
}
// Extract extended syntax using regex
const extendedSyntax = output.match(extendedSyntaxRegex)
if (extendedSyntax || partitionStatement) {
cdsSource += `@sql.append: \`\`\`sql \n`
for (let index = 0; index < extendedSyntax.length; index++) {
const element = extendedSyntax[index]
cdsSource += `${element}\n`
}
if (partitionStatement) {
cdsSource += `${partitionStatement}\n`
}
cdsSource += `\`\`\`\n`
}
return cdsSource
}