From 585b11408e28821e858d927a0b3a1893ab6da475 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Sat, 2 Jul 2022 15:14:34 +0530 Subject: [PATCH 1/2] refactor: migrate sqlclient from nc-help re #336, #2504 Signed-off-by: Pranav C --- packages/nocodb-sdk/src/lib/Api.ts | 1 - packages/nocodb/package-lock.json | 6 +- .../src/lib/db/sql-client/lib/KnexClient.ts | 3388 +++++++++++++++++ .../src/lib/db/sql-client/lib/SqlClient.ts | 114 + .../lib/db/sql-client/lib/SqlClientFactory.ts | 40 + .../src/lib/db/sql-client/lib/data.helper.ts | 267 ++ .../db/sql-client/lib/mssql/MssqlClient.ts | 2675 +++++++++++++ .../db/sql-client/lib/mssql/mssql.queries.ts | 39 + .../db/sql-client/lib/mysql/MysqlClient.ts | 2617 +++++++++++++ .../lib/db/sql-client/lib/mysql/TidbClient.ts | 61 + .../db/sql-client/lib/mysql/VitessClient.ts | 247 ++ .../sql-client/lib/mysql/fakerFunctionList.ts | 898 +++++ .../lib/mysql/findDataTypeMapping.ts | 65 + .../db/sql-client/lib/mysql/mysql.queries.ts | 364 ++ .../db/sql-client/lib/oracle/OracleClient.ts | 2220 +++++++++++ .../sql-client/lib/oracle/oracle.queries.ts | 25 + .../nocodb/src/lib/db/sql-client/lib/order.ts | 111 + .../src/lib/db/sql-client/lib/pg/PgClient.ts | 2829 ++++++++++++++ .../db/sql-client/lib/pg/YugabyteClient.ts | 140 + .../lib/db/sql-client/lib/pg/pg.queries.ts | 239 ++ .../db/sql-client/lib/sqlite/SqliteClient.ts | 2087 ++++++++++ .../sql-client/lib/sqlite/sqlite.queries.ts | 34 + packages/nocodb/src/lib/db/sql-mgr/SqlMgr.ts | 6 +- .../src/lib/db/sql-mgr/code/BaseRender.ts | 5 +- .../nocodb/src/lib/db/sql-mgr/v2/SqlMgrv2.ts | 7 +- .../lib/db/sql-migrator/lib/KnexMigrator.ts | 11 +- .../lib/db/sql-migrator/lib/KnexMigratorv2.ts | 10 +- .../lib/db/{sql-migrator => }/util/Debug.ts | 2 +- .../db/{sql-migrator => }/util/DebugMgr.ts | 0 .../{sql-migrator => }/util/FileCollection.ts | 0 .../lib/db/{sql-migrator => }/util/Result.ts | 0 .../lib/db/{sql-migrator => }/util/emit.ts | 0 .../db/{sql-migrator => }/util/file.help.ts | 0 packages/nocodb/src/lib/meta/NcMetaMgr.ts | 3 +- packages/nocodb/src/lib/utils/Emit.ts | 14 + .../nocodb/src/lib/utils/NcConfigFactory.ts | 2 +- packages/nocodb/src/lib/utils/Result.js | 13 + .../src/lib/utils/common/NcConnectionMgr.ts | 2 +- .../src/lib/utils/common/NcConnectionMgrv2.ts | 2 +- .../src/lib/v1-legacy/NcProjectBuilder.ts | 3 +- 40 files changed, 18519 insertions(+), 28 deletions(-) create mode 100644 packages/nocodb/src/lib/db/sql-client/lib/KnexClient.ts create mode 100644 packages/nocodb/src/lib/db/sql-client/lib/SqlClient.ts create mode 100644 packages/nocodb/src/lib/db/sql-client/lib/SqlClientFactory.ts create mode 100644 packages/nocodb/src/lib/db/sql-client/lib/data.helper.ts create mode 100644 packages/nocodb/src/lib/db/sql-client/lib/mssql/MssqlClient.ts create mode 100644 packages/nocodb/src/lib/db/sql-client/lib/mssql/mssql.queries.ts create mode 100644 packages/nocodb/src/lib/db/sql-client/lib/mysql/MysqlClient.ts create mode 100644 packages/nocodb/src/lib/db/sql-client/lib/mysql/TidbClient.ts create mode 100644 packages/nocodb/src/lib/db/sql-client/lib/mysql/VitessClient.ts create mode 100644 packages/nocodb/src/lib/db/sql-client/lib/mysql/fakerFunctionList.ts create mode 100644 packages/nocodb/src/lib/db/sql-client/lib/mysql/findDataTypeMapping.ts create mode 100644 packages/nocodb/src/lib/db/sql-client/lib/mysql/mysql.queries.ts create mode 100644 packages/nocodb/src/lib/db/sql-client/lib/oracle/OracleClient.ts create mode 100644 packages/nocodb/src/lib/db/sql-client/lib/oracle/oracle.queries.ts create mode 100644 packages/nocodb/src/lib/db/sql-client/lib/order.ts create mode 100644 packages/nocodb/src/lib/db/sql-client/lib/pg/PgClient.ts create mode 100644 packages/nocodb/src/lib/db/sql-client/lib/pg/YugabyteClient.ts create mode 100644 packages/nocodb/src/lib/db/sql-client/lib/pg/pg.queries.ts create mode 100644 packages/nocodb/src/lib/db/sql-client/lib/sqlite/SqliteClient.ts create mode 100644 packages/nocodb/src/lib/db/sql-client/lib/sqlite/sqlite.queries.ts rename packages/nocodb/src/lib/db/{sql-migrator => }/util/Debug.ts (99%) rename packages/nocodb/src/lib/db/{sql-migrator => }/util/DebugMgr.ts (100%) rename packages/nocodb/src/lib/db/{sql-migrator => }/util/FileCollection.ts (100%) rename packages/nocodb/src/lib/db/{sql-migrator => }/util/Result.ts (100%) rename packages/nocodb/src/lib/db/{sql-migrator => }/util/emit.ts (100%) rename packages/nocodb/src/lib/db/{sql-migrator => }/util/file.help.ts (100%) create mode 100644 packages/nocodb/src/lib/utils/Emit.ts create mode 100644 packages/nocodb/src/lib/utils/Result.js diff --git a/packages/nocodb-sdk/src/lib/Api.ts b/packages/nocodb-sdk/src/lib/Api.ts index 950123a8ad..79156c0ad5 100644 --- a/packages/nocodb-sdk/src/lib/Api.ts +++ b/packages/nocodb-sdk/src/lib/Api.ts @@ -724,7 +724,6 @@ export class HttpClient { formData.append(key, property); } else if (typeof property === 'object' && property !== null) { if (Array.isArray(property)) { - // eslint-disable-next-line functional/no-loop-statement for (const prop of property) { formData.append(`${key}[]`, prop); } diff --git a/packages/nocodb/package-lock.json b/packages/nocodb/package-lock.json index 440ce8bc87..f80b9346f8 100644 --- a/packages/nocodb/package-lock.json +++ b/packages/nocodb/package-lock.json @@ -159,7 +159,7 @@ } }, "../nocodb-sdk": { - "version": "0.92.0", + "version": "0.92.3", "license": "MIT", "dependencies": { "axios": "^0.21.1", @@ -187,7 +187,7 @@ "prettier": "^2.1.1", "standard-version": "^9.0.0", "ts-node": "^9.0.0", - "typedoc": "^0.19.0", + "typedoc": "^0.22.17", "typescript": "^4.0.2" }, "engines": { @@ -36813,7 +36813,7 @@ "prettier": "^2.1.1", "standard-version": "^9.0.0", "ts-node": "^9.0.0", - "typedoc": "^0.19.0", + "typedoc": "^0.22.17", "typescript": "^4.0.2" } }, diff --git a/packages/nocodb/src/lib/db/sql-client/lib/KnexClient.ts b/packages/nocodb/src/lib/db/sql-client/lib/KnexClient.ts new file mode 100644 index 0000000000..49f055111f --- /dev/null +++ b/packages/nocodb/src/lib/db/sql-client/lib/KnexClient.ts @@ -0,0 +1,3388 @@ +/* eslint-disable no-constant-condition */ +import Knex from 'knex'; +import Debug from '../../util/Debug'; +import Emit from '../../util/emit'; +import Result from '../../util/Result'; + +import knex from 'knex'; +import lodash from 'lodash'; +import fs from 'fs'; +import { promisify } from 'util'; +import jsonfile from 'jsonfile'; +import path from 'path'; +import mkdirp from 'mkdirp'; +import Order from './order'; +import * as dataHelp from './data.helper'; +import SqlClient from './SqlClient'; +import { Tele } from 'nc-help'; +const evt = new Emit(); + +const log = new Debug('KnexClient'); +// TODO: Im sure there are more types to handle here +const strTypes = [ + 'varchar', + 'char', + 'image', + 'character', + 'character varying', + 'nchar', + 'nvarchar', + 'clob', + 'nvarchar2', + 'varchar2', + 'raw', + 'long raw', + 'bfile', + 'nclob' +]; +const intTypes = [ + 'integer', + 'int', + 'smallint', + 'mediumint', + 'bigint', + 'tinyint', + 'int2', + 'int4', + 'int8', + 'long', + 'serial', + 'bigserial', + 'smallserial', + 'number' +]; +const floatTypes = [ + 'float', + 'double', + 'decimal', + 'numeric', + 'real', + 'double precision', + 'real', + 'money', + 'smallmoney', + 'dec' +]; +const dateTypes = [ + 'date', + 'datetime', + 'timestamp', + 'time', + 'timestamp without time zone', + 'timestamp with time zone', + 'time without time zone', + 'time with time zone', + 'datetime2', + 'smalldatetime', + 'datetimeoffset', + 'interval year', + 'interval day' +]; +const _enumTypes = ['enum', 'set']; +const yearTypes = ['year']; +const bitTypes = ['bit']; +const textTypes = ['tinytext', 'mediumtext', 'longtext', 'ntext', 'text']; +const boolTypes = ['bool', 'boolean']; +const blobTypes = ['blob', 'mediumblob', 'longblob', 'binary', 'varbinary']; +const geometryTypes = ['geometry']; +const pointTypes = ['point']; +const linestringTypes = ['linestring']; +const polygonTypes = ['polygon']; +const multipointTypes = ['multipoint']; +const multilinestringTypes = ['multilinestring']; +const multipolygonTypes = ['multipolygon']; +const jsonTypes = ['json']; + +function createPks(table, columns, pks) { + let pkCreate = 'table.primary(['; + for (let i = 0; i < pks.length; ++i) { + if (i) { + pkCreate += `"${columns[pks[i]].cn}"`; + } else { + pkCreate += `"${columns[pks[i]].cn}",`; + } + } + pkCreate += `])`; + + eval(`var inMemoryFunc = function(table) { + ${pkCreate} + }`); + console.log(pkCreate); + // @ts-ignore + inMemoryFunc(table); +} + +function columnCreate(sqlClient, table, colUiObj) { + let skip = false; + let column; + + // TODO : write a function - is the type char + if ( + colUiObj.dt === 'varchar' || + colUiObj.dt === 'char' || + colUiObj.dt === 'text' + ) { + colUiObj.cdf = colUiObj.cdf ? JSON.stringify(colUiObj.cdf) : null; + } else colUiObj.cdf = colUiObj.cdf === '' ? null : colUiObj.cdf; + + if (colUiObj.ai) { + if (colUiObj.dtx === 'bigInteger') { + column = table.bigIncrements(colUiObj.cn); + skip = true; + } else { + column = table.increments(colUiObj.cn); + skip = true; + } + } + + // specifc type + if (colUiObj.dtx === 'specificType' && !skip) { + // console.log(colUiObj); + const precision = + colUiObj.dtxp && colUiObj.dtxp !== ' ' ? colUiObj.dtxp : null; + const scale = colUiObj.dtxs && colUiObj.dtxs !== ' ' ? colUiObj.dtxs : null; + if (precision && scale) { + column = table.specificType( + colUiObj.cn, + `${colUiObj.dt}(${precision},${scale})` + ); + } else if (precision) { + column = table.specificType(colUiObj.cn, `${colUiObj.dt}(${precision})`); + } else { + column = table.specificType(colUiObj.cn, colUiObj.dt); + } + } else if (colUiObj.dtx === 'integer' && !skip) { + column = table.integer(colUiObj.cn); + } else if (colUiObj.dtx === 'bigInteger' && !skip) { + column = table.bigInteger(colUiObj.cn); + } else if (colUiObj.dtx === 'text' && !skip) { + column = table.text(colUiObj.cn); + } else if (colUiObj.dtx === 'string' && !skip) { + column = table.string(colUiObj.cn); + } else if (colUiObj.dtx === 'float' && !skip) { + column = table.float(colUiObj.cn); + } else if (colUiObj.dtx === 'decimal' && !skip) { + column = table.decimal(colUiObj.cn); + } else if (colUiObj.dtx === 'boolean' && !skip) { + column = table.boolean(colUiObj.cn); + } else if (colUiObj.dtx === 'date' && !skip) { + column = table.date(colUiObj.cn); + } else if (colUiObj.dtx === 'dateTime' && !skip) { + column = table.dateTime(colUiObj.cn); + } else if (colUiObj.dtx === 'time' && !skip) { + column = table.time(colUiObj.cn); + } else if (colUiObj.dtx === 'timestamp' && !skip) { + column = table.timestamp(colUiObj.cn); + } else if (colUiObj.dtx === 'enu' && !skip) { + column = table.enu(colUiObj.cn, JSON.stringify(colUiObj.enuValue || [])); + } else if (colUiObj.dtx === 'json' && !skip) { + column = table.json(colUiObj.cn); + } else if (colUiObj.dtx === 'uuid' && !skip) { + column = table.uuid(colUiObj.cn); + } + + if (colUiObj.pk) { + column.primary(); + } + + // log.debug(colUiObj.cn, colUiObj.nrqd); + if (!colUiObj.rqd) { + column.nullable(); + } else { + column.notNullable(); + } + + if (colUiObj.ck) { + column.unique(); + } + + if (colUiObj.un) { + column.unsigned(); + } + + if (colUiObj.cdf) { + column.defaultTo(sqlClient.raw(colUiObj.cdf)); + } + + // log.debug(colUiObj, colCreate); + // + // eval(`var inMemoryFunc = function(table) { + // ${colCreate} + // }`); + // console.log(colCreate); + // inMemoryFunc(table); +} + +function removeColumn(table, _n, _o) { + const colCreate = `table.dropColumn(n.cn)`; + + // log.debug(n, o, colCreate); + + eval(`var inMemoryFunc = function(table) { + ${colCreate} + }`); + + // @ts-ignore + inMemoryFunc(table); +} + +function renameColumn(_knexClient, connectionConfig, _table, o, n): any { + let upStatement = ``; + let downStatement = ``; + if (connectionConfig.client === 'mysql') { + const type = n.ct; + const nn = n.rqd ? 'NOT NULL' : ''; + const ai = n.ai ? 'AUTO_INCREMENT' : ''; + // const un = ''; + const _default = n.cdf ? `DEFAULT '${n.cdf}'` : ''; + + upStatement = `ALTER TABLE \`${n.tn}\` CHANGE COLUMN + \`${o.cn}\` \`${n.cn}\` ${type} ${nn} ${ai} ${_default}`; + + downStatement = `ALTER TABLE \`${n.tn}\` CHANGE COLUMN + \`${n.cn}\` \`${o.cn}\` ${type} ${nn} ${ai} ${_default}`; + + const obj = { + upStatement: [{ sql: upStatement }], + downStatement: [{ sql: downStatement }] + }; + + // console.log(obj); + + return obj; + } +} + +function pkUpdate(table, n, o) { + let colUpdate = 'table'; + // const skip = false; + // const skipColumnDefault = false; + // const skipColumnUnsigned = false; + + log.debug(n, o); + + colUpdate = ``; + let pkCount = 0; + for (let i = 0; i < o.length; ++i) { + if (o[i].pk) { + if (pkCount) { + colUpdate += `,"${o[i].cn}"`; + } else { + colUpdate += `table.dropPrimary(["${o[i].cn}"`; + } + + pkCount += 1; + log.debug(colUpdate); + } + } + if (pkCount) colUpdate += `]);\n`; + + colUpdate += ``; + pkCount = 0; + for (let i = 0; i < n.length; ++i) { + if (n[i].pk) { + if (pkCount) { + colUpdate += `,"${n[i].cn}"`; + } else { + colUpdate += `table.primary(["${n[i].cn}"`; + } + + pkCount += 1; + log.debug(colUpdate); + } + } + if (pkCount) colUpdate += `]);\n`; + + log.debug(colUpdate); + + eval(`var inMemoryFunc = function(table) { + ${colUpdate} + }`); + + // console.log(colUpdate); + + // @ts-ignore + inMemoryFunc(table); +} + +function columnUpdate(knexClient, table, n, o) { + let skip = false; + let skipColumnDefault = false; + let skipColumnUnsigned = false; + let skipNotNullable = false; + let column; + + // TODO : write a function - is the type char + if (n.dt === 'varchar' || n.dt === 'char' || n.dt === 'text') { + n.cdf = n.cdf ? JSON.stringify(n.cdf) : null; + } else { + n.cdf = n.cdf === '' ? null : n.cdf; + } + + if ('ai' in n && o && n.ai !== o.ai && n.ai) { + column = table.increments(n.cn); + skip = true; + } else if ('pk' in n && (!o || n.pk !== o.pk) && n.pk) { + column = table.primary(n.cn); + } else if ('pk' in n && o && n.pk !== o.pk && !n.pk) { + column = table.dropPrimary(n.cn); + } + + if ('dtx' in n && n.dtx === 'specificType' && !skip) { + const precision = n.dtxp && n.dtxp !== ' ' ? n.dtxp : null; + const scale = n.dtxs && n.dtxs !== ' ' ? n.dtxs : null; + if (precision && scale) { + column = table.specificType(n.cn, `${n.dt}(${precision},${scale})`); + } else if (precision) { + column = table.specificType(n.cn, `${n.dt}(${precision})`); + } else { + column = table.specificType(n.cn, n.dt); + } + } else if ('dtx' in n && n.dtx === 'integer' && !skip) { + if (!o) { + column = table.integer(n.cn); + } else { + column = table.integer(n.cn, n.dtxp); + } + } else if ('dtx' in n && n.dtx === 'bigInteger' && !skip) { + column = table.bigInteger(n.cn); + } else if ('dtx' in n && n.dtx === 'text' && !skip) { + column = table.text(n.cn); + } else if ('dtx' in n && n.dtx === 'string' && !skip) { + if (!o) { + column = table.string(n.cn); + } else { + column = table.string(n.cn, n.dtxp); + } + } else if ('dtx' in n && n.dtx === 'float' && !skip) { + if (!o) { + column = table.float(n.cn); + } else { + column = table.float(n.cn, n.dtxp, n.dtxs); + } + } else if ('dtx' in n && n.dtx === 'decimal' && !skip) { + if (!o) { + column = table.decimal(n.cn); + } else { + column = table.decimal(n.cn, n.dtxp, n.dtxs); + } + } else if ('dtx' in n && n.dtx === 'boolean' && !skip) { + column = table.boolean(n.cn); + } else if ('dtx' in n && n.dtx === 'binary' && !skip) { + column = table.binary(n.cn); + } else if ('dtx' in n && n.dtx === 'date' && !skip) { + column = table.date(n.cn); + } else if ('dtx' in n && n.dtx === 'dateTime' && !skip) { + column = table.dateTime(n.cn); + } else if ('dtx' in n && n.dtx === 'time' && !skip) { + column = table.time(n.cn); + } else if ('dtx' in n && n.dtx === 'timestamp' && !skip) { + column = table.timestamp(n.cn); + } else if ('dtx' in n && n.dtx === 'enu' && !skip) { + column = table.enu(n.cn, JSON.stringify(n.enuValue || [])); + } else if ('dtx' in n && n.dtx === 'json' && !skip) { + column = table.json(n.cn); + } else if ('dtx' in n && n.dtx === 'uuid' && !skip) { + column = table.uuid(n.cn); + } + + if (o) { + // updating existing column + /** + * if rqd is getting DISABLED and there is non_null value for default + * query builder has to include defaultTo() and unsigned() + */ + if (n.rqd !== o.rqd && !n.rqd) { + column.nullable(); + + if (n.cdf) { + column.defaultTo(knexClient.raw(n.cdf)); + skipColumnDefault = true; + } + + if (n.un) { + column.unsigned(); + skipColumnUnsigned = true; + } + } + + /** + * if rqd is getting ENABLED and there is non_null value for default + * query builder has to include defaultTo() + */ + if (n.rqd !== o.rqd && n.rqd && !skipNotNullable) { + column.notNullable(); + skipNotNullable = true; + + if (n.cdf && !skipColumnDefault) { + column.defaultTo(knexClient.raw(n.cdf)); + skipColumnDefault = true; + } + if (n.un && !skipColumnUnsigned) { + column.unsigned(); + skipColumnUnsigned = true; + } + } + + if (n.un !== o.un && !skipColumnUnsigned) { + if (n.un) column.unsigned(); + + if (n.cdf && !skipColumnDefault) { + column.defaultTo(knexClient.raw(n.cdf)); + skipColumnDefault = true; + } + if (n.rqd && !skipNotNullable) { + column.notNullable(); + skipNotNullable = true; + } + } + + /** + * if default is getting enabled and there is a rqd enabled + * query builder has to include defaultTo() + */ + if (n.cdf !== o.cdf && !skipColumnDefault) { + // console.log(knexClient.raw(n.cdf)) + column.defaultTo(n.cdf ? knexClient.raw(n.cdf) : null); + + if (n.rqd && !skipNotNullable) { + column.notNullable(); + skipNotNullable = true; + } + if (n.un && !skipColumnUnsigned) { + column.unsigned(); + skipColumnUnsigned = true; + } + } + + if ((!o || n.ck !== o.ck) && n.ck) { + column.unique(); + } + + if ((!o || n.ck !== o.ck) && !n.ck) { + column.dropUnique(n.cn); + } + } else { + // adding a new column + + if (n.rqd) { + column.notNullable(); + } else if (!n.rqd && !n.cdf) { + column.nullable(); + } + + if (n.un) { + column.unsigned(); + } + + if (n.cdf) { + column.defaultTo(knexClient.raw(n.cdf)); + } + } + + // /** + // * if rqd is getting DISABLED and there is non_null value for default + // * query builder has to include defaultTo() and unsigned() + // */ + // if ("rqd" in n && (!o || n.rqd !== o.rqd) && !n.rqd) { + // colUpdate += `.nullable()`; + // + // if (n.cdf) { + // colUpdate += `.defaultTo(knexClient.raw(\`${n.cdf}\`))`; + // } + // + // if (n.un) { + // colUpdate += `.unsigned()`; + // } + // } + // + // /** + // * if rqd is getting ENABLED and there is non_null value for default + // * query builder has to include defaultTo() + // */ + // if ("rqd" in n && + // (!o || n.rqd !== o.rqd) && + // !n.rqd === false) { + // colUpdate += `.notNullable()`; + // + // if (n.cdf && !skipColumnDefault) { + // colUpdate += `.defaultTo(knexClient.raw(\`${n.cdf}\`))`; + // skipColumnDefault = true; + // } + // if (n.un && !skipColumnDefault) { + // colUpdate += `.unsigned()`; + // skipColumnUnsigned = true; + // } + // + // } + // + // if ("ck" in n && + // (!o || n.ck !== o.ck) && + // n.ck) { + // colUpdate += `.unique()`; + // } + // + // if ("ck" in n && + // (o && n.ck !== o.ck) && + // !n.ck) { + // const dropUnique = `column.dropUnique(\"${n.cn}\");\n`; + // colUpdate = dropUnique + colUpdate; + // } + // + // if ("un" in n && (!o || n.un !== o.un) && !skipColumnUnsigned) { + // if (n.un) + // colUpdate += `.unsigned()`; + // + // if (n.cdf && !skipColumnDefault) { + // colUpdate += `.defaultTo(knexClient.raw(\`${n.cdf}\`))`; + // skipColumnDefault = true; + // } + // if (n.rqd) { + // colUpdate += `.notNullable()`; + // } + // } + // + // /** + // * if default is getting enabled and there is a rqd enabled + // * query builder has to include defaultTo() + // */ + // if ("cdf" in n && (o && n.cdf !== o.cdf) && !skipColumnDefault) { + // colUpdate += `.defaultTo(knexClient.raw(\`${n.cdf}\`))`; + // if (n.rqd) { + // colUpdate += `.notNullable()`; + // } + // if (n.un) { + // colUpdate += `.unsigned()`; + // skipColumnUnsigned = true; + // } + // + // } + + if (o && o.cno !== n.cn) { + column.renameColumn(n.cno, n.cn); + } + + if (n.altered & 1) { + // added - do nothing + } else if (n.altered & 2) { + // modified + column.alter(); + } +} + +class KnexClient extends SqlClient { + private static ___ext: boolean; + protected _connectionConfig: any; + protected metaDb: any; + protected evt: Emit; + public knex: Knex; + + constructor(connectionConfig) { + super(connectionConfig); + this.validateInput(); + if (connectionConfig.connection && connectionConfig.connection.port) + connectionConfig.connection.port = +connectionConfig.connection.port; + + this._connectionConfig = connectionConfig; + if (connectionConfig.knex) { + this.sqlClient = connectionConfig.knex; + } else { + // console.log('KnexClient',this.connectionConfig); + if ( + this.connectionConfig.connection.ssl && + typeof this.connectionConfig.connection.ssl === 'object' + ) { + if (this.connectionConfig.connection.ssl.caFilePath) { + this.connectionConfig.connection.ssl.ca = fs + .readFileSync(this.connectionConfig.connection.ssl.caFilePath) + .toString(); + } + if (this.connectionConfig.connection.ssl.keyFilePath) { + this.connectionConfig.connection.ssl.key = fs + .readFileSync(this.connectionConfig.connection.ssl.keyFilePath) + .toString(); + } + if (this.connectionConfig.connection.ssl.certFilePath) { + this.connectionConfig.connection.ssl.cert = fs + .readFileSync(this.connectionConfig.connection.ssl.certFilePath) + .toString(); + } + } + const tmpConnectionConfig = + connectionConfig.client === 'sqlite3' + ? connectionConfig.connection + : connectionConfig; + this.sqlClient = knex(tmpConnectionConfig); + } + this.knex = this.sqlClient; + this.metaDb = {}; + this.metaDb.tables = {}; + + this.evt = new Emit(); + } + + _validateInput() { + try { + const packageJson = JSON.parse( + fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8') + ); + return ( + packageJson.name === 'nocodb' || 'nocodb' in packageJson.dependencies + ); + } catch (e) {} + return true; + } + + validateInput() { + try { + if (!('___ext' in KnexClient)) { + KnexClient.___ext = this._validateInput(); + } + if (!KnexClient.___ext) { + Tele.emit('evt', { + evt_type: 'project:external', + payload: null, + check: true + }); + } + } catch (e) {} + } + + emitTele(data) { + this.evt.evt.emit('tele', { + table_count: 0, + relation_count: 0, + view_count: 0, + api_count: 0, + mysql: 0, + pg: 0, + mssql: 0, + oracledb: 0, + sqlite3: 0, + rest: 0, + graphql: 0, + ...data + }); + } + + async schemaCreateWithCredentials(_args): Promise {} + + async sequenceList(_args = {}): Promise {} + + async sequenceCreate(_args = {}): Promise {} + + async sequenceUpdate(_args = {}): Promise {} + + async sequenceDelete(_args = {}): Promise {} + + _isColumnPrimary(columnObj) { + if ( + columnObj.ck === 'PRI' || + columnObj.ck === 'PRIMARY KEY' || + columnObj.ck === 'P' + ) { + return true; + } + return false; + } + + _isColumnForeignKey(tableObj, cn) { + if (lodash.findIndex(tableObj.foreignKeys, { cn: cn }) === -1) { + return false; + } + return true; + } + + _isColumnPrimaryForInserting(tableObj, columnObj) { + if ( + columnObj.ck === 'PRI' || + columnObj.ck === 'PRIMARY KEY' || + columnObj.ck === 'P' + ) { + if (tableObj.primaryKeys.length > 1) { + if ( + lodash.findIndex(tableObj.primaryKeys, { + cn: columnObj.cn + }) > 0 + ) { + return false; + } + } + return true; + } + return false; + } + + _getMaxPksPossible(columnObject) { + const max = { + int: 2147483647, + tinyint: 127, + smallint: 32767, + mediumint: 8388607, + bigint: 2 ^ (63 - 1) + }; + + const maxUnsigned = { + int: 4294967295, + tinyint: 255, + smallint: 65535, + mediumint: 16777215, + bigint: 2 ^ (64 - 1) + }; + + if (columnObject.un) { + return maxUnsigned[columnObject.dt]; + } + return max[columnObject.dt]; + } + + _getMaxNumPossible(columnObject) { + const max = { + int: 2147483647, + tinyint: 127, + smallint: 32767, + mediumint: 8388607, + bigint: 2 ^ (63 - 1) + }; + + const maxUnsigned = { + int: 4294967295, + tinyint: 255, + smallint: 65535, + mediumint: 16777215, + bigint: 2 ^ (64 - 1) + }; + + if (columnObject.un) { + return maxUnsigned[columnObject.dt]; + } + return max[columnObject.dt]; + } + + _getMaxRowsPossible(tableObj, maxy) { + let max = 10000; + const pk = tableObj.primaryKeys[0]; + + if (tableObj.primaryKeys.length) { + const dt = this.getKnexDataTypeMock(pk.ct); + const col = lodash.find(tableObj.columns, { + cn: pk.cn + }); + + if (dt === 'integer') { + max = Math.pow(2, col.np); + } else if (dt === 'string') { + if (col.clen && col.clen < 3) { + max = 500; + } + } + } + + let max1 = 10000; + let searchFrom = 0; + let foundIndex = lodash.findIndex( + tableObj.columns, + { ck: 'UNI' }, + searchFrom + ); + + while (foundIndex !== -1) { + const col = tableObj.columns[foundIndex]; + + const dt = this.getKnexDataTypeMock(col.ct); + + if (dt === 'integer') { + max1 = Math.pow(2, col.np); + } else if (dt === 'string') { + if (col.clen && col.clen < 2) { + max1 = 25; + } + } + + searchFrom = foundIndex; + foundIndex = lodash.findIndex( + tableObj.columns, + { ck: 'UNI' }, + searchFrom + 1 + ); + } + + let max2 = 10000; + searchFrom = 0; + foundIndex = lodash.findIndex(tableObj.columns, { ck: 'MUL' }, searchFrom); + + while (foundIndex !== -1) { + const col = tableObj.columns[foundIndex]; + + const dt = this.getKnexDataTypeMock(col.ct); + + if (dt === 'integer') { + max2 = Math.pow(2, col.np); + } else if (dt === 'string') { + // if (col['character_maximum_length'] && col['character_maximum_length'] < 2) { + // max2 = 25; + // } + } + + searchFrom = foundIndex; + foundIndex = lodash.findIndex( + tableObj.columns, + { ck: 'MUL' }, + searchFrom + 1 + ); + } + + // console.log('min of: ', max, max1, max2, maxy); + return Math.min(max, max1, max2, maxy); + } + + getColumnType(_dbType): any {} + + getKnexDataTypeMock(databaseType) { + try { + const Type = databaseType; + + let colValidation = {}; + + if (dataHelp.getType(Type, strTypes)) { + colValidation = 'string'; + } else if (dataHelp.getType(Type, intTypes)) { + colValidation = 'integer'; + } else if (dataHelp.getType(Type, floatTypes)) { + colValidation = 'float'; + } else if (dataHelp.getType(Type, dateTypes)) { + colValidation = 'date'; + } else if (dataHelp.getType(Type, _enumTypes)) { + colValidation = 'enum'; + } else if (dataHelp.getType(Type, yearTypes)) { + colValidation = 'year'; + } else if (dataHelp.getType(Type, blobTypes)) { + colValidation = 'blob'; + } else if (dataHelp.getType(Type, boolTypes)) { + colValidation = 'boolean'; + } else if (dataHelp.getType(Type, geometryTypes)) { + colValidation = 'geometry'; + } else if (dataHelp.getType(Type, pointTypes)) { + colValidation = 'point'; + } else if (dataHelp.getType(Type, linestringTypes)) { + colValidation = 'linestring'; + } else if (dataHelp.getType(Type, polygonTypes)) { + colValidation = 'polygon'; + } else if (dataHelp.getType(Type, multipointTypes)) { + colValidation = 'multipoint'; + } else if (dataHelp.getType(Type, multilinestringTypes)) { + colValidation = 'multilinestring'; + } else if (dataHelp.getType(Type, multipolygonTypes)) { + colValidation = 'multipolygon'; + } else if (dataHelp.getType(Type, bitTypes)) { + colValidation = 'bit'; + } else if (dataHelp.getType(Type, textTypes)) { + colValidation = 'text'; + } else if (dataHelp.getType(Type, jsonTypes)) { + colValidation = 'json'; + } else { + colValidation = 'other'; + } + + return colValidation; + } catch (e) { + console.log(e); + return 'string'; + } + } + + getMinMax(_columnObject) {} + + async mockDb(args) { + // console.time('mockDb'); + const faker = require('faker'); + + try { + const settings = await jsonfile.readFile( + path.join(args.seedsFolder, '__xseeds.json') + ); + + await this.dbCacheInitAsyncKnex(); + + const order = new Order(this.metaDb.erMatrix); + + const orders = order.getOrder(); + console.log('Insert order by table index: ', orders); + // console.log('Insert order by table name: '); + + // order.resetIndex(15, 16); + // orders = order.getOrder(); + // console.log('Insert order by table index: ', orders); + // console.log('Insert order by table name: '); + // return; + + /** ************** START : reset all tables *************** */ + await this.knex.raw('SET foreign_key_checks = 0'); + for (let i = 0; i < orders.order.length; ++i) { + const tn = this.metaDb.erIndexTableObj[orders.order[i]]; + if (tn !== 'xc_evolutions' && this.metaDb.tables[tn].is_view === null) { + await this.knex.raw(`truncate ${tn}`); + await this.knex.raw(`ALTER TABLE ${tn} AUTO_INCREMENT = 0;`); + } + } + await this.knex.raw('SET foreign_key_checks = 1'); + /** ************** END : reset all tables *************** */ + + // return; + + // iterate over each table + for (let i = 0; i < orders.order.length; ++i) { + const tn = this.metaDb.erIndexTableObj[orders.order[i]]; + const tableObj = this.metaDb.tables[tn]; + + if (tn === 'xc_evolutions') { + } else { + /** ************** START : ignore views *************** */ + if (this.metaDb.tables[tn].is_view !== null) { + console.log('ignore view', tn); + continue; + } + + let numOfRows = +settings.rows.value || 8; + const rows = []; + let fks = []; + + let fakerColumns: any = await this.fakerColumnsList({ + seedsFolder: args.seedsFolder, + tn: tn + }); + fakerColumns = fakerColumns.data.list; + + let maxPks = numOfRows; + if (tableObj.primaryKeys.length) { + maxPks = this._getMaxPksPossible(tableObj.primaryKeys[0]); + } + + console.log('MaxPks Possible', maxPks); + + if (maxPks < numOfRows) { + numOfRows = maxPks; + } + + console.log(`\n\n Preparing to insert ${numOfRows} in ${tn}`); + + /** ************** START : create empty rows *************** */ + for (let k = 0; k < numOfRows; ++k) { + rows.push({}); + } + + // iterate over each foreign key in this table + /** ************** START : get FK column values *************** */ + for (let j = 0; j < this.metaDb.tables[tn].foreignKeys.length; ++j) { + const fkObj = this.metaDb.tables[tn].foreignKeys[j]; + // console.log('\n\tStart : get FK row', fkObj['rtn'] + '.' + fkObj['rcn'] + ' === ' + fkObj['cn']); + fks = await this.knex(fkObj.rtn) + .select(`${fkObj.rcn} as ${fkObj.cn}`) + .limit(numOfRows); + // console.log('fks:', fks); + + for ( + let rowIndex = 0, fksIndex = 0; + rowIndex < numOfRows && fksIndex < fks.length; + fksIndex++ + ) { + if (rowIndex < fks.length) { + for ( + let l = 0; + l < settings.foreign_key_rows.value && rowIndex < numOfRows; + ++l, ++rowIndex + ) { + rows[rowIndex][fkObj.cn] = fks[fksIndex][fkObj.cn]; + // rows[k+1][fkObj['cn']] = fks[k][fkObj['cn']]; + } + } else if (fks.length) { + // rows[k][fkObj['cn']] = fks[fks.length - 1][fkObj['cn']]; + break; + } else { + rows[rowIndex][fkObj.cn] = null; + } + } + + // for (let k = 0; k < numOfRows; ++k) { + // if (k < fks.length) { + // rows[k][fkObj['cn']] = fks[k][fkObj['cn']]; + // } else { + // if (fks.length) { + // //rows[k][fkObj['cn']] = fks[fks.length - 1][fkObj['cn']]; + // break; + // } else { + // rows[k][fkObj['cn']] = null; + // } + // } + // + // } + // console.log('\tEnd : get FK row', fkObj['rtn'] + '.' + fkObj['rcn'] + ' === ' + fkObj['cn']); + } + + /** ************** START : populate columns of the table *************** */ + for (let j = 0; j < this.metaDb.tables[tn].columns.length; ++j) { + const colObj = this.metaDb.tables[tn].columns[j]; + const colUiObj = this.metaDb.tables[tn].uiModel.columns[j]; + const fakerColObj = fakerColumns.find(col => col.cn === colObj.cn); + + console.log( + '\tColumn: ', + colObj.cn, + ' of type ', + colObj.dt, + colObj.np, + colObj.clen + ); + + const dt = this.getKnexDataTypeMock(colObj.dt); + const numDict = {}; + const floatDict = {}; + const strDict = {}; + + for (let k = 0; k < numOfRows; ++k) { + // console.log(this.knex(tn).insert(rows).toSQL()); + console.log(rows); + switch (dt) { + case 'year': + rows[k][colObj.cn] = Math.floor(Math.random() * 180) + 1920; + break; + + case 'enum': + { + const enums = fakerColObj.dtxp + .split(',') + .map(v => v.slice(1, -1)); + const enumIndex = Math.floor(Math.random() * enums.length); + rows[k][colObj.cn] = enums[enumIndex]; + } + break; + case 'integer': + if (this._isColumnPrimary(colObj) && colUiObj.ai) { + // primary key is auto inc - ignore creating from faker + // rows[k][colObj['cn']] = 0; + } else if (this._isColumnForeignKey(tableObj, colObj.cn)) { + // foreign key - ignore creating from faker + } else { + while (1) { + console.log('..'); + + let num = this._getMaxNumPossible(fakerColObj); + num = Math.floor(Math.random() * num); + // let max = Math.pow(2, colObj['np']) + // num = num % max; + + if ( + (colObj.ck === 'UNI' || + colObj.ck === 'MUL' || + this._isColumnPrimaryForInserting( + tableObj, + colObj + )) && + num in numDict + ) { + // console.log('>> num', num, numOfRows); + } else { + rows[k][colObj.cn] = num; + numDict[num] = num; + break; + } + } + } + + break; + + case 'float': + while (1) { + let num = Math.floor(Math.random() * 2147483647); + const max = Math.pow(2, fakerColObj.dtxp); + + num %= max; + + if ( + (colObj.ck === 'UNI' || + colObj.ck === 'MUL' || + this._isColumnPrimaryForInserting(tableObj, colObj)) && + num in floatDict + ) { + // console.log('>> num', num, numOfRows); + } else { + rows[k][colObj.cn] = num; + floatDict[num] = num; + break; + } + } + // rows[k][colObj['cn']] = faker.random.number(); + break; + + case 'date': + rows[k][colObj.cn] = faker.date.past(); + break; + case 'blob': + rows[k][colObj.cn] = faker.lorem.sentence(); + break; + case 'boolean': + rows[k][colObj.cn] = faker.random.boolean(); + break; + case 'geometry': + rows[k][colObj.cn] = this.knex.raw( + faker.fake('POINT({{random.number}}, {{random.number}})') + ); + break; + case 'point': + rows[k][colObj.cn] = this.knex.raw( + faker.fake('POINT({{random.number}}, {{random.number}})') + ); + break; + case 'linestring': + rows[k][colObj.cn] = this.knex.raw( + faker.fake( + 'LineString(POINT({{random.number}}, {{random.number}}), POINT({{random.number}}, {{random.number}}))' + ) + ); + break; + case 'polygon': + rows[k][colObj.cn] = this.knex.raw( + faker.fake( + "ST_GeomFromText('POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7,5 5))')" + ) + ); + break; + case 'multipoint': + rows[k][colObj.cn] = this.knex.raw( + faker.fake( + "ST_MPointFromText('MULTIPOINT ({{random.number}} {{random.number}}, {{random.number}} {{random.number}}, {{random.number}} {{random.number}})')" + ) + ); + break; + case 'multilinestring': + rows[k][colObj.cn] = this.knex.raw( + faker.fake( + "ST_GeomFromText('MultiLineString(({{random.number}} {{random.number}}, {{random.number}} {{random.number}}), ({{random.number}} {{random.number}}, {{random.number}} {{random.number}}))')" + ) + ); + break; + case 'multipolygon': + rows[k][colObj.cn] = this.knex.raw( + faker.fake( + "ST_GeomFromText('MultiPolygon(((0 0,0 3,3 3,3 0,0 0),(1 1,1 2,2 2,2 1,1 1)))')" + ) + ); + break; + case 'json': + rows[k][colObj.cn] = JSON.stringify({ + name: faker.name.firstName(), + suffix: faker.name.suffix() + }); + break; + case 'bit': + { + const num = Math.floor( + Math.random() * Math.pow(2, colObj.np || 6) + ); + rows[k][colObj.cn] = num; + } + break; + case 'text': + rows[k][colObj.cn] = faker.lorem.sentence(); + break; + + case 'string': + default: + if (this._isColumnForeignKey(tableObj, colObj.cn)) { + } else { + while (1) { + const fakerColumn = fakerColumns.find( + col => col.cn === colObj.cn + ); + + const fakerFuncs = fakerColumn.fakerFunction + ? fakerColumn.fakerFunction.split('.') + : []; + + let str = faker.internet.email(); + + if (fakerFuncs.length) { + // str = faker[fakerFuncs[0]][fakerFuncs[1]](); + } + + let max = 1; + if (colObj.clen) { + max = colObj.clen; + if (max < str.length) { + // max = str.length; + } + str = str.slice(0, max); + } + + if ( + (colObj.ck === 'UNI' || + this._isColumnPrimaryForInserting( + tableObj, + colObj + )) && + str in strDict + ) { + } else { + rows[k][colObj.cn] = str; + strDict[str] = 1; + break; + } + } + } + + break; + } + } + } + for (let i = 0; i < rows.length; ++i) { + // for (let key in rows[i]) { + // console.log(key,rows[i][key]); + // } + await this.knex(tn).insert(rows[i]); + } + + // await knex.raw(knex(tn).insert(rows).toSQL() + " ON DUPLICATE KEY UPDATE " + + // Object.getOwnPropertyNames(rows).map((field) => `${field}=VALUES(${field})`).join(", ")) + + this.emit(`Inserted '${rows.length}' rows in 'table.${tn}'`); + console.log('End: ', tn); + } + + // console.log(orders.order.length, tablesAsArr.length); + // + // let cycle = Object.keys(orders.cycle); + // + // for (let i = 0; i < cycle.length; ++i) { + // console.log(tablesAsArr[cycle[i]]); + // } + } + } catch (e) { + console.log('Error in mockDb : ', e); + throw e; + } + + // console.timeEnd('mockDb') + } + + async dbCacheInitAsyncKnex(_cbk = null) { + try { + const self = this; + let universalQuery = null; + let universalResults = []; + + // console.log(this.connectionConfig); + + if (this.connectionConfig.client === 'mysql') { + universalQuery = dataHelp.getMysqlSchemaQuery(); + + const results = await this.knex.raw(universalQuery, [ + this.connectionConfig.connection.database + ]); + + if (results.length) { + universalResults = results[0]; + + // osx mysql server has limitations related to open_tables + await this.knex.raw('FLUSH TABLES', []); + + // cbk(null, null) + } else { + // console.log('Cache init failed during database reading') + console.log({}, results[0]); + // cbk(err, results) + } + } + + if (universalResults.length) { + for (let i = 0; i < universalResults.length; ++i) { + const keys = Object.keys(universalResults[i]); + for (let j = 0; j < keys.length; ++j) { + const value = universalResults[i][keys[j]]; + + universalResults[i][keys[j].toLowerCase()] = value; + + // console.log(value); + } + } + + self.iterateToCacheTables(universalResults); + self.iterateToCacheTablePks(universalResults); + self.iterateToCacheTableColumns(universalResults); + self.iterateToCacheTableFks(universalResults); + self.constructUiMeta(); + + console.log('erm atrix = = = = ='); + self.iterateToCacheErMatrix(universalResults); + console.log('erm atrix = = = = ='); + // await self.mockDb() + + // console.log(this.metaDb.tables['film'].columns); + + for (let i = 0; i < universalResults.length; ++i) { + if (universalResults[i].ck === 'PRI') { + console.log( + 'PK >> ', + universalResults[i].tn, + universalResults[i].cn, + universalResults[i].ext + ); + } + } + } + } catch (e) { + console.log(e); + } + } + + getUiColumnObject(column) { + const uiColObj: any = {}; + + // console.log(column); + + uiColObj.cn = column.cn; + uiColObj.dt = column.dt; + uiColObj.data_type_knex = this.getKnexDataTypeMock(column.dt); + uiColObj.ct = column.ct; + uiColObj.nrqd = column.nrqd !== 'NO'; + uiColObj.ck = column.ck === 'MUL'; + uiColObj.pk = column.ck === 'PRI'; + uiColObj.un = column.ct.indexOf('unsigned') !== -1; + uiColObj.ai = column.ext.indexOf('auto_increment') !== -1; + + if (this.connectionConfig.client === 'mysql') { + uiColObj.cdf = column.cdf; + uiColObj.clen = column.clen; + uiColObj.np = column.np; + uiColObj.ns = column.ns; + uiColObj.dp = column.dp; + } + + return uiColObj; + } + + constructUiMeta() { + for (const tn in this.metaDb.tables) { + const table = this.metaDb.tables[tn]; + + for (let i = 0; i < table.columns.length; ++i) { + const column = table.columns[i]; + table.uiModel.columns[i] = this.getUiColumnObject(column); + } + } + } + + iterateToCacheTables(schemaResults) { + for (let i = 0; i < schemaResults.length; ++i) { + const schemaRow = schemaResults[i]; + if (0 && schemaRow.is_view) { + } else { + const tn = schemaRow.tn; + + if (!(tn in this.metaDb.tables)) { + this.metaDb.tables[tn] = {}; + this.metaDb.tables[tn].primaryKeys = []; + this.metaDb.tables[tn].foreignKeys = []; + this.metaDb.tables[tn].columns = []; + this.metaDb.tables[tn].indicies = []; + this.metaDb.tables[tn].is_view = schemaRow.is_view; + + const uiModel: any = {}; + uiModel.columns = []; + uiModel.primaryKeys = []; + uiModel.foreignKeys = []; + uiModel.indicies = []; + + this.metaDb.tables[tn].uiModel = uiModel; + } + } + } + } + + iterateToCacheTableColumns(schemaResults) { + for (let i = 0; i < schemaResults.length; ++i) { + const schemaRow = schemaResults[i]; + const tn = schemaRow.tn; + const col: any = {}; + + if (schemaRow.cn.split(' ').length > 1) { + // todo + // console.log('column skipped since it has a space in its name - seen it in a view'); + } else { + col.cn = schemaRow.cn; + col.op = schemaRow.op; + col.ck = schemaRow.ck; + col.dt = schemaRow.dt; + col.ct = schemaRow.ct; + col.rtn = schemaRow.rtn; + col.rcn = schemaRow.rcn; + col.nrqd = schemaRow.nrqd; + + if (this.connectionConfig.client === 'mysql') { + col.ext = schemaRow.ext; + col.cc = schemaRow.cc; + col.cdf = schemaRow.cdf; + col.clen = schemaRow.clen; + col.np = schemaRow.np; + col.ns = schemaRow.ns; + col.dp = schemaRow.dp; + } + + dataHelp.findOrInsertObjectArrayByKey( + col, + 'cn', + this.metaDb.tables[tn].columns + ); + } + } + } + + iterateToCacheTableFks(schemaResults) { + for (let i = 0; i < schemaResults.length; ++i) { + const schemaRow = schemaResults[i]; + const tn = schemaRow.tn; + + if (schemaRow.rtn) { + const fk: any = {}; + + fk.cn = schemaRow.cn; + fk.tn = schemaRow.tn; + fk.rtn = schemaRow.rtn; + fk.rcn = schemaRow.rcn; + fk.dt = schemaRow.dt; + fk.ct = schemaRow.ct; + + dataHelp.findOrInsertObjectArrayByKey( + fk, + 'cn', + this.metaDb.tables[tn].foreignKeys + ); + + // console.log(fk['rtn'],fk['rcn'],tn, schemaRow['cn'], this.metaDb.tables[tn]['foreignKeys'].length) + } + } + } + + iterateToCacheErMatrix(schemaResults) { + this.metaDb.erMatrix = []; + + // console.log('> > ',this.metaDb); + + const tablesAsArr = Object.keys(this.metaDb.tables); + const tablesIndex: any = {}; + const reverseTablesIndex: any = {}; + + console.log('< < < ', tablesAsArr); + + // get tables array indicies - helps to search + for (let i = 0; i < tablesAsArr.length; ++i) { + tablesIndex[tablesAsArr[i]] = i; + reverseTablesIndex[i] = tablesAsArr[i]; + } + + this.metaDb.erTablesAsArr = tablesAsArr; + this.metaDb.erTableIndexObj = tablesIndex; + this.metaDb.erIndexTableObj = reverseTablesIndex; + + // init the ermatrix + for (let i = 0; i < tablesAsArr.length; ++i) { + this.metaDb.erMatrix[i] = []; + for (let j = 0; j < tablesAsArr.length; ++j) { + this.metaDb.erMatrix[i].push(0); + } + } + + console.log(this.metaDb.erTableIndexObj, this.metaDb.erIndexTableObj); + + for (let i = 0; i < schemaResults.length; ++i) { + if (schemaResults[i].rtn !== null) { + // console.log(schemaResults[i]['rtn'], schemaResults[i]['tn']); + + const parentIndex = tablesIndex[schemaResults[i].rtn]; + const childIndex = tablesIndex[schemaResults[i].tn]; + this.metaDb.erMatrix[parentIndex][childIndex] = 1; + } + } + // console.log(this.metaDb.erMatrix); + } + + iterateToCacheTablePks(schemaResults) { + for (let i = 0; i < schemaResults.length; ++i) { + const schemaRow = schemaResults[i]; + const tn = schemaRow.tn; + + if (this._isColumnPrimary(schemaRow)) { + const pk: any = {}; + pk.cn = schemaRow.cn; + pk.op = schemaRow.op; + pk.ck = schemaRow.ck; + pk.dt = schemaRow.dt; + pk.ct = schemaRow.ct; + + dataHelp.findOrInsertObjectArrayByKey( + pk, + 'cn', + this.metaDb.tables[tn].primaryKeys + ); + } + } + } + + emit(data) { + log.api(data); + evt.evt.emit('UI', { + status: 0, + data: `SQL : ${data}` + }); + } + + emitW(data) { + log.warn(data); + evt.evt.emit('UI', { + status: 1, + data: `SQL : ${data}` + }); + } + + emitE(data) { + log.error(data); + evt.evt.emit('UI', { + status: -1, + data: `SQL : ${data}` + }); + } + + migrationInit(_args) {} + + async selectAll(tn) { + return await this.sqlClient(tn).select(); + } + + /** + * + * + * @param args + * @param args.tn + * @param args.fields + * @param args.limit + * @param args.where + * @param args.sort + * @param args.size + * @param args.orderBy + * @param args.page + * @return {Promise} + */ + async list(args) { + const { size = 10, page = 1, orderBy } = args; + + const result = new Result(); + + try { + const countResult = await this.sqlClient(args.tn).count(); + result.data.count = Object.values(countResult[0])[0]; + const query = this.sqlClient(args.tn) + .select() + .limit(size) + .offset((page - 1) * size); + if (orderBy && orderBy.length) + result.data.list = await query.orderBy(orderBy); + else result.data.list = await query; + } catch (e) { + console.log(e); + result.data.list = []; + } + return result; + } + + executeSqlFiles() {} + + async createDatabaseIfNotExists(_args): Promise {} + + async createTableIfNotExists(_args): Promise {} + + async raw(statements, ...args) { + const start = new Date().getTime(); + let response = null; + let end = null, + timeTaken = null; + try { + response = await this.sqlClient.raw(statements, ...args); + end = new Date().getTime(); + timeTaken = end - start; + log.api(`Query: (${statements}) [Took: ${timeTaken} ms]`); + this.emit(`${statements} [Took: ${timeTaken} ms]`); + return response; + } catch (e) { + end = new Date().getTime(); + timeTaken = end - start; + this.emitE(`${e} [Took: ${timeTaken} ms]`); + console.log(e); + throw e; + } + } + + // Todo: error handling + async insert(args) { + const { tn, data } = args; + const res = await this.sqlClient(tn).insert(data); + log.debug(res); + return res; + } + + async update(args) { + const { tn, data, whereConditions } = args; + const res = await this.sqlClient(tn) + .where(whereConditions) + .update(data); + return res; + } + + async delete(args) { + const { tn, whereConditions } = args; + const res = await this.sqlClient(tn) + .where(whereConditions) + .del(); + log.debug(res); + return res; + } + + async remove(tn, where) { + await this.sqlClient(tn) + .del() + .where(where); + } + + hasTable(_tn) {} + + hasDatabase(_databaseName) {} + + getKnexDataTypes() { + const result = new Result(); + + // result.data.list = [ + // "integer", + // "bigInteger", + // "text", + // "string", + // "float", + // "decimal", + // "boolean", + // "date", + // // "datetime", + // "time", + // "timestamp", + // "binary", + // "enu", + // "json", + // "specificType" + // ]; + + result.data.list = [ + 'int', + 'tinyint', + 'smallint', + 'mediumint', + 'bigint', + 'float', + 'decimal', + 'double', + 'real', + 'bit', + 'boolean', + 'serial', + 'date', + 'datetime', + 'timestamp', + 'time', + 'year', + 'char', + 'varchar', + 'nchar', + // "nvarchar", + 'text', + 'tinytext', + 'mediumtext', + 'longtext', + 'binary', + 'varbinary', + 'blob', + 'tinyblob', + 'mediumblob', + 'longblob', + 'enum', + 'set', + 'time', + 'geometry', + 'point', + 'linestring', + 'polygon', + 'multipoint', + 'multilinestring', + 'multipolygon', + // "geometrycollection", + 'json' + ]; + + return result; + } + + getKnexDataTypesAdvanced(args = {}) { + const result = new Result(); + + result.data.list = [ + { + type: 'int', + dtxp: '10', + dtxs: '', + aggrDataType: 'numeric', + cdf: '1' + }, + { + type: 'tinyint', + dtxp: '1', + dtxs: '', + aggrDataType: 'numeric', + cdf: '1' + }, + { + type: 'smallint', + dtxp: '5', + dtxs: '', + aggrDataType: 'numeric', + cdf: '1' + }, + { + type: 'mediumint', + dtxp: '8', + dtxs: '', + aggrDataType: 'numeric', + cdf: '1' + }, + { + type: 'bigint', + dtxp: '20', + dtxs: '', + aggrDataType: 'numeric', + cdf: '1' + }, + { + type: 'bit', + dtxp: '', + dtxs: '', + aggrDataType: 'numeric', + cdf: '1' + }, + { + type: 'boolean', + dtxp: '', + dtxs: '', + aggrDataType: 'boolean', + cdf: '' + }, + { + type: 'float', + dtxp: '10', + dtxs: '2', + aggrDataType: 'float', + cdf: '' + }, + { + type: 'decimal', + dtxp: '10', + dtxs: '2', + aggrDataType: 'float', + cdf: '' + }, + { + type: 'double', + dtxp: '10', + dtxs: '2', + aggrDataType: 'float', + cdf: '' + }, + { + type: 'serial', + dtxp: '', + dtxs: '', + aggrDataType: 'numeric', + cdf: '' + }, + { + type: 'date', + dtxp: '', + dtxs: '', + aggrDataType: 'datetime', + cdf: '' + }, + { + type: 'datetime', + dtxp: '', + dtxs: '', + aggrDataType: 'datetime', + cdf: '' + }, + { + type: 'timestamp', + dtxp: '', + dtxs: '', + aggrDataType: 'datetime', + cdf: '' + }, + { + type: 'time', + dtxp: '', + dtxs: '', + aggrDataType: 'time', + cdf: '' + }, + { + type: 'year', + dtxp: '', + dtxs: '', + aggrDataType: 'year', + cdf: '' + }, + { + type: 'char', + dtxp: '10', + dtxs: '', + aggrDataType: 'char', + cdf: '' + }, + { + type: 'varchar', + dtxp: '10', + dtxs: '', + aggrDataType: 'char', + cdf: '' + }, + { + type: 'nchar', + dtxp: '10', + dtxs: '', + aggrDataType: 'char', + cdf: '' + }, + { + type: 'text', + dtxp: '', + dtxs: '', + aggrDataType: 'text', + cdf: '' + }, + { + type: 'tinytext', + dtxp: '', + dtxs: '', + aggrDataType: 'text', + cdf: '' + }, + { + type: 'mediumtext', + dtxp: '', + dtxs: '', + aggrDataType: 'text', + cdf: '' + }, + { + type: 'longtext', + dtxp: '', + dtxs: '', + aggrDataType: 'text', + cdf: '' + }, + { + type: 'binary', + dtxp: '255', + dtxs: '', + aggrDataType: 'binary', + cdf: '' + }, + { + type: 'varbinary', + dtxp: '255', + dtxs: '', + aggrDataType: 'binary', + cdf: '' + }, + { + type: 'blob', + dtxp: '', + dtxs: '', + aggrDataType: 'blob', + cdf: '' + }, + { + type: 'tinyblob', + dtxp: '', + dtxs: '', + aggrDataType: 'blob', + cdf: '' + }, + { + type: 'mediumblob', + dtxp: '', + dtxs: '', + aggrDataType: 'blob', + cdf: '' + }, + { + type: 'longblob', + dtxp: '', + dtxs: '', + aggrDataType: 'blob', + cdf: '' + }, + { + type: 'enum', + dtxp: `'a','b'`, + dtxs: '', + aggrDataType: 'enum', + cdf: '' + }, + { + type: 'set', + dtxp: `'a','b'`, + dtxs: '', + aggrDataType: 'set', + cdf: '' + }, + { + type: 'geometry', + dtxp: '', + dtxs: '', + aggrDataType: 'geometry', + cdf: '' + }, + { + type: 'point', + dtxp: '', + dtxs: '', + aggrDataType: 'geometry', + cdf: '' + }, + { + type: 'linestring', + dtxp: '', + dtxs: '', + aggrDataType: 'geometry', + cdf: '' + }, + { + type: 'polygon', + dtxp: '', + dtxs: '', + aggrDataType: 'geometry', + cdf: '' + }, + { + type: 'multipoint', + dtxp: '', + dtxs: '', + aggrDataType: 'geometry', + cdf: '' + }, + { + type: 'multilinestring', + dtxp: '', + dtxs: '', + aggrDataType: 'geometry', + cdf: '' + }, + { + type: 'multipolygon', + dtxp: '', + dtxs: '', + aggrDataType: 'geometry', + cdf: '' + }, + { + type: 'json', + dtxp: '', + dtxs: '', + aggrDataType: 'json', + cdf: '' + } + ]; + + if (args && 'aggrDataType' in args) { + result.data.list = result.data.list.filter( + d => d.aggrDataType === args['aggrDataType'] + ); + } + + return result; + } + + // /** + // * + // * @param {Object} - args + // * @param {String} - args.tn + // * @param {Object[]} - args.columns + // * @param {String} - args.columns[].tn + // * @param {String} - args.columns[].cn + // * @param {String} - args.columns[].dt + // * @param {String} - args.columns[].np + // * @param {String} - args.columns[].ns - + // * @param {String} - args.columns[].clen - + // * @param {String} - args.columns[].dp - + // * @param {String} - args.columns[].cop - + // * @param {String} - args.columns[].pk - + // * @param {String} - args.columns[].nrqd - + // * @param {String} - args.columns[].not_nullable - + // * @param {String} - args.columns[].ct - + // * @param {String} - args.columns[].un - + // * @param {String} - args.columns[].ai - + // * @param {String} - args.columns[].unique - + // * @param {String} - args.columns[].cdf - + // * @param {String} - args.columns[].cc - + // * @param {String} - args.columns[].csn - + // * @param {String} - args.columns[].dtx + // * - value will be 'specificType' for all cols except ai + // * - for ai it will be integer, bigInteger + // * - tiny, small and medium Int auto increement is not supported + // * @param {String} - args.columns[].dtxp - to use in UI + // * @param {String} - args.columns[].dtxs - to use in UI + // * @returns {Promise<{upStatement, downStatement}>} + // */ + // async tableCreate(args) { + // const _func = this.tableCreate.name; + // const result = new Result(); + // log.api(`${_func}:args:`, args); + // + // try { + // args.table = args.tn; + // args.sqlClient = this.sqlClient; + // + // /**************** create table ****************/ + // let upQuery = createTable(args); + // await this.sqlClient.raw(upQuery); + // + // const downStatement = this.sqlClient.schema.dropTable(args.table).toSQL(); + // + // this.emit(`Success : ${upQuery}`); + // + // /**************** return files *************** */ + // result.data.object = { + // upStatement: [{sql: upQuery}], + // downStatement + // }; + // } catch (e) { + // log.ppe(e, _func); + // throw e; + // } + // + // return result; + // } + + /** + * + * @param {Object} - args + * @param {String} - args.tn + * @param {Object[]} - args.columns + * @param {String} - args.columns[].tn + * @param {String} - args.columns[].cn + * @param {String} - args.columns[].dt + * @param {String} - args.columns[].np + * @param {String} - args.columns[].ns - + * @param {String} - args.columns[].clen - + * @param {String} - args.columns[].dp - + * @param {String} - args.columns[].cop - + * @param {String} - args.columns[].pk - + * @param {String} - args.columns[].nrqd - + * @param {String} - args.columns[].not_nullable - + * @param {String} - args.columns[].ct - + * @param {String} - args.columns[].un - + * @param {String} - args.columns[].ai - + * @param {String} - args.columns[].unique - + * @param {String} - args.columns[].cdf - + * @param {String} - args.columns[].cc - + * @param {String} - args.columns[].csn - + * @param {String} - args.columns[].dtx + * - value will be 'specificType' for all cols except ai + * - for ai it will be integer, bigInteger + * - tiny, small and medium Int auto increement is not supported + * @param {String} - args.columns[].dtxp - to use in UI + * @param {String} - args.columns[].dtxs - to use in UI + * @returns {Promise<{upStatement, downStatement}>} + */ + async tableCreate(args) { + const _func = this.tableCreate.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + args.table = args.tn; + args.sqlClient = this.sqlClient; + + /** ************** create table *************** */ + await this.sqlClient.schema.createTable(args.table, function(table) { + const multiplePks = []; + const columns = JSON.parse(JSON.stringify(args.columns)); + + // copy PKs to new array and set PK metadata to false in original columns + for (let i = 0; i < columns.length; ++i) { + if (columns[i].pk) { + multiplePks.push(i); + columns[i].pk = false; + columns[i].ai = false; + } + } + + if (multiplePks.length > 1) { + for (let i = 0; i < columns.length; ++i) { + columnCreate(args.sqlClient, table, columns[i]); + } + + createPks(table, args.columns, multiplePks); + } else { + for (let i = 0; i < args.columns.length; ++i) { + columnCreate(args.sqlClient, table, args.columns[i]); + } + } + }); + + /** ************** create up & down statements *************** */ + const upStatement = this.sqlClient.schema + .createTable(args.table, function(table) { + const multiplePks = []; + const columns = JSON.parse(JSON.stringify(args.columns)); + + for (let i = 0; i < columns.length; ++i) { + if (columns[i].pk) { + multiplePks.push(i); + columns[i].pk = false; + columns[i].ai = false; + } + } + + if (multiplePks.length > 1) { + for (let i = 0; i < columns.length; ++i) { + columnCreate(args.sqlClient, table, columns[i]); + } + + createPks(table, args.columns, multiplePks); + } else { + for (let i = 0; i < args.columns.length; ++i) { + columnCreate(args.sqlClient, table, args.columns[i]); + } + } + }) + .toSQL(); + + this.emit(`Success : ${upStatement}`); + + const downStatement = this.sqlClient.schema.dropTable(args.table).toSQL(); + + /** ************** return files *************** */ + result.data.object = { + upStatement, + downStatement + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param {Object} - args + * @param {String} - args.tn + * @param {String} - args.tn_old + * @returns {Promise<{upStatement, downStatement}>} + */ + async tableRename(args) { + const _func = this.tableCreate.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + args.table = args.tn; + + /** ************** create table *************** */ + await this.sqlClient.schema.renameTable(args.tn_old, args.tn); + + /** ************** create up & down statements *************** */ + const upStatement = + this.querySeparator() + + this.sqlClient.schema.renameTable(args.tn_old, args.tn).toQuery(); + + this.emit(`Success : ${upStatement}`); + + const downStatement = + this.querySeparator() + + this.sqlClient.schema.renameTable(args.tn, args.tn_old).toQuery(); + + /** ************** return files *************** */ + result.data.object = { + upStatement: [{ sql: upStatement }], + downStatement: [{ sql: downStatement }] + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + // /** + // * + // * @param {Object} - args + // * @param {String} - args.table + // * @param {String} - args.table + // * @param {Object[]} - args.columns + // * @param {String} - args.columns[].tn + // * @param {String} - args.columns[].cn + // * @param {String} - args.columns[].dt + // * @param {String} - args.columns[].np + // * @param {String} - args.columns[].ns - + // * @param {String} - args.columns[].clen - + // * @param {String} - args.columns[].dp - + // * @param {String} - args.columns[].cop - + // * @param {String} - args.columns[].pk - + // * @param {String} - args.columns[].nrqd - + // * @param {String} - args.columns[].not_nullable - + // * @param {String} - args.columns[].ct - + // * @param {String} - args.columns[].un - + // * @param {String} - args.columns[].ai - + // * @param {String} - args.columns[].unique - + // * @param {String} - args.columns[].cdf - + // * @param {String} - args.columns[].cc - + // * @param {String} - args.columns[].csn - + // * @param {Number} - args.columns[].altered - 1,2,4 = addition,edited,deleted + // * @returns {Promise<{upStatement, downStatement}>} + // */ + // async tableUpdate(args) { + // const _func = this.tableUpdate.name; + // const result = new Result(); + // log.api(`${_func}:args:`, args); + // + // try { + // args.table = args.tn; + // const originalColumns = args.originalColumns; + // args.connectionConfig = this._connectionConfig; + // args.sqlClient = this.sqlClient; + // + // let upQuery = ""; + // let downQuery = ""; + // + // for (let i = 0; i < args.columns.length; ++i) { + // const oldColumn = lodash.find(originalColumns, { + // cn: args.columns[i].cno + // }); + // + // if (args.columns[i].altered & 4) { + // // col remove + // upQuery += alterTableRemoveColumn( + // args.columns[i], + // oldColumn, + // upQuery + // ); + // downQuery += alterTableAddColumn( + // oldColumn, + // args.columns[i], + // downQuery + // ); + // } else if (args.columns[i].altered & 2 || args.columns[i].altered & 8) { + // // col edit + // upQuery += alterTableChangeColumn( + // args.columns[i], + // oldColumn, + // upQuery + // ); + // downQuery += alterTableChangeColumn( + // oldColumn, + // args.columns[i], + // downQuery + // ); + // } else if (args.columns[i].altered & 1) { + // // col addition + // upQuery += alterTableAddColumn(args.columns[i], oldColumn, upQuery); + // downQuery += alterTableRemoveColumn( + // args.columns[i], + // oldColumn, + // downQuery + // ); + // } + // } + // + // upQuery += alterTablePK(args.columns, args.originalColumns, upQuery); + // downQuery += alterTablePK(args.originalColumns, args.columns, downQuery); + // + // if (upQuery) { + // upQuery = `ALTER TABLE ${args.columns[0].tn} ${upQuery};`; + // downQuery = `ALTER TABLE ${args.columns[0].tn} ${downQuery};`; + // } + // + // await this.sqlClient.raw(upQuery); + // + // console.log(upQuery); + // + // result.data.object = { + // upStatement: [{sql: upQuery}], + // downStatement: [{sql: downQuery}] + // }; + // } catch (e) { + // log.ppe(e, _func); + // throw e; + // } + // + // return result; + // } + + /** + * + * @param {Object} - args + * @param {String} - args.table + * @param {String} - args.table + * @param {Object[]} - args.columns + * @param {String} - args.columns[].tn + * @param {String} - args.columns[].cn + * @param {String} - args.columns[].dt + * @param {String} - args.columns[].np + * @param {String} - args.columns[].ns - + * @param {String} - args.columns[].clen - + * @param {String} - args.columns[].dp - + * @param {String} - args.columns[].cop - + * @param {String} - args.columns[].pk - + * @param {String} - args.columns[].nrqd - + * @param {String} - args.columns[].not_nullable - + * @param {String} - args.columns[].ct - + * @param {String} - args.columns[].un - + * @param {String} - args.columns[].ai - + * @param {String} - args.columns[].unique - + * @param {String} - args.columns[].cdf - + * @param {String} - args.columns[].cc - + * @param {String} - args.columns[].csn - + * @param {Number} - args.columns[].altered - 1,2,4 = addition,edited,deleted + * @returns {Promise<{upStatement, downStatement}>} + */ + async tableUpdate(args) { + const _func = this.tableUpdate.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + let renamed = false; + let renamedUpDownStatement: any = {}; + + try { + args.table = args.tn; + const originalColumns = args.originalColumns; + args.connectionConfig = this._connectionConfig; + args.sqlClient = this.sqlClient; + + /** ************** START : has PK changed *************** */ + const numOfPksInOriginal = []; + const numOfPksInNew = []; + let pksChanged = 0; // > 1 for new pk and < 1 for pl removed + + for (let i = 0; i < args.columns.length; ++i) { + if (args.columns[i].pk) { + numOfPksInNew.push(i); + } + } + + for (let i = 0; i < args.originalColumns.length; ++i) { + if (args.originalColumns[i].pk) { + numOfPksInOriginal.push(i); + } + } + + if (numOfPksInNew.length > numOfPksInOriginal.length) { + /** + * new primary key + */ + pksChanged = numOfPksInNew.length - numOfPksInOriginal.length; + } else if (numOfPksInNew.length < numOfPksInOriginal.length) { + /** + * primary key removed + */ + pksChanged = numOfPksInNew.length - numOfPksInOriginal.length; + } else { + /** + * check if the pks are same + * exchanging one pk for another - we will catch it here + */ + for (let i = 0; i < numOfPksInNew.length; ++i) { + if (numOfPksInNew[i] !== numOfPksInOriginal[i]) { + pksChanged = 1; + } + } + } + + console.log('pksChanged:', pksChanged); + /** ************** END : has PK changed *************** */ + + /** ************** update table *************** */ + await this.sqlClient.schema.alterTable(args.table, function(table) { + if (pksChanged) { + pkUpdate(table, args.columns, args.originalColumns); + } else { + for (let i = 0; i < args.columns.length; ++i) { + const column = lodash.find(originalColumns, { + cn: args.columns[i].cno + }); + + if (args.columns[i].altered & 8) { + // TODO: If a column is getting renamed then + // any change to column data type + // or changes to other columns are ignored + renamed = true; + renamedUpDownStatement = renameColumn( + args.sqlClient, + args.connectionConfig, + table, + column, + args.columns[i] + ); + result.data.object = renamedUpDownStatement; + } else if (args.columns[i].altered & 4) { + // col remove + removeColumn(table, args.columns[i], column); + } else if (args.columns[i].altered & 2) { + // col edit + columnUpdate(args.sqlClient, table, args.columns[i], column); + } else if (args.columns[i].altered & 1) { + // col addition + columnUpdate(args.sqlClient, table, args.columns[i], null); + } + } + } + }); + + // log.debug('after column change', r); + + if (renamed) { + await this.sqlClient.raw(renamedUpDownStatement.upStatement); + result.data.object = renamedUpDownStatement; + } else { + /** ************** create up & down statements *************** */ + const upStatement = this.sqlClient.schema + .alterTable(args.table, function(table) { + if (pksChanged) { + pkUpdate(table, args.columns, args.originalColumns); + } else { + for (let i = 0; i < args.columns.length; ++i) { + const column = lodash.find(originalColumns, { + cn: args.columns[i].cno + }); + if (args.columns[i].altered & 8) { + // col remove + // renameColumn(this.sqlClient, + // this.connectionConfig, + // table, + // args.columns[i].cno, + // args.columns[i].cn); + } else if (args.columns[i].altered & 4) { + // col remove + removeColumn(table, args.columns[i], column); + } else if (args.columns[i].altered & 2) { + // col edit + columnUpdate(args.sqlClient, table, args.columns[i], column); + } else if (args.columns[i].altered & 1) { + // col addition + columnUpdate(args.sqlClient, table, args.columns[i], null); + } + } + } + }) + .toSQL(); + // this.emit(`Success : ${upStatement}`) + + // log.debug(upStatement); + + const downStatement = this.sqlClient.schema + .alterTable(args.table, function(table) { + if (pksChanged) { + pkUpdate(table, args.columns, args.originalColumns); + } else { + for (let i = 0; i < args.columns.length; ++i) { + const column = lodash.find(originalColumns, { + cn: args.columns[i].cno + }); + if (args.columns[i].altered & 8) { + // col remove + // renameColumn(this.sqlClient, + // this.connectionConfig, + // table, + // args.columns[i].cno, + // args.columns[i].cn); + } else if (args.columns[i].altered & 4) { + // col remove reverse + columnUpdate(args.sqlClient, table, column, null); + } else if (args.columns[i].altered & 1) { + // col addition reverse + removeColumn(table, args.columns[i], null); + } else if (args.columns[i].altered & 2) { + // col edit reverse + columnUpdate(args.sqlClient, table, column, args.columns[i]); + } + } + } + }) + .toSQL(); + + result.data.object = { + upStatement, + downStatement + }; + } + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + // /** + // * + // * @param {Object} - args + // * @param args.tn + // * @returns {Promise<{upStatement, downStatement}>} + // */ + // async tableDelete(args) { + // const _func = this.tableDelete.name; + // const result = new Result(); + // log.api(`${_func}:args:`, args); + // try { + // const {columns} = args; + // args.sqlClient = this.sqlClient; + // + // /** ************** create up & down statements *************** */ + // const upStatement = this.sqlClient.schema + // .dropTable(args.tn) + // .toSQL(); + // const downQuery = createTable(args); + // + // this.emit(`Success : ${upStatement}`); + // + // /** ************** drop tn *************** */ + // await this.sqlClient.schema.dropTable(args.tn); + // + // /** ************** return files *************** */ + // result.data.object = { + // upStatement, + // downStatement : [{sql: downQuery}] + // }; + // } catch (e) { + // log.ppe(e, _func); + // throw e; + // } + // + // return result; + // } + + /** + * + * @param {Object} - args + * @param args.tn + * @returns {Promise<{upStatement, downStatement}>} + */ + async tableDelete(args) { + const _func = this.tableDelete.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + const { columns } = args; + args.sqlClient = this.sqlClient; + + /** ************** create up & down statements *************** */ + const upStatement = this.sqlClient.schema.dropTable(args.tn).toSQL(); + const downStatement = this.sqlClient.schema + .createTable(args.tn, function(tn) { + for (let i = 0; i < columns.length; ++i) { + columnCreate(args.sqlClient, tn, columns[i]); + } + }) + .toSQL(); + + this.emit(`Success : ${upStatement}`); + + /** ************** drop tn *************** */ + await this.sqlClient.schema.dropTable(args.tn); + + /** ************** return files *************** */ + result.data.object = { + upStatement, + downStatement + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param {Object} - args + * @param {String} - args.tn + * @param {String} - args.indexName + * @param {String} - args.non_unique + * @param {String[]} - args.columns + * @returns {Promise<{upStatement, downStatement}>} + */ + async indexCreate(args) { + const _func = this.indexCreate.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + const indexName = args.indexName || null; + + try { + args.table = args.tn; + + // s = await this.sqlClient.schema.index(Object.keys(args.columns)); + await this.sqlClient.schema.table(args.table, function(table) { + if (args.non_unique) { + table.index(args.columns, indexName); + } else { + table.unique(args.columns, indexName); + } + }); + + const upStatement = + this.querySeparator() + + this.sqlClient.schema + .table(args.table, function(table) { + if (args.non_unique) { + table.index(args.columns, indexName); + } else { + table.unique(args.columns, indexName); + } + }) + .toQuery(); + + this.emit(`Success : ${upStatement}`); + + const downStatement = + this.querySeparator() + + this.sqlClient.schema + .table(args.table, function(table) { + if (args.non_unique) { + table.dropIndex(args.columns, indexName); + } else { + table.dropUnique(args.columns, indexName); + } + }) + .toQuery(); + + result.data.object = { + upStatement: [{ sql: upStatement }], + downStatement: [{ sql: downStatement }] + }; + + // result.data.object = { + // upStatement, + // downStatement + // }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param {Object} - args + * @param {String} - args.tn + * @param {String[]} - args.columns + * @param {String} - args.indexName + * @param {String} - args.non_unique + * @returns {Promise<{upStatement, downStatement}>} + */ + async indexDelete(args) { + const _func = this.indexDelete.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + const indexName = args.indexName || null; + + try { + args.table = args.tn; + + // s = await this.sqlClient.schema.index(Object.keys(args.columns)); + await this.sqlClient.schema.table(args.table, function(table) { + if (args.non_unique_original) { + table.dropIndex(args.columns, indexName); + } else { + table.dropUnique(args.columns, indexName); + } + }); + + const upStatement = + this.querySeparator() + + this.sqlClient.schema + .table(args.table, function(table) { + if (args.non_unique_original) { + table.dropIndex(args.columns, indexName); + } else { + table.dropUnique(args.columns, indexName); + } + }) + .toQuery(); + + this.emit(`Success : ${upStatement}`); + + const downStatement = + this.querySeparator() + + this.sqlClient.schema + .table(args.table, function(table) { + if (args.non_unique_original) { + table.index(args.columns, indexName); + } else { + table.unique(args.columns, indexName); + } + }) + .toQuery(); + + result.data.object = { + upStatement: [{ sql: upStatement }], + downStatement: [{ sql: downStatement }] + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param {Object} - args + * @param {String} - args.parentTable + * @param {String} - args.parentColumn + * @param {String} - args.childColumn + * @param {String} - args.childTable + * @returns {Promise<{upStatement, downStatement}>} + */ + async relationCreate(args) { + const _func = this.relationCreate.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + const foreignKeyName = args.foreignKeyName || null; + + try { + // s = await this.sqlClient.schema.index(Object.keys(args.columns)); + + await this.sqlClient.schema.table(args.childTable, function(table) { + table = table + .foreign(args.childColumn, foreignKeyName) + .references(args.parentColumn) + .on(args.parentTable); + + if (args.onUpdate) { + table = table.onUpdate(args.onUpdate); + } + if (args.onDelete) { + table = table.onDelete(args.onDelete); + } + }); + + const upStatement = + this.querySeparator() + + (await this.sqlClient.schema + .table(args.childTable, function(table) { + table = table + .foreign(args.childColumn, foreignKeyName) + .references(args.parentColumn) + .on(args.parentTable); + + if (args.onUpdate) { + table = table.onUpdate(args.onUpdate); + } + if (args.onDelete) { + table = table.onDelete(args.onDelete); + } + }) + .toQuery()); + + this.emit(`Success : ${upStatement}`); + + const downStatement = + this.querySeparator() + + this.sqlClient.schema + .table(args.childTable, function(table) { + table = table.dropForeign(args.childColumn, foreignKeyName); + }) + .toQuery(); + + // let files = this.evolutionFilesCreate(args, upStatement, downStatement); + // + // // create a migration file + // //let files = this.evolutionForCreateRel(args); + // + // await this.sqlClient('xc_evolutions').insert({ + // title: files.up, + // titleDown: files.down, + // description: '', + // status: 0 + // //created: Date.now() + // }) + + result.data.object = { + upStatement: [{ sql: upStatement }], + downStatement: [{ sql: downStatement }] + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param {Object} - args + * @param {String} - args.parentTable + * @param {String} - args.parentColumn + * @param {String} - args.childColumn + * @param {String} - args.childTable + * @param {String} - args.foreignKeyName + * @returns {Promise<{upStatement, downStatement}>} + */ + async relationDelete(args) { + const _func = this.relationDelete.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + const foreignKeyName = args.foreignKeyName || null; + + try { + // const self = this; + + await this.sqlClient.schema.table(args.childTable, function(table) { + table.dropForeign(args.childColumn, foreignKeyName); + }); + + const upStatement = + this.querySeparator() + + this.sqlClient.schema + .table(args.childTable, function(table) { + table.dropForeign(args.childColumn, foreignKeyName); + }) + .toQuery(); + + const downStatement = + this.querySeparator() + + (await this.sqlClient.schema + .table(args.childTable, function(table) { + table + .foreign(args.childColumn, foreignKeyName) + .references(args.parentColumn) + .on(args.parentTable); + }) + .toQuery()); + + // let files = this.evolutionFilesCreate(args, upStatement, downStatement); + // + // // create a migration file + // //let files = this.evolutionForCreateRel(args); + // + // await this.sqlClient('xc_evolutions').insert({ + // title: files.up, + // titleDown: files.down, + // description: '', + // status: 0 + // //created: Date.now() + // }) + + result.data.object = { + upStatement: [{ sql: upStatement }], + downStatement: [{ sql: downStatement }] + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + getKnexDataType(dt) { + try { + // const Type = dt; + // + // const list = [ + // 'integer', + // 'bigInteger', + // 'text', + // 'string', + // 'float', + // 'decimal', + // 'boolean', + // 'date', + // 'datetime', + // 'time', + // 'timestamp', + // 'binary', + // 'enu', + // 'json', + // 'specificType' + // ]; + + switch (dt) { + case 'int': + return 'integer'; + break; + + case 'bigint': + return 'bigInteger'; + break; + + case 'varchar': + return 'string'; + break; + + case 'text': + case 'float': + case 'decimal': + case 'boolean': + case 'date': + case 'datetime': + case 'time': + case 'timestamp': + case 'binary': + case 'json': + return dt; + break; + + case 'enum': + return 'enu'; + break; + + default: + return 'specificType'; + break; + } + + // // TODO: Im sure there are more types to handle here + // const strTypes = [ + // "varchar", + // "text", + // "char", + // "tinytext", + // "mediumtext", + // "longtext", + // "ntext", + // "image", + // "blob", + // "mediumblob", + // "longblob", + // "binary", + // "varbinary", + // "character", + // "character varying", + // "nchar", + // "nvarchar", + // "clob", + // "nvarchar2", + // "varchar2", + // "raw", + // "long raw", + // "bfile", + // "nclob" + // ]; + // const intTypes = [ + // "bit", + // "integer", + // "int", + // "smallint", + // "mediumint", + // "bigint", + // "tinyint", + // "int2", + // "int4", + // "int8", + // "long", + // "serial", + // "bigserial", + // "smallserial", + // "bool", + // "boolean", + // "number" + // ]; + // const floatTypes = [ + // "float", + // "double", + // "decimal", + // "numeric", + // "real", + // "double precision", + // "real", + // "money", + // "smallmoney", + // "dec" + // ]; + // const dateTypes = [ + // "date", + // "datetime", + // "timestamp", + // "time", + // "year", + // "timestamp without time zone", + // "timestamp with time zone", + // "time without time zone", + // "time with time zone", + // "datetime2", + // "smalldatetime", + // "datetimeoffset", + // "interval year", + // "interval day" + // ]; + // + // let knexDataType = {}; + // + // if (this.getType(Type, strTypes)) { + // knexDataType = "string"; + // } else if (this.getType(Type, intTypes)) { + // knexDataType = "integer"; + // } else if (this.getType(Type, floatTypes)) { + // knexDataType = "float"; + // } else if (this.getType(Type, dateTypes)) { + // knexDataType = "date"; + // } else { + // knexDataType = "string"; + // } + // + // return knexDataType; + } catch (e) { + log.debug(e); + return 'string'; + } + } + + getType(colType, typesArr) { + for (let i = 0; i < typesArr.length; ++i) { + // if (typesArr[i].indexOf(colType) !== -1) { + // return 1; + // } + + if (colType.indexOf(typesArr[i]) !== -1) { + return 1; + } + } + return 0; + } + + // async tableRowList(args = {}) { + // let results = []; + + // if (args.where) { + // results = await this.sqlClient.from(args.tn).where(args.where); + // } else { + // results = await this.sqlClient.from(args.tn); + // } + + // return results; + // } + + // async tableRowRead(args = {}) { + // const results = await this.sqlClient + // .select() + // .from(args.tn) + // .where(args.where); + // return results; + // } + + // async tableRowCreate(args = {}) { + // const value = await this.sqlClient(args.tn).insert((args = {})); + // args.id = value[0]; + // return args; + // } + + // async tableRowUpdate(args = {}) { + // const result = await this.sqlClient(args.tn) + // .update(args.data) + // .where(args.where); + // return result; + // } + + // async tableRowRemove(args = {}) { + // const result = await this.sqlClient(args.tn) + // .where(args.where) + // .del(); + // return result; + // } + + /** ************** START : faker functions *************** */ + /** + * + * @param args + * @param args.seedsFolder + * @param args.tn + * @returns {Promise} + */ + async fakerColumnsList(args) { + const _func = this.fakerColumnsList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const fakerColumnPath = path.join(args.seedsFolder, `${args.tn}.json`); + + const data = await promisify(jsonfile.readFile)(fakerColumnPath); + + // this.emit(`Success :`) + + result.data.list = data; + } catch (e) { + log.ppe(e, _func); + // throw e; + result.data.list = []; + } + + return result; + } + + /** + * + * @param args + * @param args.seedsFolder + * @param args.tn + * @param args.fakerColumns + * @returns {Promise} + */ + async fakerColumnsCreate(args) { + const _func = this.fakerColumnsCreate.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const fakerColumnPath = path.join(args.seedsFolder, `${args.tn}.json`); + + await promisify(jsonfile.writeFile)(fakerColumnPath, args.fakerColumns, { + spaces: 2 + }); + + this.emit(`Created : ${fakerColumnPath}`); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + async fakerColumnsUpdate(args) { + const _func = this.fakerColumnsUpdate.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + this.emit(`Success :`); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + async fakerColumnsDelete(args) { + const _func = this.fakerColumnsDelete.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + this.emit(`Success :`); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** ************** END : faker functions *************** */ + + /** ************** START : seed functions *************** */ + + async _getmetaDb() {} + + /** + * + * @param args + * @param args.seedsFolder + * @param args.seedsFolder + * @returns {Promise} + */ + async seedInit(args) { + const _func = this.seedInit.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + console.log('in knex SeedInit'); + + try { + mkdirp.sync(args.seedsFolder); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + async seedTerm(args) { + const _func = this.seedInit.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + this.emit(`Success : seedTerm`); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.seedsFolder + * @returns {Promise} + */ + async seedStart(args) { + const _func = this.seedStart.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + this.emit(`Seeding : Started`); + + await this.mockDb(args); + this.emit(`Seeding : Finished`); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + async seedStop(args) { + const _func = this.seedStop.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + this.emit(`Success : seedStop`); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.seedsFolder + * @returns {Promise} + * @returns {result.data} - json file content + */ + async seedSettingsRead(args) { + const _func = this.seedSettingsRead.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + // this.emit(`Success : seedSettingsRead`) + const seedSettings = path.join(args.seedsFolder, '__xseeds.json'); + const data = await promisify(jsonfile.readFile)(seedSettings); + result.data = data; + } catch (e) { + log.ppe(e, _func); + // throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.seedsFolder + * @param args.settings + * @returns {Promise} + */ + async seedSettingsCreate(args) { + const _func = this.seedSettingsCreate.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const seedSettings = path.join(args.seedsFolder, '__xseeds.json'); + + await promisify(jsonfile.writeFile)(seedSettings, args.settings, { + spaces: 2 + }); + this.emit(`Success : Seed settings updated`); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + querySeparator() { + return '/* xc */\n'; + } + + getTnPath(t) { + return t; + } + + /** ************** END : seed functions *************** */ + + async tableCreateStatement(_args): Promise {} + + async tableInsertStatement(_args): Promise {} + + async tableUpdateStatement(_args): Promise {} + + async tableDeleteStatement(_args): Promise {} + + async tableTruncateStatement(_args): Promise {} + + async tableSelectStatement(_args): Promise {} + + async sequelizeModelCreate(_args): Promise {} + + genQuery(query, args = [], shouldSanitize: any = 0) { + if (shouldSanitize) { + args = (args || []).map(s => + typeof s === 'string' ? this.sanitize(s) : s + ); + } + const rawQuery = this.sqlClient.raw(query, args).toQuery(); + return shouldSanitize ? this.sanitize(rawQuery) : this.unsanitize(rawQuery); + } + + sanitize(str) { + return str.replace(/([^\\]|^)(\?+)/g, (_, m1, m2) => { + return `${m1}${m2.split('?').join('\\?')}`; + }); + } + + unsanitize(str) { + return str.replace(/\\[?]/g, '?'); + } +} + +// expose class +export default KnexClient; diff --git a/packages/nocodb/src/lib/db/sql-client/lib/SqlClient.ts b/packages/nocodb/src/lib/db/sql-client/lib/SqlClient.ts new file mode 100644 index 0000000000..b716b3c508 --- /dev/null +++ b/packages/nocodb/src/lib/db/sql-client/lib/SqlClient.ts @@ -0,0 +1,114 @@ +class SqlClient { + protected connectionConfig: any; + protected sqlClient: any; + + constructor(connectionConfig) { + this.connectionConfig = connectionConfig; + this.sqlClient = null; + } + + async testConnection(_args): Promise {} + + migrationInit(_args) {} + + migrationUp(_args) {} + + migrationDown(_args) {} + + selectAll(_tn) {} + + executeSqlFiles() {} + + async createDatabaseIfNotExists(_args): Promise {} + + async createTableIfNotExists(_args): Promise {} + + startTransaction() {} + + commit() {} + + rollback() {} + + hasTable(_tn) {} + + hasDatabase(_databaseName) {} + + async tableCreate(_args): Promise {} + + async tableUpdate(_args): Promise {} + + async columnCreate(_args): Promise {} + + async columnUpdate(_args): Promise {} + + async columnDelete(_args): Promise {} + + async indexCreate(_args): Promise {} + + async indexUpdate(_args): Promise {} + + async indexDelete(_args): Promise {} + + async relationCreate(_args): Promise {} + + async relationUpdate(_args): Promise {} + + async relationDelete(_args): Promise {} + + async databaseList(_args): Promise {} + + async tableList(_args): Promise {} + + async schemaList(_args): Promise {} + + async tableDelete(_args): Promise {} + + async columnList(_args): Promise {} + + async indexList(_args): Promise {} + + async relationList(_args): Promise {} + + async schemaCreate(_args): Promise {} + + async schemaDelete(_args): Promise {} + + async triggerList(_args): Promise {} + + async triggerCreate(_args): Promise {} + + async triggerRead(_args): Promise {} + + async functionList(_args): Promise {} + + async functionRead(_args): Promise {} + + async procedureList(_args): Promise {} + + async procedureRead(_args): Promise {} + + async viewList(_args): Promise {} + + async viewRead(_args): Promise {} + + async sequenceList(_args = {}): Promise {} + async sequenceCreate(_args = {}): Promise {} + async sequenceUpdate(_args = {}): Promise {} + async sequenceDelete(_args = {}): Promise {} + + async tableCreateStatement(_args): Promise {} + + async tableInsertStatement(_args): Promise {} + + async tableUpdateStatement(_args): Promise {} + + async tableDeleteStatement(_args): Promise {} + + async tableTruncateStatement(_args): Promise {} + + async tableSelectStatement(_args): Promise {} + + async totalRecords(_args): Promise {} +} + +export default SqlClient; diff --git a/packages/nocodb/src/lib/db/sql-client/lib/SqlClientFactory.ts b/packages/nocodb/src/lib/db/sql-client/lib/SqlClientFactory.ts new file mode 100644 index 0000000000..cbc5d22a6c --- /dev/null +++ b/packages/nocodb/src/lib/db/sql-client/lib/SqlClientFactory.ts @@ -0,0 +1,40 @@ +import MySqlClient from './mysql/MysqlClient'; +import MssqlClient from './mssql/MssqlClient'; +import OracleClient from './oracle/OracleClient'; +import SqliteClient from './sqlite/SqliteClient'; +import PgClient from './pg/PgClient'; +import YugabyteClient from './pg/YugabyteClient'; +import TidbClient from './mysql/TidbClient'; +import VitessClient from './mysql/VitessClient'; + +class SqlClientFactory { + static create(connectionConfig) { + connectionConfig.meta = connectionConfig.meta || {}; + connectionConfig.pool = connectionConfig.pool || { min: 0, max: 5 }; + connectionConfig.meta.dbtype = connectionConfig.meta.dbtype || ''; + if ( + connectionConfig.client === 'mysql' || + connectionConfig.client === 'mysql2' + ) { + if (connectionConfig.meta.dbtype === 'tidb') + return new TidbClient(connectionConfig); + if (connectionConfig.meta.dbtype === 'vitess') + return new VitessClient(connectionConfig); + return new MySqlClient(connectionConfig); + } else if (connectionConfig.client === 'sqlite3') { + return new SqliteClient(connectionConfig); + } else if (connectionConfig.client === 'mssql') { + return new MssqlClient(connectionConfig); + } else if (connectionConfig.client === 'oracledb') { + return new OracleClient(connectionConfig); + } else if (connectionConfig.client === 'pg') { + if (connectionConfig.meta.dbtype === 'yugabyte') + return new YugabyteClient(connectionConfig); + return new PgClient(connectionConfig); + } + + throw new Error('Database not supported'); + } +} + +export default SqlClientFactory; diff --git a/packages/nocodb/src/lib/db/sql-client/lib/data.helper.ts b/packages/nocodb/src/lib/db/sql-client/lib/data.helper.ts new file mode 100644 index 0000000000..1dd935f620 --- /dev/null +++ b/packages/nocodb/src/lib/db/sql-client/lib/data.helper.ts @@ -0,0 +1,267 @@ +export const findOrInsertObjectArrayByKey = (obj, key, array) => { + let found = 0; + let i = 0; + + for (i = 0; i < array.length; ++i) { + if (key in array[i]) { + if (obj[key] === array[i][key]) { + found = 1; + break; + } + } + } + + if (!found) { + array.push(obj); + } + + return array[i]; +}; + +export const findObjectInArrayByKey = (key, value, objArray) => { + for (let i = 0; i < objArray.length; ++i) { + if (objArray[i][key] === value) { + return objArray[i]; + } + } + + return null; +}; + +export const round = function(number, precision) { + const factor = Math.pow(10, precision); + const tempNumber = number * factor; + const roundedTempNumber = Math.round(tempNumber); + return roundedTempNumber / factor; +}; + +export const numberRound = (number, precision) => { + const factor = Math.pow(10, precision); + const tempNumber = number * factor; + const roundedTempNumber = Math.round(tempNumber); + return roundedTempNumber / factor; +}; + +export const numberGetLength = number => { + let n = number; + + if (number < 0) { + n = n * -1; + } + + return n.toString().length; +}; + +export const numberGetFixed = number => { + //console.log(number, typeof number); + return parseInt(number.toFixed()); +}; + +export const getStepArraySimple = function(min, max, step) { + const arr = []; + for (let i = min; i <= max; i = i + step) { + arr.push(i); + } + + return arr; +}; + +export const getStepArray = (min, max, stddev) => { + // console.log(' = = = = = = = '); + //console.log('original numbers', min, max, stddev); + + min = numberGetFixed(min); + max = numberGetFixed(max); + stddev = numberGetFixed(stddev); + + // console.log('fixed numbers', min, max, stddev); + + let minMinusHalf = min - stddev / 2; + let maxMinusHalf = max + stddev / 2; + + minMinusHalf = numberGetFixed(minMinusHalf); + maxMinusHalf = numberGetFixed(maxMinusHalf); + + // console.log('fixed numbers + (min,max)', min, max, stddev, '(', minMinusHalf, ',', maxMinusHalf, ')'); + // console.log('numbers length', 'min', numberGetLength(min), 'max', numberGetLength(max), 'stddev', numberGetLength(stddev)); + + const minLen = numberGetLength(minMinusHalf); + const maxLen = numberGetLength(maxMinusHalf); + const stddevLen = numberGetLength(stddev); + // + // console.log('- - - -'); + // console.log('Range', 'min', numberRound(minMinusHalf, -1)); + // console.log('Range', 'max', numberRound(maxMinusHalf, -1)); + // console.log('Range', 'stddev', numberRound(stddev, -1)); + + if (minLen > 1) minMinusHalf = numberRound(minMinusHalf, -1); + + if (maxLen > 2) maxMinusHalf = numberRound(maxMinusHalf, -1); + + if (stddevLen !== 1) stddev = numberRound(stddev, -1); + + const arr = []; + for (let step = minMinusHalf; step < maxMinusHalf; step = step + stddev) { + arr.push(step); + } + arr.push(maxMinusHalf); + + // console.log(arr); + + return arr; +}; + +export const getMysqlSchemaQuery = function() { + return ( + 'select ' + + 'c.table_name as tn, c.column_name as cn, c.ordinal_position as cop,' + + 'c.column_key as ck,c.is_nullable as nrqd, c.data_type, c.column_type as ct,c.extra as ext,c.privileges as priv, ' + + 'c.cc,c.cdf as cdf,c.data_type,' + + 'c.character_maximum_length as clen,c.numeric_precision as np,c.numeric_scale as ns,c.datetime_precision as dp, ' + + 'k.constraint_name as cstn, k.referenced_table_name as rtn, k.referenced_column_name as rcn, ' + + 's.index_name,s.seq_in_index, ' + + 'v.table_name as is_view ' + + 'from ' + + 'information_schema.columns as c ' + + 'left join ' + + 'information_schema.key_column_usage as k ' + + 'on ' + + 'c.column_name=k.column_name and ' + + 'c.table_schema = k.referenced_table_schema and ' + + 'c.table_name = k.table_name ' + + 'left join ' + + 'information_schema.statistics as s ' + + 'on ' + + 'c.column_name = s.column_name and ' + + 'c.table_schema = s.index_schema and ' + + 'c.table_name = s.table_name ' + + 'LEFT JOIN ' + + 'information_schema.VIEWS as v ' + + 'ON ' + + 'c.table_schema = v.table_schema and ' + + 'c.table_name = v.table_name ' + + 'where ' + + 'c.table_schema=? ' + + 'order by ' + + 'c.table_name, c.ordinal_position' + ); +}; + +export const getChartQuery = function() { + return 'select ? as ??, count(*) as _count from ?? where ?? between ? and ? '; +}; + +export const getDataType = function(colType, typesArr) { + // console.log(colType,typesArr); + for (let i = 0; i < typesArr.length; ++i) { + if (colType.indexOf(typesArr[i]) !== -1) { + return 1; + } + } + return 0; +}; + +export const getColumnType = function(column) { + const strTypes = [ + 'varchar', + 'text', + 'char', + 'tinytext', + 'mediumtext', + 'longtext', + 'ntext', + 'image', + 'blob', + 'mediumblob', + 'longblob', + 'binary', + 'varbinary', + 'character', + 'character varying', + 'nchar', + 'nvarchar', + 'clob', + 'nvarchar2', + 'varchar2', + 'raw', + 'long raw', + 'bfile', + 'nclob' + ]; + const intTypes = [ + 'bit', + 'integer', + 'int', + 'smallint', + 'mediumint', + 'bigint', + 'tinyint', + 'int2', + 'int4', + 'int8', + 'long', + 'serial', + 'bigserial', + 'smallserial', + 'bool', + 'boolean', + 'number' + ]; + const floatTypes = [ + 'float', + 'double', + 'decimal', + 'numeric', + 'real', + 'double precision', + 'real', + 'money', + 'smallmoney', + 'dec' + ]; + const dateTypes = [ + 'date', + 'datetime', + 'timestamp', + 'time', + 'year', + 'timestamp without time zone', + 'timestamp with time zone', + 'time without time zone', + 'time with time zone', + 'datetime2', + 'smalldatetime', + 'datetimeoffset', + 'interval year', + 'interval day' + ]; + + // const rowIds = ['rowId', 'urowid']; + + //console.log(column); + if (getDataType(column['data_type'], strTypes)) { + return 'string'; + } else if (getDataType(column['data_type'], intTypes)) { + return 'int'; + } else if (getDataType(column['data_type'], floatTypes)) { + return 'float'; + } else if (getDataType(column['data_type'], dateTypes)) { + return 'date'; + } else { + return 'string'; + } +}; + +export const getType = function(colType, typesArr) { + // for (let i = 0; i < typesArr.length; ++i) { + // // if (typesArr[i].indexOf(colType) !== -1) { + // // return 1; + // // } + // + // if (colType.indexOf(typesArr[i]) !== -1) { + // return 1; + // } + // } + return typesArr.includes(colType); + //return 0; +}; diff --git a/packages/nocodb/src/lib/db/sql-client/lib/mssql/MssqlClient.ts b/packages/nocodb/src/lib/db/sql-client/lib/mssql/MssqlClient.ts new file mode 100644 index 0000000000..f5176823f7 --- /dev/null +++ b/packages/nocodb/src/lib/db/sql-client/lib/mssql/MssqlClient.ts @@ -0,0 +1,2675 @@ +import { ColumnType } from 'nocodb-sdk'; + +import knex from 'knex'; +import KnexClient from '../KnexClient'; +import Debug from '../../../util/Debug'; +import Result from '../../../util/Result'; +import lodash from 'lodash'; + +const log = new Debug('MssqlClient'); + +class MssqlClient extends KnexClient { + constructor(connectionConfig: any) { + super(connectionConfig); + } + + /** + * + * @param {Object} - args + * @param args.sequence_name + * @returns {Promise<{upStatement, downStatement}>} + */ + async sequenceDelete(args: { sequence_name: string }) { + const _func = this.sequenceDelete.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const query = + this.querySeparator() + `DROP SEQUENCE ${args.sequence_name}`; + await this.sqlClient.raw(query); + result.data.object = { + upStatement: [{ sql: query }], + downStatement: [ + { + sql: this.querySeparator() + `CREATE SEQUENCE ${args.sequence_name}` + } + ] + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @returns {Object[]} - sequences + * @property {String} - sequences[].sequence_name + * @property {String} - sequences[].type + * @property {String} - sequences[].definer + * @property {String} - sequences[].modified + * @property {String} - sequences[].created + * @property {String} - sequences[].security_type + * @property {String} - sequences[].comment + * @property {String} - sequences[].character_set_client + * @property {String} - sequences[].collation_connection + * @property {String} - sequences[].database collation + */ + async sequenceList(args: { databaseName: string }) { + const _func = this.sequenceList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.database; + const rows = await this.sqlClient.raw(`SELECT * FROM sys.SEQUENCES;`); + + result.data.list = rows.map(seq => { + return { + ...seq, + sequence_name: seq.name, + original_sequence_name: seq.name + }; + }); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.sequence_name + * @param {String} - args.start_value + * @param {String} - args.min_value + * @param {String} - args.max_value + * @param {String} - args.increment_by + * @returns {Object} - result + */ + async sequenceCreate(args: { + sequence_name: string; + start_value: number; + min_value: number; + max_value: number; + increment_by: number; + }) { + const func = this.sequenceCreate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const query = + this.querySeparator() + `CREATE SEQUENCE ${args.sequence_name}`; + await this.sqlClient.raw(query); + result.data.object = { + upStatement: [{ sql: query }], + downStatement: [ + { sql: this.querySeparator() + `DROP SEQUENCE ${args.sequence_name}` } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.sequence_name + * @param {String} - args.start_value + * @param {String} - args.min_value + * @param {String} - args.max_value + * @param {String} - args.increment_by + * @returns {Object} - result + */ + async sequenceUpdate(args: { + original_sequence_name: string; + sequence_name: string; + start_value: number; + min_value: number; + max_value: number; + increment_by: number; + }) { + const func = this.sequenceUpdate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const upQuery = + this.querySeparator() + + `ALTER SEQUENCE ${args.original_sequence_name} RENAME TO ${args.sequence_name};`; + const downQuery = + this.querySeparator() + + `ALTER SEQUENCE ${args.sequence_name} RENAME TO ${args.original_sequence_name};`; + + await this.sqlClient.raw(upQuery); + result.data.object = { + upStatement: [{ sql: upQuery }], + downStatement: [{ sql: downQuery }] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * @param {Object} args + * @returns {Object} result + * @returns {Number} code + * @returns {String} message + */ + async testConnection(args: any = {}) { + const _func = this.testConnection.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + await this.sqlClient.raw('SELECT 1+1 as data'); + } catch (e) { + log.ppe(e); + result.code = -1; + result.message = e.message; + } finally { + log.api(`${_func}:result:`, result); + } + + return result; + } + + getKnexDataTypes() { + const result = new Result(); + + result.data.list = [ + 'bigint', + 'binary', + 'bit', + 'char', + 'date', + 'datetime', + 'datetime2', + 'datetimeoffset', + 'decimal', + 'float', + 'geography', + 'geometry', + 'heirarchyid', + 'image', + 'int', + 'money', + 'nchar', + 'ntext', + 'numeric', + 'nvarchar', + 'real', + 'json', + 'smalldatetime', + 'smallint', + 'smallmoney', + 'sql_variant', + 'sysname', + 'text', + 'time', + 'timestamp', + 'tinyint', + 'uniqueidentifier', + 'varbinary', + 'xml', + 'varchar' + ]; + + return result; + } + + async version(args: any = {}) { + const _func = this.version.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const rows = await this.sqlClient.raw( + `SELECT SERVERPROPERTY('productversion') as version, SERVERPROPERTY ('productlevel') as level, SERVERPROPERTY ('edition') as edition, @@version as versionD` + ); + result.data.object = {}; + + const versionDetails = rows[0]; + const version = versionDetails.version.split('.'); + result.data.object.version = versionDetails.version; + result.data.object.primary = version[0]; + result.data.object.major = version[1]; + result.data.object.minor = version[2]; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + /** + * + * @param {Object} args + * @param {String} args.database + * @returns {Result} + */ + async createDatabaseIfNotExists(args: { database: string }) { + const _func = this.createDatabaseIfNotExists.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const connectionParamsWithoutDb = JSON.parse( + JSON.stringify(this.connectionConfig) + ); + delete connectionParamsWithoutDb.connection.database; + const tempSqlClient = knex(connectionParamsWithoutDb); + + const rows = await tempSqlClient.raw( + `select name from sys.databases where name = '${args.database}'` + ); + + if (rows.length === 0) { + await tempSqlClient.raw(`CREATE DATABASE ${args.database}`); + } + + this.sqlClient = knex(this.connectionConfig); + await tempSqlClient.destroy(); + if ( + this.connectionConfig.searchPath && + this.connectionConfig.searchPath[0] + ) { + await this.sqlClient.raw( + ` IF NOT EXISTS ( SELECT * + FROM sys.schemas + WHERE name = ? ) + EXEC('CREATE SCHEMA ??')`, + [ + this.connectionConfig.searchPath[0], + this.connectionConfig.searchPath[0] + ] + ); + } + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + async dropDatabase(args: { database: string }) { + const _func = this.dropDatabase.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const connectionParamsWithoutDb = JSON.parse( + JSON.stringify(this.connectionConfig) + ); + delete connectionParamsWithoutDb.connection.database; + const tempSqlClient = knex(connectionParamsWithoutDb); + await this.sqlClient.destroy(); + this.sqlClient = tempSqlClient; + log.debug('dropping database:', args); + await tempSqlClient.raw( + `ALTER DATABASE ${args.database} SET SINGLE_USER WITH ROLLBACK IMMEDIATE; + DROP DATABASE ${args.database};` + ); + } catch (e) { + log.ppe(e, _func); + // throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + /** + * + * @param args {tn} + * @returns + */ + async createTableIfNotExists(args: { tn: string }) { + const _func = this.createTableIfNotExists.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + /** ************** START : create _evolution table if not exists *************** */ + const exists = await this.sqlClient.schema.hasTable( + this.getTnPath(args.tn) + ); + + if (!exists) { + await this.sqlClient.schema.createTable( + this.getTnPath(args.tn), + function(table) { + table.increments(); + table.string('title').notNullable(); + table.string('titleDown').nullable(); + table.string('description').nullable(); + table.integer('batch').nullable(); + table.string('checksum').nullable(); + table.integer('status').nullable(); + table.dateTime('created'); + table.timestamps(); + } + ); + log.debug('Table created:', `${this.getTnPath(args.tn)}`); + } else { + log.debug(`${this.getTnPath(args.tn)} tables exists`); + } + /** ************** END : create _evolution table if not exists *************** */ + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async hasTable(args: { tn: string }) { + const _func = this.hasTable.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + result.data.value = await this.sqlClient.schema.hasTable( + this.getTnPath(args.tn) + ); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async hasDatabase(args: { databaseName: string }) { + const _func = this.hasDatabase.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const rows = await this.sqlClient.raw( + `select name from sys.databases where name = '${args.databaseName}'` + ); + result.data.value = rows.length > 0; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - for future reasons + * @returns {Object[]} - databases + * @property {String} - databases[].database_name + */ + async databaseList(args: any = {}) { + const _func = this.databaseList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + result.data.list = await this.sqlClient.raw( + `SELECT name as database_name, database_id, create_date from sys.databases order by name` + ); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - for future reasons + * @returns {Object[]} - tables + * @property {String} - tables[].tn + */ + async tableList(args: any = {}) { + const _func = this.tableList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + result.data.list = await this.sqlClient.raw( + `select schema_name(t.schema_id) as schema_name, + t.name as tn, t.create_date, t.modify_date from sys.tables t WHERE schema_name(t.schema_id) = ? order by schema_name,tn `, + [this.schema || 'dbo'] + ); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async schemaList(args: any = {}) { + const _func = this.schemaList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + result.data.list = await this.sqlClient.raw( + `SELECT name as schema_name FROM master.${this.schema}.sysdatabases where name not in ('master', 'tempdb', 'model', 'msdb');` + ); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - columns + * @property {String} - columns[].tn + * @property {String} - columns[].cn + * @property {String} - columns[].dt + * @property {String} - columns[].dtx + * @property {String} - columns[].np + * @property {String} - columns[].ns - + * @property {String} - columns[].clen - + * @property {String} - columns[].dp - + * @property {String} - columns[].cop - + * @property {String} - columns[].pk - + * @property {String} - columns[].nrqd - + * @property {String} - columns[].not_nullable - + * @property {String} - columns[].ct - + * @property {String} - columns[].un - + * @property {String} - columns[].ai - + * @property {String} - columns[].unique - + * @property {String} - columns[].cdf - + * @property {String} - columns[].cc - + * @property {String} - columns[].csn - + */ + async columnList(args: { + tn: string; + columns?: ColumnType[]; + databaseName?: string; + }) { + const _func = this.columnList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw(`select + c.table_name as tn, + case WHEN trg1.trigger_name IS NULL THEN CAST(0 as BIT) ELSE CAST(1 as BIT) END as au, + c.column_name as cn, + c.ordinal_position as cop, + pk.constraint_type as ck, + case WHEN COLUMNPROPERTY(object_id(CONCAT('${this.schema}.', c.TABLE_NAME)), c.COLUMN_NAME, 'IsIdentity') = 1 + THEN + 1 + ELSE + 0 + END as ai, + c.is_nullable as nrqd, + c.data_type as dt, + c.column_default as cdf,c.character_maximum_length as clen, + c.character_octet_length,c.numeric_precision as np,c.numeric_scale as ns,c.datetime_precision as dp,c.character_set_name as csn, + c.collation_name as clnn, + pk.constraint_type as cst, pk.ordinal_position as op, pk.constraint_name as pk_constraint_name, + fk.parent_table as rtn, fk.parent_column as rcn, + v.table_name as is_view, + df.default_constraint_name + from information_schema.columns c + left join + ( select kc.constraint_name, kc.table_name,kc.column_name, kc.ordinal_position,tc.constraint_type + from information_schema.key_column_usage kc + inner join information_schema.table_constraints as tc + on kc.constraint_name = tc.constraint_name and tc.constraint_type in ('primary key') + where kc.table_catalog='${args.databaseName}' and kc.table_schema='${this.schema}' + ) pk + on + pk.table_name = c.table_name and pk.column_name=c.column_name + left join + ( select + ccu.table_name as child_table + ,ccu.column_name as child_column + ,kcu.table_name as parent_table + ,kcu.column_name as parent_column + ,ccu.constraint_name + from information_schema.constraint_column_usage ccu + inner join information_schema.referential_constraints rc + on ccu.constraint_name = rc.constraint_name + inner join information_schema.key_column_usage kcu + on kcu.constraint_name = rc.unique_constraint_name ) fk + on + fk.child_table = c.table_name and fk.child_column=c.column_name + left join information_schema.views v + on v.table_name=c.table_name + left join ( + SELECT + default_constraints.name default_constraint_name, all_columns.name name + FROM + sys.all_columns + INNER JOIN + sys.tables + ON all_columns.object_id = tables.object_id + INNER JOIN + sys.schemas + ON tables.schema_id = schemas.schema_id + INNER JOIN + sys.default_constraints + ON all_columns.default_object_id = default_constraints.object_id + WHERE + schemas.name = '${this.schema}' + AND tables.name = '${args.tn}') df on df.name = c.column_name + + left join ( select trg.name as trigger_name, + tab.name as [table1] + from sys.triggers trg + left join sys.objects tab + on trg.parent_id = tab.object_id + where tab.name = '${args.tn}') trg1 on trg1.trigger_name = CONCAT('xc_trigger_${args.tn}_' , c.column_name) + + where c.table_catalog='${args.databaseName}' and c.table_schema='${this.schema}' and c.table_name = '${args.tn}' + order by c.table_name, c.ordinal_position`); + + for (let i = 0; i < response.length; i++) { + const el = response[i]; + el.pk = el.ck === 'PRIMARY KEY'; + el.not_nullable = el.nrqd === 'NO'; + el.rqd = el.nrqd === 'NO'; + el.ai = el.ai === 1; + // el.unique = + el.nrqd = el.nrqd === 'YES'; + el.cno = el.cn; + el.dtxp = el.clen || el.np || el.dp; + el.dtxs = el.ns; + el.au = !!el.au; + } + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - indexes + * @property {String} - indexes[].table - + * @property {String} - indexes[].cn - + * @property {String} - indexes[].key_name - + * @property {String} - indexes[].non_unique - + * @property {String} - indexes[].seq_in_index - + * @property {String} - indexes[].collation - + * @property {String} - indexes[].cardinality - + * @property {String} - indexes[].sub_part - + * @property {String} - indexes[].packed - + * @property {String} - indexes[].null - + * @property {String} - indexes[].index_type - + * @property {String} - indexes[].comment - + * @property {String} - indexes[].index_comment - + * @property {Boolean} - indexes[].pk - + */ + async indexList(args: any = {}) { + const _func = this.indexList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + const response = await this.sqlClient.raw( + `select t.[name] as table_view, + case when t.[type] = 'U' then 'Table' + when t.[type] = 'V' then 'View' + end as [object_type], + i.index_id, + case when i.is_primary_key = 1 then 'Primary key' + when i.is_unique = 1 then 'Unique' + else 'Not Unique' end as [type], + i.[name] as index_name, + substring(column_names, 1, len(column_names)-1) as [columns], + case when i.[type] = 1 then 'Clustered index' + when i.[type] = 2 then 'Nonclustered unique index' + when i.[type] = 3 then 'XML index' + when i.[type] = 4 then 'Spatial index' + when i.[type] = 5 then 'Clustered columnstore index' + when i.[type] = 6 then 'Nonclustered columnstore index' + when i.[type] = 7 then 'Nonclustered hash index' + end as index_type + from sys.objects t + inner join sys.indexes i + on t.object_id = i.object_id + cross apply (select col.[name] + ',' + CAST(ic.key_ordinal as varchar) + ',' + from sys.index_columns ic + inner join sys.columns col + on ic.object_id = col.object_id + and ic.column_id = col.column_id + where ic.object_id = t.object_id + and ic.index_id = i.index_id + order by col.column_id + for xml path ('') ) D (column_names) + where t.is_ms_shipped <> 1 + and index_id > 0 and t.name = '${this.getTnPath(args.tn)}' + order by schema_name(t.schema_id) + '.' + t.[name], i.index_id` + ); + const rows = []; + for (let i = 0, rowCount = 0; i < response.length; ++i, ++rowCount) { + response[i].key_name = response[i].index_name; + response[i].non_unique = response[i].type === 'Not Unique' ? 1 : 0; + response[i].non_unique_original = + response[i].type === 'Not Unique' ? 1 : 0; + response[i].pk = response[i].type === 'Primary key'; + // split + response[i].columns = response[i].columns.split(','); + + if (response[i].columns.length === 2) { + rows[rowCount] = response[i]; + rows[rowCount].cn = response[i].columns[0]; + rows[rowCount].seq_in_index = 1; + } else { + const cols = response[i].columns.slice(); + for (let j = 0; j < cols.length; j += 2, ++rowCount) { + rows[rowCount] = JSON.parse(JSON.stringify(response[i])); + rows[rowCount].cn = cols[j].trim(); + rows[rowCount].seq_in_index = parseInt(cols[j + 1]); + } + rowCount--; + } + } + + // console.log(rows); + + result.data.list = rows; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - indexes + * @property {String} - indexes[].cstn - + * @property {String} - indexes[].cn - + * @property {String} - indexes[].op - + * @property {String} - indexes[].puc - + * @property {String} - indexes[].cst - + */ + async constraintList(args: any = {}) { + const _func = this.constraintList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + `select t.[name] as table_view, + case when t.[type] = 'U' then 'Table' + when t.[type] = 'V' then 'View' + end as [object_type], + i.index_id, + case when i.is_primary_key = 1 then 'Primary key' + when i.is_unique = 1 then 'Unique' + else 'Not Unique' end as [type], + i.[name] as index_name, + substring(column_names, 1, len(column_names)-1) as [columns], + case when i.[type] = 1 then 'Clustered index' + when i.[type] = 2 then 'Nonclustered unique index' + when i.[type] = 3 then 'XML index' + when i.[type] = 4 then 'Spatial index' + when i.[type] = 5 then 'Clustered columnstore index' + when i.[type] = 6 then 'Nonclustered columnstore index' + when i.[type] = 7 then 'Nonclustered hash index' + end as index_type + from sys.objects t + inner join sys.indexes i + on t.object_id = i.object_id + cross apply (select col.[name] + ', ' + CAST(ic.key_ordinal as varchar) + ', ' + from sys.index_columns ic + inner join sys.columns col + on ic.object_id = col.object_id + and ic.column_id = col.column_id + where ic.object_id = t.object_id + and ic.index_id = i.index_id + order by col.column_id + for xml path ('') ) D (column_names) + where t.is_ms_shipped <> 1 + and index_id > 0 and t.name = '${this.getTnPath(args.tn)}' + order by schema_name(t.schema_id) + '.' + t.[name], i.index_id` + ); + const rows = []; + for (let i = 0, rowCount = 0; i < response.length; ++i, ++rowCount) { + response[i].cstn = response[i].index_name; + response[i].cst = response[i].type; + response[i].columns = response[i].columns.split(','); + + if (response[i].columns.length === 2) { + rows[rowCount] = response[i]; + rows[rowCount].cn = response[i].columns[0]; + rows[rowCount].op = 1; + } else { + const cols = response[i].columns.slice(); + for (let j = 0; j < cols.length; j += 2, ++rowCount) { + rows[rowCount] = JSON.parse(JSON.stringify(response[i])); + rows[rowCount].cn = cols[j]; + rows[rowCount].op = parseInt(cols[j + 1]); + } + rowCount--; + } + } + + // console.log(rows); + + result.data.list = rows; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - relations + * @property {String} - relations[].tn + * @property {String} - relations[].cstn - + * @property {String} - relations[].tn - + * @property {String} - relations[].cn - + * @property {String} - relations[].rtn - + * @property {String} - relations[].rcn - + * @property {String} - relations[].puc - + * @property {String} - relations[].ur - + * @property {String} - relations[].dr - + * @property {String} - relations[].mo - + */ + async relationList(args: any = {}) { + const _func = this.relationList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + const response = await this.sqlClient + .raw(`select fk_tab.name as tn, '>-' as rel, pk_tab.name as rtn, + fk_cols.constraint_column_id as no, fk_col.name as cn, ' = ' as [join], + pk_col.name as rcn, fk.name as cstn, + fk.update_referential_action_desc as ur, fk.delete_referential_action_desc as dr + from sys.foreign_keys fk + inner join sys.tables fk_tab + on fk_tab.object_id = fk.parent_object_id + inner join sys.tables pk_tab + on pk_tab.object_id = fk.referenced_object_id + inner join sys.foreign_key_columns fk_cols + on fk_cols.constraint_object_id = fk.object_id + inner join sys.columns fk_col + on fk_col.column_id = fk_cols.parent_column_id + and fk_col.object_id = fk_tab.object_id + inner join sys.columns pk_col + on pk_col.column_id = fk_cols.referenced_column_id + and pk_col.object_id = pk_tab.object_id + where fk_tab.name = '${this.getTnPath(args.tn)}' + order by fk_tab.name, pk_tab.name, fk_cols.constraint_column_id`); + + const ruleMapping = { + NO_ACTION: 'NO ACTION', + CASCADE: 'CASCADE', + RESTRICT: 'RESTRICT', + SET_NULL: 'SET NULL', + SET_DEFAULT: 'SET DEFAULT' + }; + + for (const row of response) { + row.ur = ruleMapping[row.ur]; + row.dr = ruleMapping[row.dr]; + } + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - relations + * @property {String} - relations[].tn + * @property {String} - relations[].cstn - + * @property {String} - relations[].tn - + * @property {String} - relations[].cn - + * @property {String} - relations[].rtn - + * @property {String} - relations[].rcn - + * @property {String} - relations[].puc - + * @property {String} - relations[].ur - + * @property {String} - relations[].dr - + * @property {String} - relations[].mo - + */ + async relationListAll(args: any = {}) { + const _func = this.relationList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + const response = await this + .raw(`select fk_tab.name as tn, '>-' as rel, pk_tab.name as rtn, + fk_cols.constraint_column_id as no, fk_col.name as cn, ' = ' as [join], + pk_col.name as rcn, fk.name as cstn, + fk.update_referential_action_desc as ur, fk.delete_referential_action_desc as dr + from sys.foreign_keys fk + inner join sys.tables fk_tab + on fk_tab.object_id = fk.parent_object_id + inner join sys.tables pk_tab + on pk_tab.object_id = fk.referenced_object_id + inner join sys.foreign_key_columns fk_cols + on fk_cols.constraint_object_id = fk.object_id + inner join sys.columns fk_col + on fk_col.column_id = fk_cols.parent_column_id + and fk_col.object_id = fk_tab.object_id + inner join sys.columns pk_col + on pk_col.column_id = fk_cols.referenced_column_id + and pk_col.object_id = pk_tab.object_id + order by fk_tab.name, pk_tab.name, fk_cols.constraint_column_id`); + + const ruleMapping = { + NO_ACTION: 'NO ACTION', + CASCADE: 'CASCADE', + RESTRICT: 'RESTRICT', + SET_NULL: 'SET NULL', + SET_DEFAULT: 'SET DEFAULT' + }; + + for (const row of response) { + row.ur = ruleMapping[row.ur]; + row.dr = ruleMapping[row.dr]; + } + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - triggers + * @property {String} - triggers[].trigger + * @property {String} - triggers[].event + * @property {String} - triggers[].table + * @property {String} - triggers[].statement + * @property {String} - triggers[].timing + * @property {String} - triggers[].created + * @property {String} - triggers[].sql_mode + * @property {String} - triggers[].definer + * @property {String} - triggers[].character_set_client + * @property {String} - triggers[].collation_connection + * @property {String} - triggers[].database collation + */ + async triggerList(args: any = {}) { + const _func = this.triggerList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + const query = `select trg.name as trigger_name, + tab.name as [table], + case when is_instead_of_trigger = 1 then 'Instead of' + else 'After' end as [activation], + (case when objectproperty(trg.object_id, 'ExecIsUpdateTrigger') = 1 + then 'Update' else '' end + + case when objectproperty(trg.object_id, 'ExecIsDeleteTrigger') = 1 + then 'Delete' else '' end + + case when objectproperty(trg.object_id, 'ExecIsInsertTrigger') = 1 + then 'Insert' else '' end + ) as [event], + case when trg.parent_class = 1 then 'Table trigger' + when trg.parent_class = 0 then 'Database trigger' + end [class], + case when trg.[type] = 'TA' then 'Assembly (CLR) trigger' + when trg.[type] = 'TR' then 'SQL trigger' + else '' end as [type], + case when is_disabled = 1 then 'Disabled' + else 'Active' end as [status], + object_definition(trg.object_id) as [definition] + from sys.triggers trg + left join sys.objects tab + on trg.parent_id = tab.object_id + where tab.name = '${this.getTnPath(args.tn)}' + order by trg.name;`; + + const response = await this.sqlClient.raw(query); + + for (let i = 0; i < response.length; i++) { + const el = response[i]; + // el.table = el.table; + el.statement = el.definition; + el.trigger = el.trigger_name; + el.event = el.event.toUpperCase(); + el.timing = el.activation.toUpperCase(); + } + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @returns {Object[]} - functions + * @property {String} - functions[].function_name + * @property {String} - functions[].type + * @property {String} - functions[].definer + * @property {String} - functions[].modified + * @property {String} - functions[].created + * @property {String} - functions[].security_type + * @property {String} - functions[].comment + * @property {String} - functions[].character_set_client + * @property {String} - functions[].collation_connection + * @property {String} - functions[].database collation + */ + async functionList(args: any = {}) { + const _func = this.functionList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + const response = await this.sqlClient.raw( + `SELECT o.name as function_name,definition, o.create_date as created, o.modify_date as modified,o.* + FROM sys.sql_modules AS m + JOIN sys.objects AS o ON m.object_id = o.object_id + AND type IN ('FN', 'IF', 'TF')` + ); + for (let i = 0; i < response.length; i++) { + const el = response[i]; + if (el.type === 'FN') el.type = 'FUNCTION'; + } + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - For future reasons + * @returns {Object[]} - procedures + * @property {String} - procedures[].procedure_name + * @property {String} - procedures[].type + * @property {String} - procedures[].definer + * @property {String} - procedures[].modified + * @property {String} - procedures[].created + * @property {String} - procedures[].security_type + * @property {String} - procedures[].comment + * @property {String} - procedures[].definer + * @property {String} - procedures[].character_set_client + * @property {String} - procedures[].collation_connection + * @property {String} - procedures[].database collation + */ + async procedureList(args: any = {}) { + const _func = this.procedureList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + `select SPECIFIC_NAME as procedure_name, ROUTINE_TYPE as [type],LAST_ALTERED as modified, CREATED as created,ROUTINE_DEFINITION as definition ,pc.* + from ${args.databaseName}.information_schema.routines as pc where routine_type = 'PROCEDURE'` + ); + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @returns {Object[]} - views + * @property {String} - views[].sql_mode + * @property {String} - views[].create_function + * @property {String} - views[].database collation + * @property {String} - views[].collation_connection + * @property {String} - views[].character_set_client + */ + async viewList(args: any = {}) { + const _func = this.viewList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + `SELECT v.name as view_name,v.*,m.* FROM sys.views v inner join sys.schemas s on s.schema_id = v.schema_id + inner join sys.sql_modules as m on m.object_id = v.object_id` + ); + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.function_name - + * @returns {Object[]} - functions + * @property {String} - sql_mode + * @property {String} - create_function + * @property {String} - database collation + * @property {String} - collation_connection + * @property {String} - character_set_client + */ + async functionRead(args: any = {}) { + const _func = this.functionRead.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + `SELECT o.name as function_name,definition as create_function, o.create_date as created, o.modify_date as modified,o.* + FROM sys.sql_modules AS m + JOIN sys.objects AS o ON m.object_id = o.object_id + AND type IN ('FN', 'IF', 'TF') and o.name = '${args.function_name}'` + ); + + for (let i = 0; i < response.length; i++) { + const el = response[i]; + if (el.type === 'FN') el.type = 'FUNCTION'; + } + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.procedure_name - + * @returns {Object[]} - functions + * @property {String} - sql_mode + * @property {String} - create_function + * @property {String} - database collation + * @property {String} - collation_connection + * @property {String} - character_set_client + */ + async procedureRead(args: any = {}) { + const _func = this.procedureRead.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + `select SPECIFIC_NAME as procedure_name, ROUTINE_TYPE as [type],LAST_ALTERED as modified, CREATED as created,ROUTINE_DEFINITION as create_procedure ,pc.* + from ${args.databaseName}.information_schema.routines as pc where routine_type = 'PROCEDURE' and SPECIFIC_NAME='${args.procedure_name}'` + ); + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.view_name - + * @returns {Object[]} - views + * @property {String} - views[].tn + */ + async viewRead(args: any = {}) { + const _func = this.viewRead.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + `SELECT v.name as view_name,v.*,m.*, m.definition as view_definition FROM sys.views v inner join sys.schemas s on s.schema_id = v.schema_id + inner join sys.sql_modules as m on m.object_id = v.object_id where v.name = '${args.view_name}'` + ); + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async triggerRead(args: any = {}) { + const _func = this.triggerRead.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + `SELECT [so].[name] AS [trigger_name], [so].[crdate] AS [created], + USER_NAME([so].[uid]) AS [trigger_owner], USER_NAME([so2].[uid]) AS [ts], + OBJECT_NAME([so].[parent_obj]) AS [tn], + OBJECTPROPERTY( [so].[id], 'ExecIsUpdateTrigger') AS [isupdate], + OBJECTPROPERTY( [so].[id], 'ExecIsDeleteTrigger') AS [isdelete], + OBJECTPROPERTY( [so].[id], 'ExecIsInsertTrigger') AS [isinsert], + OBJECTPROPERTY( [so].[id], 'ExecIsAfterTrigger') AS [isafter], + OBJECTPROPERTY( [so].[id], 'ExecIsInsteadOfTrigger') AS [isinsteadof], + OBJECTPROPERTY([so].[id], 'ExecIsTriggerDisabled') AS [disabled],df.definition + FROM sysobjects AS [so] + INNER JOIN sys.sql_modules AS df ON object_id = so.id + INNER JOIN sysobjects AS so2 ON so.parent_obj = so2.Id + WHERE [so].[type] = 'TR' and so2.name = '${this.getTnPath( + args.tn + )}' and [so].[name] = '${args.trigger_name}'` + ); + + for (let i = 0; i < response.length; i++) { + const el = response[i]; + el.table = el.tn; + el.statement = el.definition; + el.trigger = el.trigger_name; + el.event = []; + el.timing = []; + } + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async schemaCreate(args: any = {}) { + const _func = this.schemaCreate.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const rows = await this.sqlClient.raw( + `select name from sys.databases where name = '${args.database_name}'` + ); + + if (rows.length === 0) { + await this.sqlClient.raw(`CREATE DATABASE ${args.database_name}`); + } + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async schemaDelete(args: any = {}) { + const _func = this.schemaDelete.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const connectionParamsWithoutDb = JSON.parse( + JSON.stringify(this.connectionConfig) + ); + + if ( + connectionParamsWithoutDb.connection.database === args.database_name + ) { + delete connectionParamsWithoutDb.connection.database; + const tempSqlClient = knex(connectionParamsWithoutDb); + await this.sqlClient.destroy(); + this.sqlClient = tempSqlClient; + } + + await this.sqlClient.raw( + `ALTER DATABASE ${args.database_name} SET SINGLE_USER WITH ROLLBACK IMMEDIATE; + DROP DATABASE ${args.database_name};` + ); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async triggerDelete(args: any = {}) { + const func = this.triggerDelete.name; + const result = new Result(); + log.api(`${func}:args:`, args); + // `DROP TRIGGER ${args.trigger_name}` + try { + const query = `${this.querySeparator()}DROP TRIGGER IF EXISTS ${ + args.trigger_name + }`; + + await this.sqlClient.raw(query); + + result.data.object = { + upStatement: [{ sql: query }], + downStatement: [ + { + sql: `${this.querySeparator()}${args.oldStatement}` + } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + async functionDelete(args: any = {}) { + const _func = this.functionDelete.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + const upQuery = + this.querySeparator() + `DROP FUNCTION IF EXISTS ${args.function_name}`; + const downQuery = this.querySeparator() + args.create_function; + try { + await this.sqlClient.raw(upQuery); + result.data.object = { + upStatement: [{ sql: upQuery }], + downStatement: [{ sql: downQuery }] + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async procedureDelete(args: any = {}) { + const func = this.procedureDelete.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + await this.sqlClient.raw( + `DROP PROCEDURE IF EXISTS ${args.procedure_name}` + ); + result.data.object = { + upStatement: [ + { + sql: + this.querySeparator() + + `DROP PROCEDURE IF EXISTS ${args.procedure_name}` + } + ], + downStatement: [ + { sql: this.querySeparator() + `${args.create_procedure}` } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.function_name + * @param {String} - args.event + * @param {String} - args.timing + * @returns {Object[]} - result rows + */ + async functionCreate(args: any = {}) { + const func = this.functionCreate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + await this.sqlClient.raw(`${args.create_function}`); + result.data.object = { + upStatement: [ + { sql: this.querySeparator() + `${args.create_function}` } + ], + downStatement: [ + { + sql: + this.querySeparator() + + `DROP FUNCTION IF EXISTS ${args.function_name}` + } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.function_name + * @param {String} - args.event + * @param {String} - args.timing + * @returns {Object[]} - result rows + */ + async functionUpdate(args: any = {}) { + const func = this.functionUpdate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + await this.sqlClient.raw(`DROP FUNCTION IF EXISTS ${args.function_name}`); + await this.sqlClient.raw(`${args.create_function}`); + result.data.object = { + upStatement: [ + { + sql: + this.querySeparator() + + `DROP FUNCTION IF EXISTS ${ + args.function_name + };${this.querySeparator()}\n${args.create_function}` + } + ], + downStatement: [ + { + sql: + this.querySeparator() + + `DROP FUNCTION IF EXISTS ${ + args.function_name + };${this.querySeparator()} ${args.oldCreateFunction}` + } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.procedure_name + * @param {String} - args.event + * @param {String} - args.timing + * @returns {Object[]} - result rows + */ + async procedureCreate(args: any = {}) { + const func = this.procedureCreate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + await this.sqlClient.raw(`${args.create_procedure}`); + result.data.object = { + upStatement: [ + { sql: this.querySeparator() + `${args.create_procedure}` } + ], + downStatement: [ + { + sql: this.querySeparator() + `DROP PROCEDURE ${args.procedure_name}` + } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.procedure_name + * @param {String} - args.event + * @param {String} - args.timing + * @returns {Object[]} - result rows + */ + async procedureUpdate(args: any = {}) { + const func = this.procedureUpdate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + await this.sqlClient.raw( + `DROP PROCEDURE IF EXISTS ${args.procedure_name}` + ); + await this.sqlClient.raw(`${args.create_procedure}`); + result.data.object = { + upStatement: [ + { + sql: + this.querySeparator() + + `DROP PROCEDURE IF EXISTS ${ + args.procedure_name + };${this.querySeparator()}\n${args.create_procedure}` + } + ], + downStatement: [ + { + sql: + this.querySeparator() + + `DROP PROCEDURE IF EXISTS ${ + args.procedure_name + };${this.querySeparator()}${args.oldCreateProcedure}` + } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.trigger_name + * @param {String} - args.event + * @param {String} - args.timing + * @returns {Object[]} - result rows + */ + async triggerCreate(args: any = {}) { + const func = this.triggerCreate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const query = `CREATE TRIGGER ${args.trigger_name} on ${this.getTnPath( + args.tn + )} \n${args.timing} ${args.event}\n as\n${args.statement}`; + await this.sqlClient.raw(query); + result.data.object = { + upStatement: [{ sql: this.querySeparator() + query }], + downStatement: [ + { sql: this.querySeparator() + `DROP TRIGGER ${args.trigger_name}` } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.trigger_name + * @param {String} - args.event + * @param {String} - args.timing + * @param {String} - args.oldStatement + * @returns {Object[]} - result rows + */ + async triggerUpdate(args: any = {}) { + const func = this.triggerUpdate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + // await this.sqlClient.raw(`DROP TRIGGER ${args.trigger_name}`); + await this.sqlClient.raw( + `ALTER TRIGGER ${args.trigger_name} ON ${this.getTnPath(args.tn)} \n${ + args.timing + } ${args.event}\n AS\n${args.statement}` + ); + + result.data.object = { + upStatement: [ + { + sql: + this.querySeparator() + + `ALTER TRIGGER ${args.trigger_name} ON ${this.getTnPath( + args.tn + )} \n${args.timing} ${args.event}\n AS\n${args.statement}` + } + ], + downStatement: [ + { + sql: + this.querySeparator() + + `ALTER TRIGGER ${args.trigger_name} ON ${this.getTnPath( + args.tn + )} \n${args.timing} ${args.event}\n AS\n${args.statement}` + } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.view_name + * @param {String} - args.view_definition + * @returns {Object} - up and down statements + */ + async viewCreate(args: any = {}) { + const func = this.viewCreate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const query = args.view_definition; + + await this.sqlClient.raw(query); + result.data.object = { + upStatement: [{ sql: this.querySeparator() + query }], + downStatement: [ + { sql: this.querySeparator() + `DROP VIEW ${args.view_name}` } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.view_name + * @param {String} - args.view_definition + * @param {String} - args.oldViewDefination + * @returns {Object} - up and down statements + */ + async viewUpdate(args: any = {}) { + const func = this.viewUpdate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const query = args.view_definition; + + await this.sqlClient.raw(query); + result.data.object = { + upStatement: [{ sql: this.querySeparator() + query }], + downStatement: [ + { + sql: + this.querySeparator() + + `DROP VIEW ${args.view_name} ; ${this.querySeparator()}${ + args.oldViewDefination + };` + } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.view_name + * @param {String} - args.view_definition + * @param {String} - args.oldViewDefination + * @returns {Object} - up and down statements + */ + async viewDelete(args: any = {}) { + const func = this.viewDelete.name; + const result = new Result(); + log.api(`${func}:args:`, args); + // `DROP TRIGGER ${args.view_name}` + try { + const query = `DROP VIEW ${args.view_name}`; + + await this.sqlClient.raw(query); + + result.data.object = { + upStatement: [{ sql: this.querySeparator() + query }], + downStatement: [ + { + sql: this.querySeparator() + args.oldViewDefination + } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args + * @param {String} - args.tn + * @param {Object[]} - args.columns + * @param {String} - args.columns[].tn + * @param {String} - args.columns[].cn + * @param {String} - args.columns[].dt + * @param {String} - args.columns[].np + * @param {String} - args.columns[].ns - + * @param {String} - args.columns[].clen - + * @param {String} - args.columns[].dp - + * @param {String} - args.columns[].cop - + * @param {String} - args.columns[].pk - + * @param {String} - args.columns[].nrqd - + * @param {String} - args.columns[].not_nullable - + * @param {String} - args.columns[].ct - + * @param {String} - args.columns[].un - + * @param {String} - args.columns[].ai - + * @param {String} - args.columns[].unique - + * @param {String} - args.columns[].cdf - + * @param {String} - args.columns[].cc - + * @param {String} - args.columns[].csn - + * @param {String} - args.columns[].dtx + * - value will be 'specificType' for all cols except ai + * - for ai it will be integer, bigInteger + * - tiny, small and medium Int auto increement is not supported + * @param {String} - args.columns[].dtxp - to use in UI + * @param {String} - args.columns[].dtxs - to use in UI + * @returns {Promise<{upStatement, downStatement}>} + */ + async tableCreate(args) { + const _func = this.tableCreate.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + args.table = args.tn; + args.sqlClient = this.sqlClient; + + /**************** create table ****************/ + const upQuery = this.querySeparator() + this.createTable(args.tn, args); + await this.sqlClient.raw(upQuery); + + const downStatement = + this.querySeparator() + + this.sqlClient.schema.dropTable(args.table).toString(); + + this.emit(`Success : ${upQuery}`); + + const triggerStatements = await this.afterTableCreate(args); + + /**************** return files *************** */ + result.data.object = { + upStatement: [{ sql: upQuery }, ...triggerStatements.upStatement], + downStatement: [ + ...triggerStatements.downStatement, + { sql: downStatement } + ] + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + async afterTableCreate(args) { + const result = { upStatement: [], downStatement: [] }; + let upQuery = ''; + let downQuery = ''; + + const pk = args.columns.find(c => c.pk); + if (!pk) return result; + + for (let i = 0; i < args.columns.length; i++) { + const column = args.columns[i]; + if (column.au) { + const triggerName = `xc_trigger_${args.table_name}_${column.column_name}`; + const triggerCreateQuery = + this.querySeparator() + + `CREATE TRIGGER ${this.schema}.${triggerName} ON ${this.schema}.${args.table_name} AFTER UPDATE + AS + BEGIN + SET NOCOUNT ON; + UPDATE ${this.schema}.${args.table_name} Set ${column.column_name} = GetDate() where ${pk.column_name} in (SELECT ${pk.column_name} FROM Inserted) + END;`; + + upQuery += triggerCreateQuery; + + await this.sqlClient.raw(triggerCreateQuery); + + downQuery += + this.querySeparator() + + `DROP TRIGGER IF EXISTS ${this.schema}.${triggerName};`; + } + } + result.upStatement[0] = { sql: upQuery }; + result.downStatement[0] = { sql: downQuery }; + return result; + } + + async afterTableUpdate(args) { + const result = { upStatement: [], downStatement: [] }; + let upQuery = ''; + let downQuery = ''; + + const pk = args.columns.find(c => c.pk); + if (!pk) return result; + + for (let i = 0; i < args.columns.length; i++) { + const column = args.columns[i]; + if (column.au && column.altered === 1) { + const triggerName = `xc_trigger_${args.table_name}_${column.column_name}`; + const triggerCreateQuery = + this.querySeparator() + + `CREATE TRIGGER ${this.schema}.${triggerName} ON ${this.schema}.${args.table_name} AFTER UPDATE + AS + BEGIN + SET NOCOUNT ON; + UPDATE ${this.schema}.${args.table_name} Set ${column.column_name} = GetDate() where ${pk.column_name} in (SELECT ${pk.column_name} FROM Inserted) + END;`; + + upQuery += triggerCreateQuery; + + await this.sqlClient.raw(triggerCreateQuery); + + downQuery += + this.querySeparator() + + `DROP TRIGGER IF EXISTS ${this.schema}.${triggerName};`; + } + } + result.upStatement[0] = { sql: upQuery }; + result.downStatement[0] = { sql: downQuery }; + return result; + } + + /** + * + * @param {Object} - args + * @param {String} - args.table + * @param {String} - args.table + * @param {Object[]} - args.columns + * @param {String} - args.columns[].tn + * @param {String} - args.columns[].cn + * @param {String} - args.columns[].dt + * @param {String} - args.columns[].np + * @param {String} - args.columns[].ns - + * @param {String} - args.columns[].clen - + * @param {String} - args.columns[].dp - + * @param {String} - args.columns[].cop - + * @param {String} - args.columns[].pk - + * @param {String} - args.columns[].nrqd - + * @param {String} - args.columns[].not_nullable - + * @param {String} - args.columns[].ct - + * @param {String} - args.columns[].un - + * @param {String} - args.columns[].ai - + * @param {String} - args.columns[].unique - + * @param {String} - args.columns[].cdf - + * @param {String} - args.columns[].cc - + * @param {String} - args.columns[].csn - + * @param {Number} - args.columns[].altered - 1,2,4 = addition,edited,deleted + * @returns {Promise<{upStatement, downStatement}>} + */ + async tableUpdate(args) { + const _func = this.tableUpdate.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + args.table = args.tn; + const originalColumns = args.originalColumns; + args.connectionConfig = this._connectionConfig; + args.sqlClient = this.sqlClient; + + let upQuery = ''; + let downQuery = ''; + + for (let i = 0; i < args.columns.length; ++i) { + const oldColumn = lodash.find(originalColumns, { + cn: args.columns[i].cno + }); + + if (args.columns[i].altered & 4) { + // col remove + upQuery += this.alterTableRemoveColumn( + args.table, + args.columns[i], + oldColumn, + upQuery + ); + downQuery += this.alterTableAddColumn( + args.table, + oldColumn, + args.columns[i], + downQuery + ); + } else if (args.columns[i].altered & 2 || args.columns[i].altered & 8) { + // col edit + upQuery += this.alterTableChangeColumn( + args.table, + args.columns[i], + oldColumn, + upQuery + ); + downQuery += this.alterTableChangeColumn( + args.table, + oldColumn, + args.columns[i], + downQuery + ); + } else if (args.columns[i].altered & 1) { + // col addition + upQuery += this.alterTableAddColumn( + args.table, + args.columns[i], + oldColumn, + upQuery + ); + downQuery += this.alterTableRemoveColumn( + args.table, + args.columns[i], + oldColumn, + downQuery + ); + } + } + + upQuery += this.alterTablePK( + args.table, + args.columns, + args.originalColumns, + upQuery + ); + downQuery += this.alterTablePK( + args.table, + args.originalColumns, + args.columns, + downQuery + ); + + if (upQuery) { + //upQuery = `ALTER TABLE ${args.columns[0].tn} ${upQuery};`; + //downQuery = `ALTER TABLE ${args.columns[0].tn} ${downQuery};`; + } + + await this.sqlClient.raw(upQuery); + + console.log(upQuery); + + const afterUpdate = await this.afterTableUpdate(args); + + result.data.object = { + upStatement: [ + { sql: this.querySeparator() + upQuery }, + ...afterUpdate.upStatement + ], + downStatement: [ + ...afterUpdate.downStatement, + { sql: this.querySeparator() + downQuery } + ] + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param {Object} - args + * @param args.tn + * @returns {Promise<{upStatement, downStatement}>} + */ + async tableDelete(args) { + const _func = this.tableDelete.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + // const { columns } = args; + args.sqlClient = this.sqlClient; + + /** ************** create up & down statements *************** */ + const upStatement = + this.querySeparator() + + this.sqlClient.schema.dropTable(this.getTnPath(args.tn)).toString(); + let downQuery = this.querySeparator() + this.createTable(args.tn, args); + + this.emit(`Success : ${upStatement}`); + + let relationsList: any = await this.relationList({ + tn: this.getTnPath(args.tn) + }); + + relationsList = relationsList.data.list; + + for (const relation of relationsList) { + downQuery += + this.querySeparator() + + (await this.sqlClient.schema + .table(this.getTnPath(relation.tn), function(table) { + table = table + .foreign(relation.cn, null) + .references(relation.rcn) + .on(relation.rtn); + + if (relation.ur) { + table = table.onUpdate(relation.ur); + } + if (relation.dr) { + table = table.onDelete(relation.dr); + } + }) + .toQuery()); + } + + let indexList: any = await this.indexList(args); + + // Todo: filter foeign key in proper way + indexList = indexList.data.list.filter( + ({ type, key_name }) => + type !== 'Primary key' && key_name.indexOf('fk_') === -1 + ); + + const indexesMap: { [key: string]: any } = {}; + + for (const { key_name, non_unique, cn } of indexList) { + if (!(key_name in indexesMap)) { + indexesMap[key_name] = { + tn: this.getTnPath(args.tn), + indexName: key_name, + non_unique, + columns: [] + }; + } + indexesMap[key_name].columns.push(cn); + } + + for (const { non_unique, tn, columns, key_name } of Object.values( + indexesMap + )) { + downQuery += + this.querySeparator() + + this.sqlClient.schema + .table(tn, function(table) { + if (non_unique) { + table.index(columns, key_name); + } else { + table.unique(columns, key_name); + } + }) + .toQuery(); + } + + /** ************** drop tn *************** */ + await this.sqlClient.schema.dropTable(this.getTnPath(args.tn)); + + /** ************** return files *************** */ + result.data.object = { + upStatement: [{ sql: upStatement }], + downStatement: [{ sql: downQuery }] + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableCreateStatement(args) { + const _func = this.tableCreateStatement.name; + let result = new Result(); + log.api(`${_func}:args:`, args); + try { + result = await this.columnList(args); + const upQuery = this.createTable(args.tn, { + tn: args.tn, + columns: result.data.list + }); + result.data = upQuery; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableInsertStatement(args) { + const _func = this.tableCreateStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + result.data = `INSERT INTO \`${this.getTnPath(args.tn)}\` (`; + let values = ' VALUES ('; + const response = await this.columnList(args); + if (response.data && response.data.list) { + for (let i = 0; i < response.data.list.length; ++i) { + if (!i) { + result.data += `\n${response.data.list[i].cn}\n\t`; + values += `\n<${response.data.list[i].cn}>\n\t`; + } else { + result.data += `, \`${response.data.list[i].cn}\`\n\t`; + values += `, <${response.data.list[i].cn}>\n\t`; + } + } + } + + result.data += `)`; + values += `);`; + result.data += values; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableUpdateStatement(args) { + const _func = this.tableUpdateStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + result.data = `UPDATE ${this.getTnPath(args.tn)} \nSET\n`; + const response = await this.columnList(args); + if (response.data && response.data.list) { + for (let i = 0; i < response.data.list.length; ++i) { + if (!i) { + result.data += `${response.data.list[i].cn} = <\`${response.data.list[i].cn}\`>\n\t`; + } else { + result.data += `,${response.data.list[i].cn} = <\`${response.data.list[i].cn}\`>\n\t`; + } + } + } + + result.data += ';'; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableDeleteStatement(args) { + const _func = this.tableDeleteStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + result.data = `DELETE FROM ${this.getTnPath(args.tn)} where ;`; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableTruncateStatement(args) { + const _func = this.tableTruncateStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + result.data = `TRUNCATE TABLE ${this.getTnPath(args.tn)};`; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableSelectStatement(args) { + const _func = this.tableSelectStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + result.data = `SELECT `; + const response = await this.columnList(args); + if (response.data && response.data.list) { + for (let i = 0; i < response.data.list.length; ++i) { + if (!i) { + result.data += `${response.data.list[i].cn}\n\t`; + } else { + result.data += `, ${response.data.list[i].cn}\n\t`; + } + } + } + + result.data += ` FROM ${args.tn};`; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + alterTablePK(t, n, o, _existingQuery, createTable = false) { + const shouldSanitize = true; + const numOfPksInOriginal = []; + const numOfPksInNew = []; + let pksChanged = 0; + + for (let i = 0; i < n.length; ++i) { + if (n[i].pk) { + if (n[i].altered !== 4) numOfPksInNew.push(n[i].cn); + } + } + + for (let i = 0; i < o.length; ++i) { + if (o[i].pk) { + numOfPksInOriginal.push(o[i].cn); + } + } + + if (numOfPksInNew.length === numOfPksInOriginal.length) { + for (let i = 0; i < numOfPksInNew.length; ++i) { + if (numOfPksInOriginal[i] !== numOfPksInNew[i]) { + pksChanged = 1; + break; + } + } + } else { + pksChanged = numOfPksInNew.length - numOfPksInOriginal.length; + } + + let query = ''; + if (!numOfPksInNew.length && !numOfPksInOriginal.length) { + // do nothing + } else if (pksChanged) { + // todo: pk constraint delete + query += numOfPksInOriginal.length ? ',DROP PRIMARY KEY' : ''; + + if (numOfPksInNew.length) { + if (createTable) { + // query += `, PRIMARY KEY(${numOfPksInNew.join(',')})`; + query += this.genQuery( + `, PRIMARY KEY(??)`, + [numOfPksInNew], + shouldSanitize + ); + } else { + query += this.genQuery( + `ALTER TABLE ?? ADD PRIMARY KEY(??)`, + [this.getTnPath(t), numOfPksInNew], + shouldSanitize + ); + } + } + } + + return query; + } + + getTnPath(t) { + return this.schema ? `${this.schema}.${t}` : t; + } + + alterTableRemoveColumn(t, n, o, existingQuery) { + const shouldSanitize = true; + let query = existingQuery ? ';' : ''; + if (n.cdf) { + query += this.genQuery( + `\nALTER TABLE ?? DROP CONSTRAINT ??;`, + [this.getTnPath(t), o.default_constraint_name || `DF_${t}_${n.cn}`], + shouldSanitize + ); + } + query += this.genQuery( + `\nALTER TABLE ?? DROP COLUMN ??`, + [this.getTnPath(t), n.cn], + shouldSanitize + ); + return query; + } + + createTableColumn(t, n, o, existingQuery) { + return this.alterTableColumn(t, n, o, existingQuery, 0); + } + + alterTableAddColumn(t, n, o, existingQuery) { + return this.alterTableColumn(t, n, o, existingQuery, 1); + } + + alterTableChangeColumn(t, n, o, existingQuery) { + return this.alterTableColumn(t, n, o, existingQuery, 2); + } + + createTable(table, args) { + const shouldSanitize = true; + let query = ''; + + for (let i = 0; i < args.columns.length; ++i) { + query += this.createTableColumn(table, args.columns[i], null, query); + } + query += this.alterTablePK(table, args.columns, [], query, true); + query = this.genQuery( + `CREATE TABLE ?? (${query});`, + [this.getTnPath(args.tn)], + shouldSanitize + ); + return query; + } + + alterTableColumn(t, n, o, existingQuery, change = 2) { + let query = ''; + + const defaultValue = getDefaultValue(n); + const shouldSanitize = true; + + if (change === 0) { + query = existingQuery ? ',' : ''; + query += this.genQuery(`?? ${n.dt}`, [n.cn], shouldSanitize); + query += !getDefaultLengthIsDisabled(n.dt) && n.dtxp ? `(${n.dtxp})` : ''; + query += n.rqd ? ' NOT NULL' : ' NULL'; + query += n.ai ? ' IDENTITY(1,1)' : ' '; + query += defaultValue + ? this.genQuery( + ` CONSTRAINT ?? DEFAULT ${defaultValue}`, + [`DF_${t}_${n.cn}`], + shouldSanitize + ) + : ''; + if (defaultValue) { + n.default_constraint_name = `DF_${t}_${n.cn}`; + } + } else if (change === 1) { + query += this.genQuery(` ADD ?? ${n.dt}`, [n.cn], shouldSanitize); + query += !getDefaultLengthIsDisabled(n.dt) && n.dtxp ? `(${n.dtxp})` : ''; + query += n.rqd ? ' NOT NULL' : ' NULL'; + query += n.ai ? ' IDENTITY(1,1)' : ' '; + query += defaultValue + ? this.genQuery( + ` CONSTRAINT ?? DEFAULT ${defaultValue}`, + [`DF_${t}_${n.cn}`], + shouldSanitize + ) + : ' '; + // shouldSanitize = false; + query = this.genQuery( + `ALTER TABLE ?? ${query};`, + [this.getTnPath(t)], + shouldSanitize + ); + if (defaultValue) { + n.default_constraint_name = `DF_${t}_${n.cn}`; + } + } else { + //ALTER TABLE mssql_dev_1.${this.schema}.basictypes DROP CONSTRAINT DF__basictype__title__3D5E1FD2 + if (n.cn !== o.cn) { + // query += `\nALTER TABLE ${t} RENAME COLUMN ${n.cno} TO ${n.cn} + query += this.genQuery( + `\nEXEC sp_rename ?, ?, 'COLUMN';\n`, + [`${this.getTnPath(t)}.${n.cno}`, `${n.cn}`], + shouldSanitize + ); + } + + if (n.dtxp !== o.dtxp && !['text'].includes(n.dt)) { + query += this.genQuery( + `\nALTER TABLE ?? ALTER COLUMN ?? ${n.dt}(${n.dtxp});\n`, + [this.getTnPath(t), n.cn], + shouldSanitize + ); + } else if (n.dt !== o.dt) { + query += this.genQuery( + `\nALTER TABLE ?? ALTER COLUMN ?? TYPE ${n.dt};\n`, + [this.getTnPath(t), n.cn], + shouldSanitize + ); + } + + if (n.rqd !== o.rqd) { + query += this.genQuery( + `\nALTER TABLE ?? ALTER COLUMN ?? ${n.dt}`, + [this.getTnPath(t), n.cn], + shouldSanitize + ); + if ( + ![ + 'int', + 'bigint', + 'bit', + 'real', + 'float', + 'decimal', + 'money', + 'smallint', + 'tinyint', + 'geometry', + 'datetime', + 'text' + ].includes(n.dt) + ) + query += n.dtxp && n.dtxp != -1 ? `(${n.dtxp})` : ''; + query += n.rqd ? ` NOT NULL;\n` : ` NULL;\n`; + } + + if (n.cdf !== o.cdf || n.cn !== o.cn) { + if (o.default_constraint_name) + query += this.genQuery( + `\nALTER TABLE ?? DROP CONSTRAINT ??;`, + [this.getTnPath(t), o.default_constraint_name], + shouldSanitize + ); + if (n.cdf) { + query += this.genQuery( + `\nALTER TABLE ?? ADD CONSTRAINT ?? DEFAULT ${n.cdf} FOR ??;`, + [this.getTnPath(t), `DF_${n.tn}_${n.cn}`, n.cn], + shouldSanitize + ); + // todo: hack + n.default_constraint_name = `DF_${n.tn}_${n.cn}`; + } + } + } + return query; + } + + get schema() { + return ( + (this.connectionConfig && + this.connectionConfig.searchPath && + this.connectionConfig.searchPath[0]) || + 'dbo' + ); + } + + /** + * + * @param {Object} args + * @returns {Object} result + * @returns {Number} code + * @returns {String} message + */ + async totalRecords(args: any = {}) { + const func = this.totalRecords.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + const data = await this.sqlClient + .raw(`SELECT SUM(RecordCount) AS TotalRecords FROM + (SELECT (SCHEMA_NAME(A.schema_id) + '.' + A.Name) AS TableName, AVG(B.rows) AS RecordCount + FROM sys.objects A + INNER JOIN sys.partitions B ON A.object_id = B.object_id + WHERE A.type = 'U' + GROUP BY A.schema_id,A.Name) src`); + result.data = data[0]; + } catch (e) { + result.code = -1; + result.message = e.message; + result.object = e; + } finally { + log.api(`${func} :result: ${result}`); + } + return result; + } +} + +function getDefaultValue(n) { + if (n.cdf === undefined || n.cdf === null) return n.cdf; + switch (n.dt) { + case 'boolean': + case 'bool': + case 'tinyint': + case 'int': + case 'samllint': + case 'bigint': + case 'integer': + case 'smallint': + case 'mediumint': + case 'int2': + case 'int4': + case 'int8': + case 'long': + case 'serial': + case 'bigserial': + case 'smallserial': + case 'number': + case 'float': + case 'double': + case 'decimal': + case 'numeric': + case 'real': + case 'double precision': + case 'money': + case 'smallmoney': + case 'dec': + return n.cdf; + break; + + case 'datetime': + case 'timestamp': + case 'date': + case 'time': + if ( + n.cdf.toLowerCase().indexOf('getdate') > -1 || + /\(([\d\w'", ]*)\)$/.test(n.cdf) + ) { + return n.cdf; + } + return JSON.stringify(n.cdf); + break; + default: + return JSON.stringify(n.cdf); + break; + } +} + +function getDefaultLengthIsDisabled(type) { + switch (type) { + // case 'binary': + // case 'char': + // case 'sql_variant': + // case 'nvarchar': + // case 'nchar': + // case 'ntext': + // case 'varbinary': + // case 'sysname': + case 'bigint': + case 'bit': + case 'date': + case 'datetime': + case 'datetime2': + case 'datetimeoffset': + case 'decimal': + case 'float': + case 'geography': + case 'geometry': + case 'heirarchyid': + case 'image': + case 'int': + case 'money': + case 'numeric': + case 'real': + case 'json': + case 'smalldatetime': + case 'smallint': + case 'smallmoney': + case 'text': + case 'time': + case 'timestamp': + case 'tinyint': + case 'uniqueidentifier': + case 'xml': + return true; + break; + default: + case 'varchar': + return false; + break; + } +} + +export default MssqlClient; diff --git a/packages/nocodb/src/lib/db/sql-client/lib/mssql/mssql.queries.ts b/packages/nocodb/src/lib/db/sql-client/lib/mssql/mssql.queries.ts new file mode 100644 index 0000000000..ccd133ee1b --- /dev/null +++ b/packages/nocodb/src/lib/db/sql-client/lib/mssql/mssql.queries.ts @@ -0,0 +1,39 @@ +// https://docs.microsoft.com/en-us/sql/t-sql/data-types/data-types-transact-sql?view=sql-server-2017 + +export default { + bigint: {}, + numeric: {}, + bit: {}, + smallint: {}, + decimal: {}, + smallmoney: {}, + int: {}, + tinyint: {}, + money: {}, + float: {}, + real: {}, + date: {}, + datetimeoffset: {}, + datetime2: {}, + smalldatetime: {}, + datetime: {}, + time: {}, + char: {}, + varchar: {}, + text: {}, + nchar: {}, + nvarchar: {}, + ntext: {}, + binary: {}, + varbinary: {}, + image: {}, + cursor: {}, + rowversion: {}, + hierarchyid: {}, + uniqueidentifier: {}, + sql_variant: {}, + xml: {}, + 'Spatial Geometry Types': {}, + 'Spatial Geography Types': {}, + table: {} +}; diff --git a/packages/nocodb/src/lib/db/sql-client/lib/mysql/MysqlClient.ts b/packages/nocodb/src/lib/db/sql-client/lib/mysql/MysqlClient.ts new file mode 100644 index 0000000000..a7786980ac --- /dev/null +++ b/packages/nocodb/src/lib/db/sql-client/lib/mysql/MysqlClient.ts @@ -0,0 +1,2617 @@ +import knex from 'knex'; + +import lodash from 'lodash'; +import Debug from '../../../util/Debug'; +import Emit from '../../../util/emit'; +import Result from '../../../util/Result'; + +import queries from './mysql.queries'; +import KnexClient from '../KnexClient'; +import jsonfile from 'jsonfile'; +import path from 'path'; +import mkdirp from 'mkdirp'; +import { promisify } from 'util'; + +import { nanoid } from 'nanoid'; + +import fakerFunctionList from './fakerFunctionList'; +import * as findDataType from './findDataTypeMapping'; +import levenshtein from 'fast-levenshtein'; + +const log = new Debug('MysqlClient'); +const evt = new Emit(); + +class MysqlClient extends KnexClient { + private queries: any; + private _version: any; + private types: any; + + constructor(connectionConfig) { + super(connectionConfig); + this.queries = queries; + this._version = {}; + } + + emit(data) { + log.api(data); + evt.evt.emit('UI', { + status: 0, + data: `SQL : ${data}` + }); + } + + emitW(data) { + log.warn(data); + evt.evt.emit('UI', { + status: 1, + data: `SQL : ${data}` + }); + } + + emitE(data) { + log.error(data); + evt.evt.emit('UI', { + status: -1, + data: `SQL : ${data}` + }); + } + + /** + * + * + * @param {Object} args + * @returns {Object} result + * @returns {Number} code + * @returns {String} message + */ + async schemaCreateWithCredentials(args): Promise { + const func = this.schemaCreateWithCredentials.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + if (!args.schema) { + args.schema = `nc${nanoid(8)}`; + } + if (!args.user) { + args.user = `nc${nanoid(8)}`; + } + if (!args.password) { + args.password = nanoid(16); + } + + // const connectionParamsWithoutDb = JSON.parse( + // JSON.stringify(this.connectionConfig) + // ); + // + // delete connectionParamsWithoutDb.connection.database; + // + // const tempSqlClient = knex(connectionParamsWithoutDb); + + const data = await this.sqlClient.raw( + 'create database if not exists ??', + [args.schema] + ); + + await this.sqlClient.raw( + `CREATE USER ?@'localhost' IDENTIFIED WITH mysql_native_password BY ?`, + [args.user, args.password] + ); + await this.sqlClient.raw( + `GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, INDEX, DROP, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES PRIVILEGES ON ??.* TO ?@'localhost'`, + [args.schema, args.user] + ); + await this.sqlClient.raw(`FLUSH PRIVILEGES`); + + log.debug('Create database if not exists', data); + + // create new knex client + // this.sqlClient = knex(this.connectionConfig); + // tempSqlClient.destroy(); + result.object = args; + } catch (e) { + // log.ppe(e); + result.code = -1; + result.message = e.message; + result.object = e; + } + return result; + } + + /** + * + * + * @param {Object} args + * @returns {Object} result + * @returns {Number} code + * @returns {String} message + */ + async testConnection(args: any = {}) { + const func = this.testConnection.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + // await this.sqlClient.raw(this.getQuery(_func)) + await this.sqlClient.raw('SELECT 1+1 as data'); + } catch (e) { + // log.ppe(e); + result.code = -1; + result.message = e.message; + result.object = e; + } finally { + if (result.code) { + this.emitE(`TestConnection result: ${result.message}`); + } else { + this.emit(`TestConnection result: ${result.code}`); + } + } + return result; + } + + getKnexDataTypes() { + const result = new Result(); + + // result.data.list = [ + // "integer", + // "bigInteger", + // "text", + // "string", + // "float", + // "decimal", + // "boolean", + // "date", + // // "datetime", + // "time", + // "timestamp", + // "binary", + // "enu", + // "json", + // "specificType" + // ]; + + result.data.list = [ + 'int', + 'tinyint', + 'smallint', + 'mediumint', + 'bigint', + 'float', + 'decimal', + 'double', + 'real', + 'bit', + 'boolean', + 'serial', + 'date', + 'datetime', + 'timestamp', + 'time', + 'year', + 'char', + 'varchar', + 'nchar', + // "nvarchar", + 'text', + 'tinytext', + 'mediumtext', + 'longtext', + 'binary', + 'varbinary', + 'blob', + 'tinyblob', + 'mediumblob', + 'longblob', + 'enum', + 'set', + 'time', + 'geometry', + 'point', + 'linestring', + 'polygon', + 'multipoint', + 'multilinestring', + 'multipolygon', + // "geometrycollection", + 'json' + ]; + + return result; + } + + /** + * Returns mysql version + * + * @param {Object} args - for future reasons + * @returns {Object} result + * @returns {Number} result.code + * @returns {String} result.message + * @returns {Object} result.data + * @returns {Object} result.data.object - {version, primary, major, minor,key} + */ + async version(args?: any) { + const func = this.version.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + result.data.object = {}; + const data = await this.sqlClient.raw('select version() as version'); + log.debug(data[0][0]); + result.data.object.version = data[0][0].version; + const versions = data[0][0].version.split('.'); + + if (versions.length && versions.length === 3) { + result.data.object.primary = versions[0]; + result.data.object.major = versions[1]; + result.data.object.minor = versions[2]; + result.data.object.key = versions[0] + versions[1]; + } else { + result.code = -1; + result.message = `Invalid version : ${data[0][0].version}`; + } + } catch (e) { + log.ppe(e); + result.code = -1; + result.message = e.message; + } finally { + log.api(`${func} :result: %o`, result); + } + return result; + } + + /** + * + * + * @param {Object} args + * @returns {Object} result + * @returns {Number} code + * @returns {String} message + * @returns {String[]} list + */ + async getDataTypes(args: any = {}) { + const func = this.getDataTypes.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + await this.version(); + if (this._version.key in this.types) { + result.data.list = this.types[this._version.key]; + } else { + result.data.list = this.types.default; + } + } catch (e) { + log.ppe(e); + result.code = -1; + result.message = e.message; + } finally { + log.api(`${func} :result: ${result}`); + } + return result; + } + + /** + * + * @param {Object} args + * @param {String} args.database + * @returns {Result} + */ + async createDatabaseIfNotExists(args: any = {}) { + const func = this.createDatabaseIfNotExists.name; + const result = new Result(); + log.api(`${func}:args:`, args, this.connectionConfig); + + try { + // create a new knex client without database param + const connectionParamsWithoutDb = JSON.parse( + JSON.stringify(this.connectionConfig) + ); + + delete connectionParamsWithoutDb.connection.database; + + const tempSqlClient = knex(connectionParamsWithoutDb); + + const data = await tempSqlClient.raw(this.queries[func].default.sql, [ + args.database + ]); + + log.debug('Create database if not exists', data); + + // create new knex client + this.sqlClient = knex(this.connectionConfig); + await tempSqlClient.destroy(); + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + async dropDatabase(args: any = {}) { + const func = this.dropDatabase.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + log.api('dropping database:', args); + await this.sqlClient.raw(this.queries[func].default.sql, [args.database]); + } catch (e) { + if (e) log.ppe(e.message, func); + } + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param args {tn} + * @returns + */ + async createTableIfNotExists(args: any = {}) { + const func = this.createTableIfNotExists.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + /** ************** START : create _evolution table if not exists *************** */ + const exists = await this.sqlClient.schema.hasTable(args.tn); + + if (!exists) { + await this.sqlClient.schema.createTable(args.tn, function(table) { + table.increments(); + table.string('title').notNullable(); + table.string('titleDown').nullable(); + table.string('description').nullable(); + table.integer('batch').nullable(); + table.string('checksum').nullable(); + table.integer('status').nullable(); + table.dateTime('created'); + table.timestamps(); + }); + log.debug('Table created:', `${args.tn}`); + } else { + log.debug(`${args.tn} tables exists`); + } + /** ************** END : create _evolution table if not exists *************** */ + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + + return result; + } + + // async startTransaction() { + // let err = await this.sqlClient.raw("SET autocommit = 0"); + // log.debug("SET autocommit = 0:", err); + // err = await this.sqlClient.raw("start transaction"); + // log.debug("start transaction:", err); + // } + + // async commit() { + // const err = await this.sqlClient.raw("commit"); + // log.debug("commit:", err); + // await this.sqlClient.raw("SET autocommit = 1"); + // log.debug("SET autocommit = 1:", err); + // } + + // async rollback() { + // const err = await this.sqlClient.raw("rollback"); + // log.debug("rollback:", err); + // await this.sqlClient.raw("SET autocommit = 1"); + // log.debug("SET autocommit = 1:", err); + // } + + async hasTable(args) { + const func = this.hasTable.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + const response = await this.sqlClient.schema.hasTable(args.tn); + result.data.value = response; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + async hasDatabase(args) { + const func = this.hasDatabase.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + const rows = await this.sqlClient.raw(this.queries[func].default.sql, [ + `${args.databaseName}` + ]); + result.data.value = rows.length > 0; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - for future reasons + * @returns {Object[]} - databases + * @property {String} - databases[].database_name + */ + async databaseList(args: any = {}) { + const func = this.databaseList.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + const response = await this.sqlClient.raw('SHOW databases'); + + log.debug(response.length); + + if (response.length === 2) { + for (let i = 0; i < response[0].length; ++i) { + response[0][i].database_name = response[0][i].Database; + } + result.data.list = response[0]; + } else { + log.debug( + 'Unknown response for databaseList:', + result.data.list.length + ); + result.data.list = []; + } + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result.data.list.length); + + return result; + } + + /** + * + * @param {Object} - args - for future reasons + * @returns {Object[]} - tables + * @property {String} - tables[].tn + */ + async tableList(args?: any) { + const func = this.tableList.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + const response = await this.sqlClient.raw( + `SHOW FULL TABLES WHERE TABLE_TYPE NOT LIKE 'VIEW'` + ); + // const keyInResponse = `Tables_in_${ + // this.connectionConfig.connection.database.toLowerCase() + // }`; + let keyInResponse; + + if (response.length === 2) { + for (let i = 0; i < response[0].length; ++i) { + if (!keyInResponse) { + keyInResponse = Object.keys(response[0][i]).find(k => + /^Tables_in_/i.test(k) + ); + } + response[0][i].tn = response[0][i][keyInResponse]; + } + result.data.list = response[0]; + } else { + log.debug( + 'Unknown response for databaseList:', + result.data.list.length + ); + result.data.list = []; + } + + this.emitTele({ + mysql: 1, + table_count: result.data.list.length, + api_count: result.data.list.length * 10 + }); + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result.data.list); + + return result; + } + + async schemaList(args) { + const func = this.schemaList.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + const response = await this.sqlClient.raw( + `select schema_name + from + information_schema.schemata + where + schema_name not in ('information_schema','performance_schema','sys','mysql') + order by schema_name;` + ); + if (response.length === 2) { + result.data.list = response[0].map(v => + lodash.mapKeys(v, (_, k) => k.toLowerCase()) + ); + } else { + log.debug('Unknown response for schemaList:', result.data.list.length); + result.data.list = []; + } + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result.data.list); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - columns + * @returns {string} - columns[].ai, + * @returns {string} - columns[].clen, + * @returns {string} - columns[].csn, + * @returns {string} - columns[].cc, + * @returns {string} - columns[].cdf, + * @returns {string} - columns[].cn, + * @returns {string} - columns[].cop, + * @returns {string} - columns[].ct, + * @returns {string} - columns[].dt, + * @returns {string} - columns[].dtx, + * @returns {string} - columns[].dtxp, + * @returns {string} - columns[].dtxs, + * @returns {string} - columns[].nrqd, + * @returns {string} - columns[].rqd, + * @returns {string} - columns[].np, + * @returns {string} - columns[].ns, + * @returns {string} - columns[].cno, + * @returns {string} - columns[].pk, + * @returns {string} - columns[].tn, + * @returns {string} - columns[].unique, + * @returns {string} - columns[].un + */ + async columnList(args: any = {}) { + const func = this.columnList.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + await this._getQuery({ + func + }), + [args.databaseName, args.tn, args.databaseName, args.tn] + ); + + if (response.length === 2) { + const columns = []; + + if (!response[0].length) { + result.code = -1; + result.message = 'Table not found or Invalid table name'; + } + + for (let i = 0; i < response[0].length; ++i) { + const column: any = {}; + + response[0][i] = lodash.mapKeys(response[0][i], (_v, k) => + k.toLowerCase() + ); + + if (this._version.key === '57' || this._version.key === '80') { + column.dp = response[0][i].dp; + column.generated_expression = response[0][i].generated_expression; + } + + column.tn = response[0][i].tn; + column.cn = response[0][i].cn; + column.cno = response[0][i].cn; + column.dt = response[0][i].dt; + column.np = response[0][i].np; + column.ns = response[0][i].ns; + column.clen = response[0][i].clen; + // column.dp = response[0][i].dp; + column.cop = response[0][i].cop; + + column.pk = response[0][i].ck === 'PRI'; + + column.nrqd = response[0][i].nrqd !== 'NO'; + column.rqd = !column.nrqd; + + response[0][i].ct = response[0][i].ct || ''; + column.un = response[0][i].ct.indexOf('unsigned') !== -1; + + column.ct = response[0][i].ct || ''; + response[0][i].ext = response[0][i].ext || ''; + column.ai = response[0][i].ext.indexOf('auto_increment') !== -1; + + response[0][i].cst = response[0][i].cst || ' '; + column.unique = response[0][i].cst.indexOf('UNIQUE') !== -1; + + if (column.dt === 'timestamp' || column.dt === 'datetime') { + if (response[0][i].cdf && response[0][i].ext) { + const str = response[0][i].ext; + // column.cdf_sequelize = response[0][i].cdf; + column.cdf = + response[0][i].cdf + + str.substring( + str.lastIndexOf('DEFAULT_GENERATED') + + 'DEFAULT_GENERATED'.length + ); + } else { + column.cdf = response[0][i].cdf; + } + } else { + column.cdf = response[0][i].cdf; + } + + column.cc = response[0][i].cc; + + column.csn = response[0][i].csn; + + // knex specific + column.dtx = 'specificType'; + //column.data_type_x_specific = response[0][i].dt; + if ( + column.dt === 'int' || + column.dt === 'tinyint' || + column.dt === 'mediumint' || + column.dt === 'bigint' || + column.dt === 'enum' || + column.dt === 'set' + ) { + column.dtxp = column.ct.substring( + column.ct.lastIndexOf('(') + 1, + column.ct.lastIndexOf(')') + ); + column.dtxs = response[0][i].ns; + } else { + column.dtxp = + response[0][i].clen || response[0][i].np || response[0][i].dp; + column.dtxs = response[0][i].ns; + } + + column.au = false; + columns.push(column); + + // column['dtx'] = response[0][i]['dt']; + } + + result.data.list = columns; + } else { + log.debug('Unknown response for databaseList:', response); + result.data.list = []; + } + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result list length = `, result.data.list.length); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - indexes + * @property {String} - indexes[].table - + * @property {String} - indexes[].cn - + * @property {String} - indexes[].key_name - + * @property {String} - indexes[].non_unique - + * @property {String} - indexes[].seq_in_index - + * @property {String} - indexes[].collation - + * @property {String} - indexes[].cardinality - + * @property {String} - indexes[].sub_part - + * @property {String} - indexes[].packed - + * @property {String} - indexes[].null - + * @property {String} - indexes[].index_type - + * @property {String} - indexes[].comment - + * @property {String} - indexes[].index_comment - + */ + async indexList(args: any = {}) { + const func = this.indexList.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + const response = await this.sqlClient.raw( + this.queries[func].default.sql, + [args.tn] + ); + + if (response.length === 2) { + const indexes = []; + + for (let i = 0; i < response[0].length; ++i) { + let index = response[0][i]; + index = lodash.mapKeys(index, function(_v, k) { + return k.toLowerCase(); + }); + index.cn = index.column_name; + delete index.column_name; + index.tn = index.table; + delete index.table; + index.non_unique_original = index.non_unique; + indexes.push(index); + } + + result.data.list = indexes; + } else { + log.debug( + 'Unknown response for databaseList:', + result.data.list.length + ); + result.data.list = []; + } + log.api(`${func}: result %O`, result.data.list); + } catch (e) { + log.ppe(e, func); + throw e; + } + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - indexes + * @property {String} - indexes[].cstn - + * @property {String} - indexes[].cn - + * @property {String} - indexes[].op - + * @property {String} - indexes[].puc - + * @property {String} - indexes[].cst - + */ + async constraintList(args: any = {}) { + const func = this.constraintList.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + const response = await this.sqlClient.raw( + await this._getQuery({ + func + }), + [this.connectionConfig.connection.database, args.tn] + ); + + if (response.length === 2) { + const indexes = []; + + for (let i = 0; i < response[0].length; ++i) { + let index = response[0][i]; + index = lodash.mapKeys(index, function(_v, k) { + return k.toLowerCase(); + }); + indexes.push(index); + } + + result.data.list = indexes; + } else { + log.debug( + 'Unknown response for databaseList:', + result.data.list.length + ); + result.data.list = []; + } + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - relations + * @property {String} - relations[].tn + * @property {String} - relations[].cstn - + * @property {String} - relations[].tn - + * @property {String} - relations[].cn - + * @property {String} - relations[].rtn - + * @property {String} - relations[].rcn - + * @property {String} - relations[].puc - + * @property {String} - relations[].ur - + * @property {String} - relations[].dr - + * @property {String} - relations[].mo - + */ + async relationList(args: any = {}) { + const func = this.relationList.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + this.queries[func].default.sql, + [args.databaseName, args.tn] + ); + + if (response.length === 2) { + const relations = []; + + for (let i = 0; i < response[0].length; ++i) { + let relation = response[0][i]; + relation = lodash.mapKeys(relation, function(_v, k) { + return k.toLowerCase(); + }); + relations.push(relation); + } + + result.data.list = relations; + } else { + log.debug('Unknown response for databaseList:', response); + result.data.list = []; + } + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - relations + * @property {String} - relations[].tn + * @property {String} - relations[].cstn - + * @property {String} - relations[].tn - + * @property {String} - relations[].cn - + * @property {String} - relations[].rtn - + * @property {String} - relations[].rcn - + * @property {String} - relations[].puc - + * @property {String} - relations[].ur - + * @property {String} - relations[].dr - + * @property {String} - relations[].mo - + */ + async relationListAll(args: any = {}) { + const func = this.relationListAll.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + this.queries[func].default.sql, + { databaseName: args.databaseName } + ); + + if (response.length === 2) { + const relations = []; + + for (let i = 0; i < response[0].length; ++i) { + let relation = response[0][i]; + relation = lodash.mapKeys(relation, function(_v, k) { + return k.toLowerCase(); + }); + relations.push(relation); + } + + result.data.list = relations; + + this.emitTele({ + mysql: 1, + relation_count: result.data.list.length, + api_count: result.data.list.length * 10 + }); + } else { + log.debug('Unknown response for databaseList:', response); + result.data.list = []; + } + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - triggers + * @property {String} - triggers[].trigger + * @property {String} - triggers[].event + * @property {String} - triggers[].table + * @property {String} - triggers[].statement + * @property {String} - triggers[].timing + * @property {String} - triggers[].created + * @property {String} - triggers[].sql_mode + * @property {String} - triggers[].definer + * @property {String} - triggers[].character_set_client + * @property {String} - triggers[].collation_connection + * @property {String} - triggers[].database collation + */ + async triggerList(args: any = {}) { + const func = this.triggerList.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + this.queries[func].default.sql, + [`%${args.tn}%`] + ); + + if (response.length === 2) { + const triggers = []; + + for (let i = 0; i < response[0].length; ++i) { + let trigger = response[0][i]; + trigger = lodash.mapKeys(trigger, function(_v, k) { + return k.toLowerCase(); + }); + trigger.trigger_name = trigger.trigger; + triggers.push(trigger); + } + + result.data.list = triggers; + } else { + log.debug('Unknown response for databaseList:', response); + result.data.list = []; + } + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @returns {Object[]} - functions + * @property {String} - functions[].function_name + * @property {String} - functions[].type + * @property {String} - functions[].definer + * @property {String} - functions[].modified + * @property {String} - functions[].created + * @property {String} - functions[].security_type + * @property {String} - functions[].comment + * @property {String} - functions[].character_set_client + * @property {String} - functions[].collation_connection + * @property {String} - functions[].database collation + */ + async functionList(args: any = {}) { + const func = this.functionList.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + this.queries[func].default.sql, + [args.databaseName] + ); + + if (response.length === 2) { + const functions = []; + + for (let i = 0; i < response[0].length; ++i) { + let fn = response[0][i]; + fn = lodash.mapKeys(fn, function(_v, k) { + return k.toLowerCase(); + }); + fn.function_name = fn.name; + functions.push(fn); + } + + result.data.list = functions; + } else { + log.debug( + 'Unknown response for databaseList:', + result.data.list.length + ); + result.data.list = []; + } + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - For future reasons + * @returns {Object[]} - procedures + * @property {String} - procedures[].procedure_name + * @property {String} - procedures[].type + * @property {String} - procedures[].definer + * @property {String} - procedures[].modified + * @property {String} - procedures[].created + * @property {String} - procedures[].security_type + * @property {String} - procedures[].comment + * @property {String} - procedures[].definer + * @property {String} - procedures[].character_set_client + * @property {String} - procedures[].collation_connection + * @property {String} - procedures[].database collation + */ + async procedureList(args: any = {}) { + const func = this.procedureList.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + args.databaseName = this.connectionConfig.connection.database; + // `show procedure status where db='${args.databaseName}'`, + const response = await this.sqlClient.raw( + this.queries[func].default.sql, + [args.databaseName] + ); + + if (response.length === 2) { + const procedures = []; + + for (let i = 0; i < response[0].length; ++i) { + let procedure = response[0][i]; + procedure = lodash.mapKeys(procedure, function(_v, k) { + return k.toLowerCase(); + }); + procedure.procedure_name = procedure.name; + procedures.push(procedure); + } + + result.data.list = procedures; + } else { + log.debug('Unknown response for databaseList:', response); + result.data.list = []; + } + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @returns {Object[]} - views + * @property {String} - views[].sql_mode + * @property {String} - views[].create_function + * @property {String} - views[].database collation + * @property {String} - views[].collation_connection + * @property {String} - views[].character_set_client + */ + async viewList(args: any = {}) { + const func = this.viewList.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + args.databaseName = this.connectionConfig.connection.database; + // `SHOW FULL TABLES IN ${args.databaseName} WHERE TABLE_TYPE LIKE 'VIEW';` + const response = await this.sqlClient.raw( + this.queries[func].default.sql, + [] + ); + + let keyInResponse; + + if (response.length === 2) { + const views = []; + + for (let i = 0; i < response[0].length; ++i) { + if (!keyInResponse) { + keyInResponse = Object.keys(response[0][i]).find(k => + /^Tables_in_/i.test(k) + ); + } + const view = response[0][i]; + view.view_name = view[keyInResponse]; + views.push(view); + } + + result.data.list = views; + } else { + log.debug('Unknown response for databaseList:', response); + result.data.list = []; + } + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result.data.list.length); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.function_name - + * @returns {Object[]} - functions + * @property {String} - sql_mode + * @property {String} - create_function + * @property {String} - database collation + * @property {String} - collation_connection + * @property {String} - character_set_client + */ + async functionRead(args: any = {}) { + const func = this.functionRead.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + args.databaseName = this.connectionConfig.connection.database; + // `SHOW CREATE FUNCTION ${args.function_name};` + const response = await this.sqlClient.raw( + this.queries[func].default.sql, + [args.function_name] + ); + + if (response.length === 2) { + const _functions = []; + + for (let i = 0; i < response[0].length; ++i) { + let _function = response[0][i]; + + _function = lodash.mapKeys(_function, function(_v, k) { + return k.toLowerCase(); + }); + + _function.create_function = _function['create function']; + + _functions.push(_function); + } + + result.data.list = _functions; + } else { + log.debug('Unknown response for databaseList:', response); + result.data.list = []; + } + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.procedure_name - + * @returns {Object[]} - functions + * @property {String} - sql_mode + * @property {String} - create_function + * @property {String} - database collation + * @property {String} - collation_connection + * @property {String} - character_set_client + */ + async procedureRead(args: any = {}) { + const func = this.procedureRead.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + args.databaseName = this.connectionConfig.connection.database; + // `show create procedure ${args.procedure_name};` + const response = await this.sqlClient.raw( + this.queries[func].default.sql, + [args.procedure_name] + ); + + if (response.length === 2) { + const procedures = []; + + for (let i = 0; i < response[0].length; ++i) { + let procedure = response[0][i]; + + procedure = lodash.mapKeys(procedure, function(_v, k) { + return k.toLowerCase(); + }); + + procedure.create_procedure = procedure['create procedure']; + + procedures.push(procedure); + } + + result.data.list = procedures; + } else { + log.debug('Unknown response for databaseList:', response); + result.data.list = []; + } + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.view_name - + * @returns {Object[]} - views + * @property {String} - views[].tn + */ + async viewRead(args: any = {}) { + const func = this.viewRead.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + args.databaseName = this.connectionConfig.connection.database; + // `SELECT * FROM INFORMATION_SCHEMA.VIEWS + // WHERE TABLE_SCHEMA = '${args.databaseName}' + // AND TABLE_NAME = '${args.view_name}';` + const response = await this.sqlClient.raw( + this.queries[func].default.sql, + [args.databaseName, args.view_name] + ); + + if (response.length === 2) { + const views = []; + + for (let i = 0; i < response[0].length; ++i) { + let view = response[0][i]; + + view = lodash.mapKeys(view, function(_v, k) { + return k.toLowerCase(); + }); + + views.push(view); + } + + result.data.list = views; + } else { + log.debug('Unknown response for databaseList:', response); + result.data.list = []; + } + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + + return result; + } + + async triggerRead(args: any = {}) { + const func = this.triggerRead.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + args.databaseName = this.connectionConfig.connection.database; + // `SHOW FULL TABLES IN ${args.databaseName} WHERE TABLE_TYPE LIKE 'VIEW';`; + const response = await this.sqlClient.raw( + this.queries[func].default.sql, + [args.databaseName] + ); + + if (response.length === 2) { + const views = []; + + for (let i = 0; i < response[0].length; ++i) { + const view = response[0][i]; + view.view_name = view[`Tables_in_${args.databaseName}`]; + views.push(view); + } + + result.data.list = views; + } else { + log.debug('Unknown response for databaseList:', response); + result.data.list = []; + } + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + + return result; + } + + async schemaCreate(args: any = {}) { + const func = this.triggerList.name; + log.api(`${func}:args:`, args); + // `create database ${args.database_name}` + const rows = await this.sqlClient.raw(this.queries[func].default.sql, [ + args.database_name + ]); + return rows; + } + + async schemaDelete(args: any = {}) { + const func = this.schemaDelete.name; + log.api(`${func}:args:`, args); + // `drop database ${args.database_name}` + const rows = await this.sqlClient.raw(this.queries[func].default.sql, [ + args.database_name + ]); + return rows; + } + + /** ************** END : sql queries *************** */ + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.trigger_name + * @param {String} - args.event + * @param {String} - args.timing + * @returns {Object[]} - result rows + */ + async triggerCreate(args: any = {}) { + const func = this.triggerCreate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const query = + this.querySeparator() + + `CREATE TRIGGER \`${args.trigger_name}\` \n${args.timing} ${args.event}\nON ${args.tn} FOR EACH ROW\n${args.statement}`; + await this.sqlClient.raw(query); + result.data.object = { + upStatement: [{ sql: query }], + downStatement: [ + { sql: this.querySeparator() + `DROP TRIGGER ${args.trigger_name}` } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.trigger_name + * @param {String} - args.event + * @param {String} - args.timing + * @param {String} - args.oldStatement + * @returns {Object[]} - result rows + */ + async triggerUpdate(args: any = {}) { + const func = this.triggerUpdate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + await this.sqlClient.raw(`DROP TRIGGER ${args.trigger_name}`); + await this.sqlClient.raw( + `CREATE TRIGGER \`${args.trigger_name}\` \n${args.timing} ${args.event}\nON ${args.tn} FOR EACH ROW\n${args.statement}` + ); + + result.data.object = { + upStatement: [ + { + sql: `${this.querySeparator()}DROP TRIGGER ${ + args.trigger_name + };\n${this.querySeparator()}CREATE TRIGGER \`${ + args.trigger_name + }\` \n${args.timing} ${args.event}\nON ${args.tn} FOR EACH ROW\n${ + args.statement + }` + } + ], + downStatement: [ + { + sql: `${this.querySeparator()}CREATE TRIGGER \`${ + args.trigger_name + }\` \n${args.timing} ${args.event}\nON ${args.tn} FOR EACH ROW\n${ + args.oldStatement + }` + } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + async triggerDelete(args: any = {}) { + const func = this.triggerDelete.name; + const result = new Result(); + log.api(`${func}:args:`, args); + // `DROP TRIGGER ${args.trigger_name}` + try { + const query = `${this.querySeparator()}DROP TRIGGER ${args.trigger_name}`; + await this.sqlClient.raw(query); + + result.data.object = { + upStatement: [{ sql: query }], + downStatement: [ + { + sql: `${this.querySeparator()}CREATE TRIGGER \`${ + args.trigger_name + }\` \n${args.timing} ${args.event}\nON ${args.tn} FOR EACH ROW\n${ + args.oldStatement + }` + } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.view_name + * @param {String} - args.view_definition + * @returns {Object} - up and down statements + */ + async viewCreate(args: any = {}) { + const func = this.viewCreate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const query = + this.querySeparator() + + `CREATE VIEW ${args.view_name} AS \n${args.view_definition}`; + + await this.sqlClient.raw(query); + result.data.object = { + upStatement: [{ sql: query }], + downStatement: [ + { sql: this.querySeparator() + `DROP VIEW ${args.view_name}` } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.view_name + * @param {String} - args.view_definition + * @param {String} - args.oldViewDefination + * @returns {Object} - up and down statements + */ + async viewUpdate(args: any = {}) { + const func = this.viewUpdate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const query = + this.querySeparator() + + `CREATE OR REPLACE VIEW ${args.view_name} AS \n${args.view_definition}`; + + await this.sqlClient.raw(query); + result.data.object = { + upStatement: [{ sql: query }], + downStatement: [ + { + sql: + this.querySeparator() + + `CREATE VIEW ${args.view_name} AS \n${args.oldViewDefination}` + } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.view_name + * @param {String} - args.view_definition + * @param {String} - args.oldViewDefination + * @returns {Object} - up and down statements + */ + async viewDelete(args: any = {}) { + const func = this.viewDelete.name; + const result = new Result(); + log.api(`${func}:args:`, args); + // `DROP TRIGGER ${args.view_name}` + try { + const query = this.querySeparator() + `DROP VIEW ${args.view_name}`; + + await this.sqlClient.raw(query); + + result.data.object = { + upStatement: [{ sql: query }], + downStatement: [ + { + sql: + this.querySeparator() + + `CREATE VIEW ${args.view_name} AS \n${args.oldViewDefination}` + } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.function_name + * @param {String} - args.create_function + * @returns {Object} - up and down statements + */ + async functionCreate(args: any = {}) { + const func = this.functionCreate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + await this.sqlClient.raw(`${args.create_function}`); + result.data.object = { + upStatement: [ + { sql: this.querySeparator() + `${args.create_function}` } + ], + downStatement: [ + { sql: this.querySeparator() + `DROP FUNCTION ${args.function_name}` } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.function_name + * @param {String} - args.create_function + * @param {String} - args.oldCreateFunction + * @returns {Object} - up and down statements + */ + async functionUpdate(args: any = {}) { + const func = this.functionUpdate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + await this.sqlClient.raw(`DROP FUNCTION IF EXISTS ${args.function_name}`); + await this.sqlClient.raw(`${args.create_function}`); + result.data.object = { + upStatement: [ + { + sql: + this.querySeparator() + + `DROP FUNCTION IF EXISTS ${ + args.function_name + };${this.querySeparator()}\n${args.create_function}` + } + ], + downStatement: [ + { + sql: + this.querySeparator() + + `DROP FUNCTION IF EXISTS ${ + args.function_name + };${this.querySeparator()}${args.oldCreateFunction}` + } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.function_name + * @param {String} - args.create_function + * @returns {Object} - up and down statements + */ + async functionDelete(args: any = {}) { + const func = this.functionDelete.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + await this.sqlClient.raw(`DROP FUNCTION IF EXISTS ${args.function_name}`); + result.data.object = { + upStatement: [ + { + sql: + this.querySeparator() + + `DROP FUNCTION IF EXISTS ${args.function_name}` + } + ], + downStatement: [ + { sql: this.querySeparator() + `${args.create_function}` } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.procedure_name + * @param {String} - args.create_procedure + * @returns {Object} - up and down statements + */ + async procedureCreate(args: any = {}) { + const func = this.procedureCreate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + await this.sqlClient.raw(`${args.create_procedure}`); + result.data.object = { + upStatement: [ + { sql: this.querySeparator() + `${args.create_procedure}` } + ], + downStatement: [ + { + sql: this.querySeparator() + `DROP PROCEDURE ${args.procedure_name}` + } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.procedure_name + * @param {String} - args.create_procedure + * @param {String} - args.oldCreateProcedure + * @returns {Object} - up and down statements + */ + async procedureUpdate(args: any = {}) { + const func = this.procedureUpdate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + await this.sqlClient.raw( + `DROP PROCEDURE IF EXISTS ${args.procedure_name}` + ); + await this.sqlClient.raw(`${args.create_procedure}`); + result.data.object = { + upStatement: [ + { + sql: + this.querySeparator() + + `DROP PROCEDURE IF EXISTS ${ + args.procedure_name + }; ${this.querySeparator()} \n${args.create_procedure}` + } + ], + downStatement: [ + { + sql: + this.querySeparator() + + `DROP PROCEDURE IF EXISTS ${ + args.procedure_name + }; ${this.querySeparator()} ${args.oldCreateProcedure}` + } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.procedure_name + * @param {String} - args.create_procedure + * @returns {Object} - up and down statements + */ + async procedureDelete(args: any = {}) { + const func = this.procedureDelete.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + await this.sqlClient.raw( + `DROP PROCEDURE IF EXISTS ${args.procedure_name}` + ); + result.data.object = { + upStatement: [ + { + sql: + this.querySeparator() + + `DROP PROCEDURE IF EXISTS ${args.procedure_name}` + } + ], + downStatement: [ + { sql: this.querySeparator() + `${args.create_procedure}` } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} args + * @param {String} func : function name + * @returns {Result} + * @returns {Object} - Result.data + * @returns {String} - Result.data.value - sql query + */ + async _getQuery(args) { + try { + if (lodash.isEmpty(this._version)) { + const result = await this.version(); + this._version = result.data.object; + log.debug( + `Version was empty for ${args.func}: population version for database as`, + this._version + ); + } + + // log.debug(this._version, args); + if (this._version.key in this.queries[args.func]) { + return this.queries[args.func][this._version.key].sql; + } + return this.queries[args.func].default.sql; + } catch (error) { + log.ppe(error, this._getQuery.name); + throw error; + } + } + + mapFieldWithSuggestedFakerFn(cols) { + const newCols = cols.map(col => { + let suggestion = null; + let l_score = Infinity; + const nativeType = findDataType.mapDataType(col.dt); + fakerFunctionList.forEach((fakerFn, i) => { + if (nativeType !== 'string' && nativeType !== fakerFn.type) return; + + if (i) { + const ls = levenshtein.get( + col.cn.toLowerCase(), + fakerFn.name.toLowerCase() + ); + if (l_score > ls) { + l_score = ls; + suggestion = fakerFn; + } + } else { + suggestion = fakerFn; + l_score = levenshtein.get( + col.cn.toLowerCase(), + fakerFn.name.toLowerCase() + ); + } + }); + + if (l_score < 3) { + return { ...col, fakerFunction: suggestion.value }; + } + return col; + }); + + return newCols; + } + + /** + * + * @param args + * @param args.seedsFolder + * @returns {Promise} + * @returns {result.data} - __xseeds.json file content + */ + async seedInit(args) { + const _func = this.seedInit.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + console.log('in mysql SeedInit'); + + try { + mkdirp.sync(args.seedsFolder); + + const seedSettings = path.join(args.seedsFolder, '__xseeds.json'); + await promisify(jsonfile.writeFile)( + seedSettings, + { + rows: { value: 8, description: 'Maximum number of rows' }, + foreign_key_rows: { + value: 2, + description: '1:n - Total number foreign key per relation' + } + }, + { spaces: 2 } + ); + + let tables: any = await this.tableList(); + + tables = tables.data.list; + + for (const table of tables) { + let columns: any = await this.columnList({ tn: table.tn }); + columns = columns.data.list; + + for (let i = 0; i < columns.length; ++i) { + columns[i]['fakerFunction'] = null; + } + + columns = this.mapFieldWithSuggestedFakerFn(columns); + + let relations: any = await this.relationList({ tn: table.tn }); + relations = relations.data.list; + + for (let i = 0; i < relations.length; i++) { + const relation = relations[i]; + for (let i = 0; i < columns.length; i++) { + const column = columns[i]; + if (column.cn === relation.cn) { + columns[i] = { ...column, ...relation }; + } + } + } + + // let fakerColumnPath = path.join(args.seedsFolder, `${table.tn}.json`); + // + // await promisify(jsonfile.writeFile)(fakerColumnPath, + // columns, + // {spaces: 2}); + + await this.fakerColumnsCreate({ + seedsFolder: args.seedsFolder, + tn: table.tn, + fakerColumns: columns + }); + } + + result.data = await promisify(jsonfile.readFile)(seedSettings); + } catch (e) { + log.ppe(e, _func); + throw e; + } + return result; + } + + /** + * + * @param {Object} - args + * @param {String} - args.tn + * @param {Object[]} - args.columns + * @param {String} - args.columns[].tn + * @param {String} - args.columns[].cn + * @param {String} - args.columns[].dt + * @param {String} - args.columns[].np + * @param {String} - args.columns[].ns - + * @param {String} - args.columns[].clen - + * @param {String} - args.columns[].dp - + * @param {String} - args.columns[].cop - + * @param {String} - args.columns[].pk - + * @param {String} - args.columns[].nrqd - + * @param {String} - args.columns[].not_nullable - + * @param {String} - args.columns[].ct - + * @param {String} - args.columns[].un - + * @param {String} - args.columns[].ai - + * @param {String} - args.columns[].unique - + * @param {String} - args.columns[].cdf - + * @param {String} - args.columns[].cc - + * @param {String} - args.columns[].csn - + * @param {String} - args.columns[].dtx + * - value will be 'specificType' for all cols except ai + * - for ai it will be integer, bigInteger + * - tiny, small and medium Int auto increement is not supported + * @param {String} - args.columns[].dtxp - to use in UI + * @param {String} - args.columns[].dtxs - to use in UI + * @returns {Promise<{upStatement, downStatement}>} + */ + async tableCreate(args) { + const _func = this.tableCreate.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + args.table = args.tn; + args.sqlClient = this.sqlClient; + + /**************** create table ****************/ + const upQuery = this.querySeparator() + this.createTable(args); + await this.sqlClient.raw(upQuery); + + const downStatement = + this.querySeparator() + + this.sqlClient.schema.dropTable(args.table).toString(); + + this.emit(`Success : ${upQuery}`); + + /**************** return files *************** */ + result.data.object = { + upStatement: [{ sql: upQuery }], + downStatement: [{ sql: downStatement }] + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param {Object} - args + * @param {String} - args.table + * @param {String} - args.table + * @param {Object[]} - args.columns + * @param {String} - args.columns[].tn + * @param {String} - args.columns[].cn + * @param {String} - args.columns[].dt + * @param {String} - args.columns[].np + * @param {String} - args.columns[].ns - + * @param {String} - args.columns[].clen - + * @param {String} - args.columns[].dp - + * @param {String} - args.columns[].cop - + * @param {String} - args.columns[].pk - + * @param {String} - args.columns[].nrqd - + * @param {String} - args.columns[].not_nullable - + * @param {String} - args.columns[].ct - + * @param {String} - args.columns[].un - + * @param {String} - args.columns[].ai - + * @param {String} - args.columns[].unique - + * @param {String} - args.columns[].cdf - + * @param {String} - args.columns[].cc - + * @param {String} - args.columns[].csn - + * @param {Number} - args.columns[].altered - 1,2,4 = addition,edited,deleted + * @returns {Promise<{upStatement, downStatement}>} + */ + async tableUpdate(args) { + const _func = this.tableUpdate.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + args.table = args.tn; + const originalColumns = args.originalColumns; + args.connectionConfig = this._connectionConfig; + args.sqlClient = this.sqlClient; + + let upQuery = ''; + let downQuery = ''; + + for (let i = 0; i < args.columns.length; ++i) { + const oldColumn = lodash.find(originalColumns, { + cn: args.columns[i].cno + }); + + if (args.columns[i].altered & 4) { + // col remove + upQuery += this.alterTableRemoveColumn( + args.columns[i], + oldColumn, + upQuery + ); + downQuery += this.alterTableAddColumn( + oldColumn, + args.columns[i], + downQuery + ); + } else if (args.columns[i].altered & 2 || args.columns[i].altered & 8) { + // col edit + upQuery += this.alterTableChangeColumn( + args.columns[i], + oldColumn, + upQuery + ); + downQuery += this.alterTableChangeColumn( + oldColumn, + args.columns[i], + downQuery + ); + } else if (args.columns[i].altered & 1) { + // col addition + upQuery += this.alterTableAddColumn( + args.columns[i], + oldColumn, + upQuery + ); + downQuery += this.alterTableRemoveColumn( + args.columns[i], + oldColumn, + downQuery + ); + } + } + + upQuery += this.alterTablePK(args.columns, args.originalColumns, upQuery); + downQuery += this.alterTablePK( + args.originalColumns, + args.columns, + downQuery + ); + + if (upQuery) { + upQuery = this.genQuery(`ALTER TABLE ?? ${this.sanitize(upQuery)};`, [ + args.tn + ]); + downQuery = this.genQuery( + `ALTER TABLE ?? ${this.sanitize(downQuery)};`, + [args.tn] + ); + } + + await this.sqlClient.raw(upQuery); + + console.log(upQuery); + + result.data.object = { + upStatement: [{ sql: this.querySeparator() + upQuery }], + downStatement: [{ sql: this.querySeparator() + downQuery }] + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param {Object} - args + * @param args.table_name + * @param args.ignore + * @returns {Promise<{upStatement, downStatement}>} + */ + async tableDelete(args) { + const _func = this.tableDelete.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.sqlClient = this.sqlClient; + + /** ************** create up & down statements *************** */ + const upStatement = + this.querySeparator() + + this.sqlClient.schema.dropTable(args.table_name).toString(); + + let createStatement = await this.sqlClient.raw( + `show create table \`${args.table_name}\`` + ); + createStatement = Object.entries(createStatement[0][0]).find( + ([k]) => k.toLowerCase() === 'create table' + )[1]; + + const downQuery = this.querySeparator() + createStatement; //createTable(args); + + this.emit(`Success : ${upStatement}`); + + /** ************** drop table_name *************** */ + await this.sqlClient.schema.dropTable(args.table_name); + + /** ************** return files *************** */ + result.data.object = { + upStatement: [{ sql: upStatement }], + downStatement: [{ sql: downQuery }] + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableCreateStatement(args) { + const _func = this.tableCreateStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + result.data = ';'; + const response = await this.sqlClient.raw( + `show create table \`${args.tn}\`;` + ); + if (response.length === 2) { + result.data = response[0][0]['Create Table']; + } + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableInsertStatement(args) { + const _func = this.tableCreateStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + let values = ' VALUES ('; + result.data = `INSERT INTO \`${args.tn}\` (`; + const response = await this.columnList(args); + if (response.data && response.data.list) { + for (let i = 0; i < response.data.list.length; ++i) { + if (!i) { + result.data += `\n\`${response.data.list[i].cn}\`\n\t`; + values += `\n<${response.data.list[i].cn}>\n\t`; + } else { + result.data += `, \`${response.data.list[i].cn}\`\n\t`; + values += `, <${response.data.list[i].cn}>\n\t`; + } + } + } + + result.data += `)`; + values += `);`; + result.data += values; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableUpdateStatement(args) { + const _func = this.tableUpdateStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + result.data = `UPDATE \`${args.tn}\` \nSET\n`; + const response = await this.columnList(args); + if (response.data && response.data.list) { + for (let i = 0; i < response.data.list.length; ++i) { + if (!i) { + result.data += `\`${response.data.list[i].cn}\` = <\`${response.data.list[i].cn}\`>\n\t`; + } else { + result.data += `,\`${response.data.list[i].cn}\` = <\`${response.data.list[i].cn}\`>\n\t`; + } + } + } + + result.data += ';'; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableDeleteStatement(args) { + const _func = this.tableDeleteStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + result.data = `DELETE FROM ${args.tn};`; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableTruncateStatement(args) { + const _func = this.tableTruncateStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + result.data = `TRUNCATE TABLE ${args.tn};`; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableSelectStatement(args) { + const _func = this.tableSelectStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + result.data = `SELECT `; + const response = await this.columnList(args); + if (response.data && response.data.list) { + for (let i = 0; i < response.data.list.length; ++i) { + if (!i) { + result.data += `${response.data.list[i].cn}\n\t`; + } else { + result.data += `, ${response.data.list[i].cn}\n\t`; + } + } + } + + result.data += ` FROM ${args.tn};`; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @returns {Object[]} - sequences + * @property {String} - sequences[].sequence_name + * @property {String} - sequences[].type + * @property {String} - sequences[].definer + * @property {String} - sequences[].modified + * @property {String} - sequences[].created + * @property {String} - sequences[].security_type + * @property {String} - sequences[].comment + * @property {String} - sequences[].character_set_client + * @property {String} - sequences[].collation_connection + * @property {String} - sequences[].database collation + */ + async sequenceList(args: any = {}) { + const _func = this.sequenceList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + result.data.list = []; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + alterTableRemoveColumn(n, _o, existingQuery) { + let query = existingQuery ? ',' : ''; + query += this.genQuery(` DROP COLUMN ??`, [n.cn]); + return query; + } + + createTableColumn(n, o, existingQuery) { + return this.alterTableColumn(n, o, existingQuery, 0); + } + + alterTableAddColumn(n, o, existingQuery) { + return this.alterTableColumn(n, o, existingQuery, 1); + } + + alterTableChangeColumn(n, o, existingQuery) { + return this.alterTableColumn(n, o, existingQuery, 2); + } + + createTable(args) { + let query = ''; + + for (let i = 0; i < args.columns.length; ++i) { + query += this.createTableColumn(args.columns[i], null, query); + } + + query += this.alterTablePK(args.columns, [], query, true); + query = this.genQuery(`CREATE TABLE ?? (${this.sanitize(query)});`, [ + args.tn + ]); + + return query; + } + + alterTableColumn(n, o, existingQuery, change = 2) { + let query = existingQuery ? ',' : ''; + + const scale = parseInt(n.dtxs) ? parseInt(n.dtxs) : null; + if (change === 2) { + query += this.genQuery( + ` + CHANGE + COLUMN ?? ?? ${n.dt}`, + [o.cn, n.cn] + ); + } else if (change === 1) { + query += this.genQuery( + ` + ADD + COLUMN ?? ${n.dt}`, + [n.cn] + ); + } else { + query += this.genQuery(` ?? ${n.dt}`, [n.cn]); + } + if (!n.dt.endsWith('text')) { + query += n.dtxp && n.dtxp !== ' ' ? `(${n.dtxp}` : ''; + query += scale ? `,${scale}` : ''; + query += n.dtxp && n.dtxp !== ' ' ? ')' : ''; + } + query += n.un ? ' UNSIGNED' : ''; + query += n.rqd ? ' NOT NULL' : ' NULL'; + query += n.ai ? ' auto_increment' : ''; + const defaultValue = getDefaultValue(n); + query += defaultValue + ? ` + DEFAULT ${defaultValue}` + : ''; + + return query; + } + + alterTablePK(n, o, _existingQuery, createTable = false) { + const numOfPksInOriginal = []; + const numOfPksInNew = []; + let pksChanged = 0; + + for (let i = 0; i < n.length; ++i) { + if (n[i].pk) { + if (n[i].altered !== 4) numOfPksInNew.push(n[i].cn); + } + } + + for (let i = 0; i < o.length; ++i) { + if (o[i].pk) { + numOfPksInOriginal.push(o[i].cn); + } + } + + if (numOfPksInNew.length === numOfPksInOriginal.length) { + for (let i = 0; i < numOfPksInNew.length; ++i) { + if (numOfPksInOriginal[i] !== numOfPksInNew[i]) { + pksChanged = 1; + break; + } + } + } else { + pksChanged = numOfPksInNew.length - numOfPksInOriginal.length; + } + + let query = ''; + if (!numOfPksInNew.length && !numOfPksInOriginal.length) { + // do nothing + } else if (pksChanged) { + query += numOfPksInOriginal.length ? ',DROP PRIMARY KEY' : ''; + + if (numOfPksInNew.length) { + if (createTable) { + query += this.genQuery( + `, PRIMARY + KEY(??)`, + [numOfPksInNew] + ); + } else { + query += this.genQuery( + `, ADD + PRIMARY + KEY(??)`, + [numOfPksInNew] + ); + } + } + } + + return query; + } + + /** + * + * @param {Object} args + * @returns {Object} result + * @returns {Number} code + * @returns {String} message + */ + async totalRecords(args: any = {}) { + const func = this.totalRecords.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + const data = await this.sqlClient.raw( + `SELECT SUM(table_rows) as TotalRecords FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '${this.connectionConfig.connection.database}';` + ); + result.data = data[0][0]; + } catch (e) { + result.code = -1; + result.message = e.message; + result.object = e; + } finally { + log.api(`${func} :result: ${result}`); + } + return result; + } +} + +function getDefaultValue(n) { + if (n.cdf === undefined || n.cdf === null) return n.cdf; + switch (n.dt) { + case 'boolean': + case 'bool': + case 'tinyint': + case 'int': + case 'samllint': + case 'bigint': + case 'integer': + case 'smallint': + case 'mediumint': + case 'int2': + case 'int4': + case 'int8': + case 'long': + case 'serial': + case 'bigserial': + case 'smallserial': + case 'number': + case 'float': + case 'double': + case 'decimal': + case 'numeric': + case 'real': + case 'double precision': + case 'money': + case 'smallmoney': + case 'dec': + return n.cdf; + break; + + case 'datetime': + case 'timestamp': + case 'date': + case 'time': + if ( + n.cdf.indexOf('CURRENT_TIMESTAMP') > -1 || + /\(([\d\w'", ]*)\)$/.test(n.cdf) + ) { + return n.cdf; + } + return JSON.stringify(n.cdf); + break; + default: + return JSON.stringify(n.cdf); + break; + } +} + +export default MysqlClient; diff --git a/packages/nocodb/src/lib/db/sql-client/lib/mysql/TidbClient.ts b/packages/nocodb/src/lib/db/sql-client/lib/mysql/TidbClient.ts new file mode 100644 index 0000000000..f13600f667 --- /dev/null +++ b/packages/nocodb/src/lib/db/sql-client/lib/mysql/TidbClient.ts @@ -0,0 +1,61 @@ +import _ from 'lodash'; +import Debug from '../../../util/Debug'; +import Result from '../../../util/Result'; + +const log = new Debug('TidbClient'); + +import MysqlClient from './MysqlClient'; + +class Tidb extends MysqlClient { + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - indexes + * @property {String} - indexes[].cstn - + * @property {String} - indexes[].cn - + * @property {String} - indexes[].op - + * @property {String} - indexes[].puc - + * @property {String} - indexes[].cst - + */ + async constraintList(args: any = {}) { + const func = this.constraintList.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + const response = await this.sqlClient.raw( + `select *, TABLE_NAME as tn from INFORMATION_SCHEMA.KEY_COLUMN_USAGE where CONSTRAINT_SCHEMA='${this.connectionConfig.connection.database}' and TABLE_NAME='${args.tn}'` + ); + + if (response.length === 2) { + const indexes = []; + + for (let i = 0; i < response[0].length; ++i) { + let index = response[0][i]; + index = _.mapKeys(index, function(_v, k) { + return k.toLowerCase(); + }); + indexes.push(index); + } + + result.data.list = indexes; + } else { + log.debug( + 'Unknown response for databaseList:', + result.data.list.length + ); + result.data.list = []; + } + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + + return result; + } +} + +export default Tidb; diff --git a/packages/nocodb/src/lib/db/sql-client/lib/mysql/VitessClient.ts b/packages/nocodb/src/lib/db/sql-client/lib/mysql/VitessClient.ts new file mode 100644 index 0000000000..4e80e7794f --- /dev/null +++ b/packages/nocodb/src/lib/db/sql-client/lib/mysql/VitessClient.ts @@ -0,0 +1,247 @@ +import _ from 'lodash'; +import Debug from '../../../util/Debug'; +import Result from '../../../util/Result'; + +const log = new Debug('VitessClient'); + +import MysqlClient from './MysqlClient'; + +class Vitess extends MysqlClient { + constructor(connectionConfig: any) { + super(connectionConfig); + } + + async relationList(_args: any = {}) { + const result = new Result(); + result.data.list = []; + return result; + } + + async relationListAll(_args: any = {}) { + const result = new Result(); + result.data.list = []; + return result; + } + + /** + * + * @param {Object} - args - for future reasons + * @returns {Object[]} - databases + * @property {String} - databases[].database_name + */ + async databaseList(args: any = {}) { + const func = this.databaseList.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + const response = await this.sqlClient.raw('SHOW databases'); + + log.debug(response.length); + + if (response.length === 2) { + for (let i = 0; i < response[0].length; ++i) { + response[0][i].database_name = response[0][i].Databases; + } + result.data.list = response[0]; + } else { + log.debug( + 'Unknown response for databaseList:', + result.data.list.length + ); + result.data.list = []; + } + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result.data.list.length); + + return result; + } + + /** + * + * @param {Object} - args - for future reasons + * @returns {Object[]} - tables + * @property {String} - tables[].tn + */ + async tableList(args: any = {}) { + const func = this.tableList.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + const response = await this.sqlClient.raw('SHOW TABLES'); + const keyInResponse = `Tables_in_vt_${this.connectionConfig.connection.database}`; + + if (response.length === 2) { + for (let i = 0; i < response[0].length; ++i) { + response[0][i].tn = response[0][i][keyInResponse]; + } + result.data.list = response[0]; + } else { + log.debug( + 'Unknown response for databaseList:', + result.data.list.length + ); + result.data.list = []; + } + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result.data.list.length); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - columns + * @property {String} - columns[].tn + * @property {String} - columns[].cn + * @property {String} - columns[].dt + * @property {String} - columns[].dtx + * @property {String} - columns[].np + * @property {String} - columns[].ns - + * @property {String} - columns[].clen - + * @property {String} - columns[].dp - + * @property {String} - columns[].cop - + * @property {String} - columns[].pk - + * @property {String} - columns[].nrqd - + * @property {String} - columns[].not_nullable - + * @property {String} - columns[].ct - + * @property {String} - columns[].un - + * @property {String} - columns[].ai - + * @property {String} - columns[].unique - + * @property {String} - columns[].cdf - + * @property {String} - columns[].cc - + * @property {String} - columns[].csn - + */ + async columnList(args: any = {}) { + const func = this.columnList.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + `select *, table_name as tn from information_schema.columns where table_name = '${args.tn}' ORDER by ordinal_position` + ); + + if (response.length === 2) { + const columns = []; + + for (let i = 0; i < response[0].length; ++i) { + const column: any = {}; + + response[0][i] = _.mapKeys(response[0][i], (_v, k) => + k.toLowerCase() + ); + + column.tn = args.tn; + column.cn = response[0][i].cn; + column.dt = response[0][i].dt; + column.np = response[0][i].np; + column.ns = response[0][i].ns; + column.clen = response[0][i].clen; + // column.dp = response[0][i].dp; + column.cop = response[0][i].op; + column.dtx = this.getKnexDataType(column.dt); + column.pk = response[0][i].ck === 'PRI'; + + column.nrqd = response[0][i].nrqd !== 'NO'; + column.not_nullable = !column.nrqd; + + response[0][i].ct = response[0][i].ct || ''; + column.un = response[0][i].ct.indexOf('unsigned') !== -1; + + column.ct = response[0][i].ct || ''; + response[0][i].ext = response[0][i].ext || ''; + column.ai = response[0][i].ext.indexOf('auto_increment') !== -1; + + response[0][i].cst = response[0][i].cst || ' '; + column.unique = response[0][i].cst.indexOf('UNIQUE') !== -1; + + column.cdf = response[0][i].cdf; + column.cc = response[0][i].cc; + + column.csn = response[0][i].csn; + + columns.push(column); + + // column['dtx'] = response[0][i]['dt']; + } + + result.data.list = columns; + } else { + log.debug('Unknown response for databaseList:', response); + result.data.list = []; + } + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result list length = `, result.data.list.length); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - indexes + * @property {String} - indexes[].cstn - + * @property {String} - indexes[].cn - + * @property {String} - indexes[].op - + * @property {String} - indexes[].puc - + * @property {String} - indexes[].cst - + */ + async constraintList(args: any = {}) { + const func = this.constraintList.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + const response = await this.sqlClient.raw( + `select *, TABLE_NAME as tn from INFORMATION_SCHEMA.KEY_COLUMN_USAGE where TABLE_NAME = '${args.tn}' ORDER by ordinal_position;` + ); + + if (response.length === 2) { + const indexes = []; + + for (let i = 0; i < response[0].length; ++i) { + let index = response[0][i]; + index = _.mapKeys(index, function(_v, k) { + return k.toLowerCase(); + }); + indexes.push(index); + } + + result.data.list = indexes; + } else { + log.debug( + 'Unknown response for databaseList:', + result.data.list.length + ); + result.data.list = []; + } + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + + return result; + } +} +export default Vitess; diff --git a/packages/nocodb/src/lib/db/sql-client/lib/mysql/fakerFunctionList.ts b/packages/nocodb/src/lib/db/sql-client/lib/mysql/fakerFunctionList.ts new file mode 100644 index 0000000000..6a05030857 --- /dev/null +++ b/packages/nocodb/src/lib/db/sql-client/lib/mysql/fakerFunctionList.ts @@ -0,0 +1,898 @@ +export default [ + { + name: 'zipCode', + group: 'address', + value: 'address.zipCode', + type: 'string' + }, + { + name: 'city', + group: 'address', + value: 'address.city', + type: 'string' + }, + { + name: 'cityPrefix', + group: 'address', + value: 'address.cityPrefix', + type: 'string' + }, + { + name: 'citySuffix', + group: 'address', + value: 'address.citySuffix', + type: 'string' + }, + { + name: 'streetName', + group: 'address', + value: 'address.streetName', + type: 'string' + }, + { + name: 'streetAddress', + group: 'address', + value: 'address.streetAddress', + type: 'string' + }, + { + name: 'streetSuffix', + group: 'address', + value: 'address.streetSuffix', + type: 'string' + }, + { + name: 'streetPrefix', + group: 'address', + value: 'address.streetPrefix', + type: 'string' + }, + { + name: 'secondaryAddress', + group: 'address', + value: 'address.secondaryAddress', + type: 'string' + }, + { + name: 'county', + group: 'address', + value: 'address.county', + type: 'string' + }, + { + name: 'country', + group: 'address', + value: 'address.country', + type: 'string' + }, + { + name: 'countryCode', + group: 'address', + value: 'address.countryCode', + type: 'string' + }, + { + name: 'state', + group: 'address', + value: 'address.state', + type: 'string' + }, + { + name: 'stateAbbr', + group: 'address', + value: 'address.stateAbbr', + type: 'string' + }, + { + name: 'latitude', + group: 'address', + value: 'address.latitude', + type: 'number' + }, + { + name: 'longitude', + group: 'address', + value: 'address.longitude', + type: 'number' + }, + { + name: 'color', + group: 'commerce', + value: 'commerce.color', + type: 'string' + }, + { + name: 'department', + group: 'commerce', + value: 'commerce.department', + type: 'string' + }, + { + name: 'productName', + group: 'commerce', + value: 'commerce.productName', + type: 'string' + }, + { + name: 'price', + group: 'commerce', + value: 'commerce.price', + type: 'number' + }, + { + name: 'productAdjective', + group: 'commerce', + value: 'commerce.productAdjective', + type: 'string' + }, + { + name: 'productMaterial', + group: 'commerce', + value: 'commerce.productMaterial', + type: 'string' + }, + { + name: 'product', + group: 'commerce', + value: 'commerce.product', + type: 'string' + }, + { + name: 'suffixes', + group: 'company', + value: 'company.suffixes', + type: 'string' + }, + { + name: 'companyName', + group: 'company', + value: 'company.companyName', + type: 'string' + }, + { + name: 'companySuffix', + group: 'company', + value: 'company.companySuffix', + type: 'string' + }, + { + name: 'catchPhrase', + group: 'company', + value: 'company.catchPhrase', + type: 'string' + }, + { + name: 'bs', + group: 'company', + value: 'company.bs', + type: 'string' + }, + { + name: 'catchPhraseAdjective', + group: 'company', + value: 'company.catchPhraseAdjective', + type: 'string' + }, + { + name: 'catchPhraseDescriptor', + group: 'company', + value: 'company.catchPhraseDescriptor', + type: 'string' + }, + { + name: 'catchPhraseNoun', + group: 'company', + value: 'company.catchPhraseNoun', + type: 'string' + }, + { + name: 'bsAdjective', + group: 'company', + value: 'company.bsAdjective', + type: 'string' + }, + { + name: 'bsBuzz', + group: 'company', + value: 'company.bsBuzz', + type: 'string' + }, + { + name: 'bsNoun', + group: 'company', + value: 'company.bsNoun', + type: 'string' + }, + { + name: 'column', + group: 'database', + value: 'database.column', + type: 'string' + }, + { + name: 'type', + group: 'database', + value: 'database.type', + type: 'string' + }, + { + name: 'collation', + group: 'database', + value: 'database.collation', + type: 'string' + }, + { + name: 'engine', + group: 'database', + value: 'database.engine', + type: 'string' + }, + { + name: 'past', + group: 'date', + value: 'date.past', + type: 'date' + }, + { + name: 'future', + group: 'date', + value: 'date.future', + type: 'date' + }, + // { + // name: 'between', + // group: 'date', + // value: 'date.between', + // type: 'date' + // }, + { + name: 'recent', + group: 'date', + value: 'date.recent', + type: 'date' + }, + { + name: 'soon', + group: 'date', + value: 'date.soon', + type: 'date' + }, + { + name: 'month', + group: 'date', + value: 'date.month', + type: 'string' + }, + { + name: 'weekday', + group: 'date', + value: 'date.weekday', + type: 'string' + }, + { + name: 'account', + group: 'finance', + value: 'finance.account', + type: 'string' + }, + { + name: 'accountName', + group: 'finance', + value: 'finance.accountName', + type: 'string' + }, + { + name: 'mask', + group: 'finance', + value: 'finance.mask', + type: 'string' + }, + { + name: 'amount', + group: 'finance', + value: 'finance.amount', + type: 'number' + }, + { + name: 'transactionType', + group: 'finance', + value: 'finance.transactionType', + type: 'string' + }, + { + name: 'currencyCode', + group: 'finance', + value: 'finance.currencyCode', + type: 'string' + }, + { + name: 'currencyName', + group: 'finance', + value: 'finance.currencyName', + type: 'string' + }, + { + name: 'currencySymbol', + group: 'finance', + value: 'finance.currencySymbol', + type: 'string' + }, + { + name: 'bitcoinAddress', + group: 'finance', + value: 'finance.bitcoinAddress', + type: 'string' + }, + { + name: 'ethereumAddress', + group: 'finance', + value: 'finance.ethereumAddress', + type: 'string' + }, + { + name: 'iban', + group: 'finance', + value: 'finance.iban', + type: 'string' + }, + { + name: 'bic', + group: 'finance', + value: 'finance.bic', + type: 'string' + }, + { + name: 'abbreviation', + group: 'hacker', + value: 'hacker.abbreviation', + type: 'string' + }, + { + name: 'adjective', + group: 'hacker', + value: 'hacker.adjective', + type: 'string' + }, + { + name: 'noun', + group: 'hacker', + value: 'hacker.noun', + type: 'string' + }, + { + name: 'verb', + group: 'hacker', + value: 'hacker.verb', + type: 'string' + }, + { + name: 'ingverb', + group: 'hacker', + value: 'hacker.ingverb', + type: 'string' + }, + { + name: 'phrase', + group: 'hacker', + value: 'hacker.phrase', + type: 'string' + }, + // { + // name: 'randomize', + // group: 'helpers', + // value: 'helpers.randomize', + // type: 'string' + // }, + // { + // name: 'slugify', + // group: 'helpers', + // value: 'helpers.slugify', + // type: 'string' + // }, + // { + // name: 'replaceSymbolWithNumber', + // group: 'helpers', + // value: 'helpers.replaceSymbolWithNumber', + // type: 'string' + // }, + // { + // name: 'replaceSymbols', + // group: 'helpers', + // value: 'helpers.replaceSymbols', + // type: 'string' + // }, + // { + // name: 'shuffle', + // group: 'helpers', + // value: 'helpers.shuffle', + // type: 'string' + // }, + // { + // name: 'mustache', + // group: 'helpers', + // value: 'helpers.mustache', + // type: 'string' + // }, + { + name: 'createCard', + group: 'helpers', + value: 'helpers.createCard', + type: 'string' + }, + { + name: 'contextualCard', + group: 'helpers', + value: 'helpers.contextualCard', + type: 'string' + }, + { + name: 'userCard', + group: 'helpers', + value: 'helpers.userCard', + type: 'string' + }, + { + name: 'createTransaction', + group: 'helpers', + value: 'helpers.createTransaction', + type: 'string' + }, + { + name: 'image', + group: 'image', + value: 'image.image', + type: 'string' + }, + { + name: 'avatar', + group: 'image', + value: 'image.avatar', + type: 'string' + }, + { + name: 'imageUrl', + group: 'image', + value: 'image.imageUrl', + type: 'string' + }, + { + name: 'abstract', + group: 'image', + value: 'image.abstract', + type: 'string' + }, + { + name: 'animals', + group: 'image', + value: 'image.animals', + type: 'string' + }, + { + name: 'business', + group: 'image', + value: 'image.business', + type: 'string' + }, + { + name: 'cats', + group: 'image', + value: 'image.cats', + type: 'string' + }, + { + name: 'city', + group: 'image', + value: 'image.city', + type: 'string' + }, + { + name: 'food', + group: 'image', + value: 'image.food', + type: 'string' + }, + { + name: 'nightlife', + group: 'image', + value: 'image.nightlife', + type: 'string' + }, + { + name: 'fashion', + group: 'image', + value: 'image.fashion', + type: 'string' + }, + { + name: 'people', + group: 'image', + value: 'image.people', + type: 'string' + }, + { + name: 'nature', + group: 'image', + value: 'image.nature', + type: 'string' + }, + { + name: 'sports', + group: 'image', + value: 'image.sports', + type: 'string' + }, + { + name: 'technics', + group: 'image', + value: 'image.technics', + type: 'string' + }, + { + name: 'transport', + group: 'image', + value: 'image.transport', + type: 'string' + }, + { + name: 'dataUri', + group: 'image', + value: 'image.dataUri', + type: 'string' + }, + { + name: 'avatar', + group: 'internet', + value: 'internet.avatar', + type: 'string' + }, + { + name: 'email', + group: 'internet', + value: 'internet.email', + type: 'string' + }, + { + name: 'exampleEmail', + group: 'internet', + value: 'internet.exampleEmail', + type: 'string' + }, + { + name: 'userName', + group: 'internet', + value: 'internet.userName', + type: 'string' + }, + { + name: 'protocol', + group: 'internet', + value: 'internet.protocol', + type: 'string' + }, + { + name: 'url', + group: 'internet', + value: 'internet.url', + type: 'string' + }, + { + name: 'domainName', + group: 'internet', + value: 'internet.domainName', + type: 'string' + }, + { + name: 'domainSuffix', + group: 'internet', + value: 'internet.domainSuffix', + type: 'string' + }, + { + name: 'domainWord', + group: 'internet', + value: 'internet.domainWord', + type: 'string' + }, + { + name: 'ip', + group: 'internet', + value: 'internet.ip', + type: 'string' + }, + { + name: 'ipv6', + group: 'internet', + value: 'internet.ipv6', + type: 'string' + }, + { + name: 'userAgent', + group: 'internet', + value: 'internet.userAgent', + type: 'string' + }, + { + name: 'color', + group: 'internet', + value: 'internet.color', + type: 'string' + }, + { + name: 'mac', + group: 'internet', + value: 'internet.mac', + type: 'string' + }, + { + name: 'password', + group: 'internet', + value: 'internet.password', + type: 'string' + }, + { + name: 'word', + group: 'lorem', + value: 'lorem.word', + type: 'string' + }, + { + name: 'words', + group: 'lorem', + value: 'lorem.words', + type: 'string' + }, + { + name: 'sentence', + group: 'lorem', + value: 'lorem.sentence', + type: 'string' + }, + { + name: 'slug', + group: 'lorem', + value: 'lorem.slug', + type: 'string' + }, + { + name: 'sentences', + group: 'lorem', + value: 'lorem.sentences', + type: 'string' + }, + { + name: 'paragraph', + group: 'lorem', + value: 'lorem.paragraph', + type: 'string' + }, + { + name: 'paragraphs', + group: 'lorem', + value: 'lorem.paragraphs', + type: 'string' + }, + { + name: 'text', + group: 'lorem', + value: 'lorem.text', + type: 'string' + }, + { + name: 'lines', + group: 'lorem', + value: 'lorem.lines', + type: 'string' + }, + { + name: 'firstName', + group: 'name', + value: 'name.firstName', + type: 'string' + }, + { + name: 'lastName', + group: 'name', + value: 'name.lastName', + type: 'string' + }, + { + name: 'findName', + group: 'name', + value: 'name.findName', + type: 'string' + }, + { + name: 'jobTitle', + group: 'name', + value: 'name.jobTitle', + type: 'string' + }, + { + name: 'prefix', + group: 'name', + value: 'name.prefix', + type: 'string' + }, + { + name: 'suffix', + group: 'name', + value: 'name.suffix', + type: 'string' + }, + { + name: 'title', + group: 'name', + value: 'name.title', + type: 'string' + }, + { + name: 'jobDescriptor', + group: 'name', + value: 'name.jobDescriptor', + type: 'string' + }, + { + name: 'jobArea', + group: 'name', + value: 'name.jobArea', + type: 'string' + }, + { + name: 'jobType', + group: 'name', + value: 'name.jobType', + type: 'string' + }, + { + name: 'phoneNumber', + group: 'phone', + value: 'phone.phoneNumber', + type: 'string' + }, + { + name: 'phoneNumberFormat', + group: 'phone', + value: 'phone.phoneNumberFormat', + type: 'string' + }, + { + name: 'phoneFormats', + group: 'phone', + value: 'phone.phoneFormats', + type: 'string' + }, + { + name: 'number', + group: 'random', + value: 'random.number', + type: 'number' + }, + { + name: 'float', + group: 'random', + value: 'random.float', + type: 'number' + }, + // Todo : use it for collection or enum + // { + // name: 'arrayElement', + // group: 'random', + // value: 'random.arrayElement', + // type: 'collection' + // }, + // { + // name: 'objectElement', + // group: 'random', + // value: 'random.objectElement', + // type: 'string' + // }, + { + name: 'uuid', + group: 'random', + value: 'random.uuid', + type: 'string' + }, + { + name: 'boolean', + group: 'random', + value: 'random.boolean', + type: 'boolean' + }, + { + name: 'word', + group: 'random', + value: 'random.word', + type: 'string' + }, + { + name: 'words', + group: 'random', + value: 'random.words', + type: 'string' + }, + { + name: 'image', + group: 'random', + value: 'random.image', + type: 'string' + }, + { + name: 'locale', + group: 'random', + value: 'random.locale', + type: 'string' + }, + { + name: 'alphaNumeric', + group: 'random', + value: 'random.alphaNumeric', + type: 'string' + }, + { + name: 'hexaDecimal', + group: 'random', + value: 'random.hexaDecimal', + type: 'string' + }, + { + name: 'fileName', + group: 'system', + value: 'system.fileName', + type: 'string' + }, + { + name: 'commonFileName', + group: 'system', + value: 'system.commonFileName', + type: 'string' + }, + { + name: 'mimeType', + group: 'system', + value: 'system.mimeType', + type: 'string' + }, + { + name: 'commonFileType', + group: 'system', + value: 'system.commonFileType', + type: 'string' + }, + { + name: 'commonFileExt', + group: 'system', + value: 'system.commonFileExt', + type: 'string' + }, + { + name: 'fileType', + group: 'system', + value: 'system.fileType', + type: 'string' + }, + { + name: 'fileExt', + group: 'system', + value: 'system.fileExt', + type: 'string' + }, + { + name: 'directoryPath', + group: 'system', + value: 'system.directoryPath', + type: 'string' + }, + { + name: 'filePath', + group: 'system', + value: 'system.filePath', + type: 'string' + }, + { + name: 'semver', + group: 'system', + value: 'system.semver', + type: 'string' + } +]; +// export default functionList; diff --git a/packages/nocodb/src/lib/db/sql-client/lib/mysql/findDataTypeMapping.ts b/packages/nocodb/src/lib/db/sql-client/lib/mysql/findDataTypeMapping.ts new file mode 100644 index 0000000000..3a07f378a9 --- /dev/null +++ b/packages/nocodb/src/lib/db/sql-client/lib/mysql/findDataTypeMapping.ts @@ -0,0 +1,65 @@ +export const mapDataType = function(type) { + switch (type) { + case 'int': + case 'tinyint': + case 'smallint': + case 'mediumint': + case 'bigint': + case 'bit': + case 'boolean': + case 'float': + case 'decimal': + case 'double': + case 'serial': + return 'number'; + case 'date': + case 'datetime': + case 'timestamp': + case 'time': + case 'year': + return 'date'; + case 'char': + case 'varchar': + case 'nchar': + case 'text': + case 'tinytext': + case 'mediumtext': + case 'longtext': + return 'string'; + case 'binary': + break; + case 'varbinary': + break; + case 'blob': + break; + case 'tinyblob': + break; + case 'mediumblob': + break; + case 'longblob': + break; + case 'enum': + case 'set': + return 'string'; + break; + case 'geometry': + break; + case 'point': + break; + case 'linestring': + break; + case 'polygon': + break; + case 'multipoint': + break; + case 'multilinestring': + break; + case 'multipolygon': + break; + case 'json': + return 'string'; + break; + } + + return 'string'; +}; diff --git a/packages/nocodb/src/lib/db/sql-client/lib/mysql/mysql.queries.ts b/packages/nocodb/src/lib/db/sql-client/lib/mysql/mysql.queries.ts new file mode 100644 index 0000000000..ca42badc71 --- /dev/null +++ b/packages/nocodb/src/lib/db/sql-client/lib/mysql/mysql.queries.ts @@ -0,0 +1,364 @@ +const columnListOld = `SELECT +c.table_name as tn, +c.column_name as cn, +c.data_type as dt, +c.column_type as ct, +c.character_maximum_length as clen, +c.numeric_precision as np, +c.numeric_scale as ns, +-- c.datetime_precision as dp, +c.ordinal_position as cop, +c.column_key as ck, +c.extra as ext, -- gives ai +c.column_default as cdf, +c.is_nullable as nrqd, +c.privileges as priv, +c.column_comment as cc, +-- c.generation_expression, +c.character_set_name as csn, +c.collation_name as clnn, +ct.CONSTRAINT_TYPE as cst +FROM +information_schema.COLUMNS as c +Left join( + select + tc.CONSTRAINT_NAME, + tc.TABLE_NAME, + tc.CONSTRAINT_SCHEMA, + tc.CONSTRAINT_TYPE, + s.COLUMN_NAME + from + information_schema.TABLE_CONSTRAINTS as tc + LEFT JOIN information_schema.STATISTICS as s ON + s.table_schema = tc.CONSTRAINT_SCHEMA + and s.TABLE_NAME = tc.TABLE_NAME + and s.INDEX_NAME = tc.CONSTRAINT_NAME + where + tc.CONSTRAINT_SCHEMA = ? + and s.TABLE_NAME = ? +) ct on +c.TABLE_SCHEMA = ct.CONSTRAINT_SCHEMA +AND c.TABLE_NAME = ct.TABLE_NAME +AND c.COLUMN_NAME = ct.COLUMN_NAME +WHERE +c.table_schema = ? +and c.TABLE_NAME = ? +ORDER BY +c.table_name, +c.ordinal_position`; + +export default { + columnList: { + '55': { + sql: columnListOld, + paramsHints: ['databaseName', 'tn', 'databaseName', 'tn'] + }, + '56': { + sql: columnListOld, + paramsHints: ['databaseName', 'tn', 'databaseName', 'tn'] + }, + default: { + sql: `SELECT + c.table_name as tn, + c.column_name as cn, + c.data_type as dt, + c.column_type as ct, + c.character_maximum_length as clen, + c.numeric_precision as np, + c.numeric_scale as ns, + c.datetime_precision as dp, + c.ordinal_position as cop, + c.column_key as ck, + c.extra as ext, -- gives ai + c.column_default as cdf, + c.is_nullable as nrqd, + c.privileges as priv, + c.column_comment as cc, +-- c.generation_expression, + c.character_set_name as csn, + c.collation_name as clnn, + ct.CONSTRAINT_TYPE as cst + FROM + information_schema.COLUMNS as c + Left join( + select + tc.CONSTRAINT_NAME, + tc.TABLE_NAME, + tc.CONSTRAINT_SCHEMA, + tc.CONSTRAINT_TYPE, + s.COLUMN_NAME + from + information_schema.TABLE_CONSTRAINTS as tc + LEFT JOIN information_schema.STATISTICS as s ON + s.table_schema = tc.CONSTRAINT_SCHEMA + and s.TABLE_NAME = tc.TABLE_NAME + and s.INDEX_NAME = tc.CONSTRAINT_NAME + and tc.CONSTRAINT_TYPE != 'UNIQUE' + and tc.CONSTRAINT_TYPE != 'FOREIGN KEY' + where + tc.CONSTRAINT_SCHEMA = ? + and s.TABLE_NAME = ? + ) ct on + c.TABLE_SCHEMA = ct.CONSTRAINT_SCHEMA + AND c.TABLE_NAME = ct.TABLE_NAME + AND c.COLUMN_NAME = ct.COLUMN_NAME + WHERE + c.table_schema = ? + and c.TABLE_NAME = ? + ORDER BY + c.table_name, + c.ordinal_position`, + paramsHints: ['databaseName', 'tn', 'databaseName', 'tn'] + } + }, + constraintList: { + default: { + sql: `SELECT + k.constraint_name as cstn, + k.column_name as cn, + k.ordinal_position as op, + k.position_in_unique_constraint as puc, + t.constraint_type as cst +FROM information_schema.table_constraints t +LEFT JOIN information_schema.key_column_usage k +USING(constraint_name,table_schema,table_name) +WHERE +t.table_schema=? +AND t.table_name=?;`, + paramsHints: ['database', 'tn'] + } + }, + createDatabaseIfNotExists: { + default: { + sql: `create database if not exists ??`, + paramsHints: ['database'] + } + }, + createTableIfNotExists: { + default: { + sql: ``, + paramsHints: [] + } + }, + + dropDatabase: { + default: { + sql: `drop database ??`, + paramsHints: ['database'] + } + }, + databaseList: { + default: { + sql: `SHOW databases`, + paramsHints: [] + } + }, + + hasDatabase: { + default: { + sql: `SHOW DATABASES LIKE ?`, + paramsHints: ['databaseName'] + } + }, + indexList: { + default: { + sql: `show index from ??`, + paramsHints: ['tn'] + } + }, + + functionList: { + default: { + sql: `show function status where db=?`, + paramsHints: ['databaseName'] + } + }, + functionRead: { + default: { + sql: `SHOW CREATE FUNCTION ??`, + paramsHints: ['function_name'] + } + }, + functionDelete: { + default: { + sql: `DROP FUNCTION IF EXISTS ??`, + paramsHints: ['function_name'] + } + }, + + procedureList: { + default: { + sql: `show procedure status where db=?`, + paramsHints: ['databaseName'] + } + }, + procedureRead: { + default: { + sql: `show create procedure ??`, + paramsHints: ['procedure_name'] + } + }, + procedureDelete: { + default: { + sql: `DROP PROCEDURE IF EXISTS ??`, + paramsHints: ['procedure_name'] + } + }, + + relationList: { + default: { + sql: `SELECT + kcu.CONSTRAINT_NAME as cstn, + kcu.TABLE_NAME as tn, + kcu.COLUMN_NAME as cn, + kcu.POSITION_IN_UNIQUE_CONSTRAINT as puc, + kcu.REFERENCED_TABLE_NAME as rtn, + kcu.REFERENCED_COLUMN_NAME as rcn, + rc.MATCH_OPTION as mo, + rc.UPDATE_RULE as ur, + rc.DELETE_RULE as dr, + kcu.table_schema as ts + FROM + information_schema.KEY_COLUMN_USAGE AS kcu + INNER JOIN information_schema.REFERENTIAL_CONSTRAINTS AS rc ON + kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME + Group by kcu.CONSTRAINT_NAME, + kcu.TABLE_NAME, + kcu.COLUMN_NAME, + kcu.POSITION_IN_UNIQUE_CONSTRAINT, + kcu.REFERENCED_TABLE_NAME, + kcu.REFERENCED_COLUMN_NAME, + rc.MATCH_OPTION, + rc.UPDATE_RULE, + rc.DELETE_RULE ,kcu.table_schema + Having + kcu.table_schema = ? + AND kcu.referenced_column_name IS NOT NULL + AND kcu.table_name=?`, + paramsHints: ['database', 'tn'] + } + }, + + relationListAll: { + default: { + sql: ` + SELECT + kcu.CONSTRAINT_NAME AS cstn, + kcu.TABLE_NAME AS tn, + kcu.COLUMN_NAME AS cn, + kcu.POSITION_IN_UNIQUE_CONSTRAINT AS puc, + kcu.REFERENCED_TABLE_NAME AS rtn, + kcu.REFERENCED_COLUMN_NAME AS rcn, + rc.MATCH_OPTION AS mo, + rc.UPDATE_RULE AS ur, + rc.DELETE_RULE AS dr, + kcu.table_schema AS ts + FROM + (SELECT + table_schema, + CONSTRAINT_NAME, + TABLE_NAME, + COLUMN_NAME, + POSITION_IN_UNIQUE_CONSTRAINT, + REFERENCED_TABLE_NAME, + REFERENCED_COLUMN_NAME + FROM + information_schema.KEY_COLUMN_USAGE + WHERE + table_schema = :databaseName) AS kcu + INNER JOIN + (SELECT + CONSTRAINT_SCHEMA, + MATCH_OPTION, + UPDATE_RULE, + DELETE_RULE, + CONSTRAINT_NAME + FROM + information_schema.REFERENTIAL_CONSTRAINTS + WHERE + CONSTRAINT_SCHEMA = :databaseName) AS rc ON kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME + AND kcu.table_schema = rc.CONSTRAINT_SCHEMA + INNER JOIN + (SELECT + table_schema, TABLE_NAME, COLUMN_NAME + FROM + INFORMATION_SCHEMA.COLUMNS + WHERE + table_schema = :databaseName AND TABLE_NAME IN (SELECT + TABLE_NAME + FROM + INFORMATION_SCHEMA.TABLES + WHERE + table_schema = :databaseName + AND LOWER(TABLE_TYPE) = 'base table')) AS col ON col.table_schema = kcu.table_schema + AND col.table_name = kcu.TABLE_NAME + + AND kcu.REFERENCED_COLUMN_NAME IS NOT NULL + GROUP BY cstn , tn , rcn , cn , puc , rtn ,cn, mo , ur , dr , ts`, + paramsHints: ['database'] + } + }, + + schemaCreate: { + default: { + sql: `create database ??`, + paramsHints: ['database_name'] + } + }, + schemaDelete: { + default: { + sql: `drop database ??`, + paramsHints: ['database_name'] + } + }, + triggerList: { + default: { + sql: `SHOW TRIGGERS like ?`, + paramsHints: ['tn'] + } + }, + tableList: { + default: { + sql: ``, + paramsHints: [] + } + }, + testConnection: { + default: { + sql: ``, + paramsHints: [] + } + }, + triggerRead: { + default: { + sql: `SHOW FULL TABLES IN ?? WHERE TABLE_TYPE LIKE 'VIEW';`, + paramsHints: ['databaseName'] + } + }, + triggerDelete: { + default: { + sql: `DROP TRIGGER ??`, + paramsHints: ['trigger_name'] + } + }, + version: { + default: { + sql: ``, + paramsHints: [] + } + }, + viewRead: { + default: { + sql: `select * FROM INFORMATION_SCHEMA.VIEWS WHERE + TABLE_SCHEMA = ? AND TABLE_NAME = ?`, + paramsHints: ['databaseName', 'view_name'] + } + }, + // + viewList: { + default: { + sql: `SHOW FULL TABLES WHERE TABLE_TYPE LIKE 'VIEW'`, + paramsHints: [] + } + } +}; diff --git a/packages/nocodb/src/lib/db/sql-client/lib/oracle/OracleClient.ts b/packages/nocodb/src/lib/db/sql-client/lib/oracle/OracleClient.ts new file mode 100644 index 0000000000..114284b7dc --- /dev/null +++ b/packages/nocodb/src/lib/db/sql-client/lib/oracle/OracleClient.ts @@ -0,0 +1,2220 @@ +import knex from 'knex'; +import _ from 'lodash'; +import KnexClient from '../KnexClient'; +import Debug from '../../../util/Debug'; +import Result from '../../../util/Result'; +import lodash from 'lodash'; + +const log = new Debug('OracleClient'); + +class OracleClient extends KnexClient { + constructor(connectionConfig) { + // connectionConfig.connection.user = connectionConfig.connection.user.toUpperCase(); + super(connectionConfig); + } + + getKnexDataTypes() { + const result = new Result(); + + // result.data.list = [ + // "integer", + // "bigInteger", + // "text", + // "string", + // "float", + // "decimal", + // "boolean", + // "date", + // // "datetime", + // "time", + // "timestamp", + // "binary", + // "enu", + // "json", + // "specificType" + // ]; + + result.data.list = [ + 'bfile', + 'binary rowid', + 'binary double', + 'binary_float', + 'blob', + 'canoical', + 'cfile', + 'char', + 'clob', + 'content pointer', + 'contigous array', + 'date', + 'decimal', + 'double precision', + 'float', + 'integer', + 'interval day to second', + 'interval year to month', + 'lob pointer', + 'long', + 'long raw', + 'named collection', + 'named object', + 'nchar', + 'nclob', + 'number', + 'nvarchar2', + 'octet', + 'oid', + 'pointer', + 'raw', + 'real', + 'ref', + 'ref cursor', + 'rowid', + 'signed binary integer', + 'smallint', + 'table', + 'time', + 'time with tz', + 'timestamp', + 'timestamp with local time zone', + 'timestamp with local tz', + 'timestamp with timezone', + 'timestamp with tz', + 'unsigned binary integer', + 'urowid', + 'varchar', + 'varchar2', + 'varray', + 'varying array' + ]; + + return result; + } + + /** + * @param {Object} args + * @returns {Object} result + * @returns {Number} code + * @returns {String} message + */ + async testConnection(args: any = {}) { + const _func = this.testConnection.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + await this.raw(`SELECT 1+1 AS "data" FROM DUAL`); + } catch (e) { + log.ppe(e); + result.code = -1; + result.message = e.message; + } finally { + log.api(`${_func}:result:`, result); + } + + return result; + } + + async version(args: any = {}) { + const _func = this.version.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const rows = await this.raw( + `SELECT * FROM PRODUCT_COMPONENT_VERSION WHERE product LIKE 'Oracle%'` + ); + result.data.object = {}; + + let versionDetails = rows[0]; + versionDetails = _.mapKeys(versionDetails, (_v, k) => k.toLowerCase()); + const version = versionDetails.version.split('.'); + result.data.object.version = versionDetails.version; + result.data.object.primary = version[0]; + result.data.object.major = version[1]; + result.data.object.minor = version[2]; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + /** + * + * @param {Object} args + * @param {String} args.database + * @returns {Result} + */ + async createDatabaseIfNotExists(args: any = {}) { + const _func = this.createDatabaseIfNotExists.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const connectionParamsWithoutDb = JSON.parse( + JSON.stringify(this.connectionConfig) + ); + connectionParamsWithoutDb.connection.database = 'xe'; + connectionParamsWithoutDb.connection.user = 'system'; + connectionParamsWithoutDb.connection.password = 'oracle'; + // log.debug(connectionParamsWithoutDb); + const tempSqlClient = knex(connectionParamsWithoutDb); + + log.debug('checking if db exists'); + const rows = await tempSqlClient.raw( + `select USERNAME from SYS.ALL_USERS WHERE USERNAME = '${this.connectionConfig.connection.user}'` + ); + + if (rows.length === 0) { + log.debug('creating database:', args); + await tempSqlClient.raw( + `CREATE USER ${this.connectionConfig.connection.user} IDENTIFIED BY ${this.connectionConfig.connection.user}` + ); + await tempSqlClient.raw( + `GRANT ALL PRIVILEGES TO ${this.connectionConfig.connection.user}` + ); + await tempSqlClient.raw( + `GRANT EXECUTE ON DBMS_AQ TO ${this.connectionConfig.connection.user}` + ); + await tempSqlClient.raw( + `GRANT EXECUTE ON DBMS_AQADM TO ${this.connectionConfig.connection.user}` + ); + } + + await tempSqlClient.destroy(); + // create new knex client + this.sqlClient = knex(this.connectionConfig); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + async dropDatabase(args) { + const _func = this.dropDatabase.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const connectionParamsWithoutDb = JSON.parse( + JSON.stringify(this.connectionConfig) + ); + connectionParamsWithoutDb.connection.database = 'xe'; + connectionParamsWithoutDb.connection.user = 'system'; + connectionParamsWithoutDb.connection.password = 'oracle'; + // log.debug(connectionParamsWithoutDb); + const tempSqlClient = knex(connectionParamsWithoutDb); + await this.sqlClient.destroy(); + log.debug('dropping database:', this.connectionConfig.connection.user); + // await tempSqlClient.raw(`ALTER SYSTEM enable restricted session`); + const sessions = await tempSqlClient.raw(`select SID,SERIAL# from v$session where username = '${this.connectionConfig.connection.user}' + `); + log.debug( + `Active Sessions for ${this.connectionConfig.connection.user}: `, + sessions + ); + for (let i = 0; i < sessions.length; i++) { + const session = sessions[i]; + await tempSqlClient.raw( + `alter system kill session '${session.SID},${session['SERIAL#']}' immediate` + ); + } + + // await tempSqlClient.raw(`ALTER SYSTEM disable restricted session`); + await tempSqlClient.raw( + `drop user ${this.connectionConfig.connection.user} cascade` + ); + log.debug('dropped database:', this.connectionConfig.connection.user); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + /** + * + * @param args {tn} + * @returns + */ + async createTableIfNotExists(args) { + const _func = this.createTableIfNotExists.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + /** ************** START : create _evolution table if not exists *************** */ + const exists = await this.hasTable({ tn: args.tn }); + + if (!exists.data.value) { + await this.sqlClient.schema.createTable(args.tn, function(table) { + // table.increments(); + table.string('title').notNullable(); + table.string('titleDown').nullable(); + table.string('description').nullable(); + table.integer('batch').nullable(); + table.string('checksum').nullable(); + table.integer('status').nullable(); + table.dateTime('created'); + table.timestamps(); + }); + log.debug('Table created:', `${args.tn}`); + } else { + log.debug(`${args.tn} tables exists`); + } + /** ************** END : create _evolution table if not exists *************** */ + } catch (e) { + log.ppe(e, _func); + if ( + // INFO: knex issue + JSON.stringify(e.message).indexOf( + 'exact fetch returns more than requested number of rows' + ) > -1 + ) { + log.warn( + `safely ignored Exception:exact fetch returns more than requested number of rows knex create table issue` + ); + } else { + throw e; + } + } + + log.api(`${_func}: result`, result); + + return result; + } + + // async startTransaction() { + // let err = await this.raw("SET autocommit = 0"); + // log.debug("SET autocommit = 0:", err); + // err = await this.raw("start transaction"); + // log.debug("start transaction:", err); + // } + + // async commit() { + // const err = await this.raw("commit"); + // log.debug("commit:", err); + // await this.raw("SET autocommit = 1"); + // log.debug("SET autocommit = 1:", err); + // } + + // async rollback() { + // const err = await this.raw("rollback"); + // log.debug("rollback:", err); + // await this.raw("SET autocommit = 1"); + // log.debug("SET autocommit = 1:", err); + // } + + async hasTable(args: any = {}) { + const _func = this.hasTable.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const rows = await this.raw( + `select TABLE_NAME as tn FROM all_tables WHERE OWNER = '${this.connectionConfig.connection.user}' AND tn = '${args.tn}'` + ); + result.data.value = rows.length > 0; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async hasDatabase(args: any = {}) { + const _func = this.hasDatabase.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const rows = await this.raw( + `select USERNAME from SYS.ALL_USERS WHERE USERNAME = '${args.databaseName}'` + ); + result.data.value = rows.length > 0; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - for future reasons + * @returns {Object[]} - databases + * @property {String} - databases[].database_name + */ + async databaseList(args: any = {}) { + const _func = this.databaseList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const rows = await this.raw( + `select USERNAME as "database_name" from SYS.ALL_USERS order by USERNAME` + ); + result.data.list = rows; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - for future reasons + * @returns {Object[]} - tables + * @property {String} - tables[].tn + */ + async tableList(args: any = {}) { + const _func = this.tableList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + args.databaseName = this.connectionConfig.connection.user; + + const rows = await this.raw( + `select table_name FROM all_tables WHERE owner='${args.databaseName}'` + ); + for (let i = 0; i < rows.length; i++) { + let el = rows[i]; + el = _.mapKeys(el, (_v, k) => k.toLowerCase()); + rows[i] = el; + } + + result.data.list = rows; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async schemaList(args: any = {}) { + const _func = this.schemaList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const rows = await this.raw( + `SELECT username AS schema_name + FROM dba_users u + WHERE ACCOUNT_STATUS = 'OPEN' AND EXISTS ( + SELECT 1 + FROM dba_objects o + WHERE o.owner = u.username ) + AND default_tablespace not in ('SYSTEM','SYSAUX') ` + ); + for (let i = 0; i < rows.length; i++) { + let el = rows[i]; + el = _.mapKeys(el, (_v, k) => k.toLowerCase()); + rows[i] = el; + } + + result.data.list = rows; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - columns + * @property {String} - columns[].tn + * @property {String} - columns[].cn + * @property {String} - columns[].dt + * @property {String} - columns[].dtx + * @property {String} - columns[].np + * @property {String} - columns[].ns - + * @property {String} - columns[].clen - + * @property {String} - columns[].dp - + * @property {String} - columns[].cop - + * @property {String} - columns[].pk - + * @property {String} - columns[].nrqd - + * @property {String} - columns[].not_nullable - + * @property {String} - columns[].ct - + * @property {String} - columns[].un - + * @property {String} - columns[].ai - + * @property {String} - columns[].unique - + * @property {String} - columns[].cdf - + * @property {String} - columns[].cc - + * @property {String} - columns[].csn - + */ + + async columnList(args: any = {}) { + const _func = this.columnList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.user; + + const response = await this.raw(`SELECT + seq.sequence_name, + CASE + WHEN seq.sequence_name IS NOT NULL + AND p.constraint_type = 'P' THEN 1 + ELSE 0 + END as ai, + c.table_name as tn, + c.column_name AS cn, + c.column_id AS cop, + p.constraint_type AS ck, + c.NULLABLE AS nrqd, + c.DATA_TYPE AS dt, + c.DATA_LENGTH, + c.DATA_PRECISION, + c.DEFAULT_LENGTH, + c.DATA_DEFAULT AS cdf, + c.character_set_name as csn, + c.CHAR_LENGTH AS clen, + -- c.DEFAULT_ON_NULL, + c.LOW_VALUE, + c.HIGH_VALUE, + c.DATA_SCALE AS ns, + p.constraint_name AS pk_constraint_name, + p.POSITION AS p_con_ordinal_position + FROM + SYS.ALL_TAB_COLS c + LEFT JOIN( + SELECT + a.table_name, + a.column_name, + a.constraint_name, + c.constraint_type, + a.position, + c_pk.table_name r_table_name, + c_pk.constraint_name r_pk, + cc_pk.column_name r_column_name + FROM + all_cons_columns a + JOIN all_constraints c ON + ( + a.owner = c.owner + AND a.constraint_name = c.constraint_name + ) + LEFT JOIN all_constraints c_pk ON + ( + c.r_owner = c_pk.owner + AND c.r_constraint_name = c_pk.constraint_name + ) + LEFT JOIN all_cons_columns cc_pk ON + ( + cc_pk.owner = c_pk.owner + AND cc_pk.constraint_name = c_pk.constraint_name + ) + WHERE + a.owner = '${args.databaseName}' + AND c.constraint_type = 'P' + ) p ON + c.table_name = p.table_name + AND c.column_name = p.column_name + LEFT OUTER JOIN( + SELECT + t.table_name, + d.referenced_name AS sequence_name, + d.REFERENCED_OWNER AS OWNER, + c.COLUMN_NAME + FROM + user_trigger_cols t, + user_dependencies d, + user_tab_cols c + WHERE + d.name = t.trigger_name + AND t.TABLE_NAME = c.TABLE_NAME + AND t.COLUMN_NAME = c.COLUMN_NAME + AND d.referenced_type = 'SEQUENCE' + AND d.type = 'TRIGGER' + ) seq ON + c.table_name = seq.table_name + AND c.column_name = seq.column_name + WHERE + c.owner = '${args.databaseName}' AND c.table_name = '${args.tn}' + ORDER BY + c.table_name, + c.column_id, + c.column_name`); + + for (let i = 0; i < response.length; i++) { + let el = response[i]; + el = _.mapKeys(el, (_v, k) => k.toLowerCase()); + el.np = el.data_precision; + el.pk = el.ck === 'P'; + el.nrqd = el.nrqd === 'Y'; + el.not_nullable = el.nrqd === 'N'; + el.ai = el.ai === 1; + + el.cno = el.cn; + el.dtxp = el.clen || el.np; + // || + // el.dp;; + el.dtxs = el.ns; + + el.au = false; + + response[i] = el; + } + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - indexes + * @property {String} - indexes[].table - + * @property {String} - indexes[].cn - + * @property {String} - indexes[].key_name - + * @property {String} - indexes[].non_unique - + * @property {String} - indexes[].seq_in_index - + * @property {String} - indexes[].collation - + * @property {String} - indexes[].cardinality - + * @property {String} - indexes[].sub_part - + * @property {String} - indexes[].packed - + * @property {String} - indexes[].null - + * @property {String} - indexes[].index_type - + * @property {String} - indexes[].comment - + * @property {String} - indexes[].index_comment - + */ + async indexList(args: any = {}) { + const _func = this.indexList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.user; + + const response = await this.raw( + `select ind_col.COLUMN_POSITION AS postion, ind.index_name as key_name, + ind_col.column_name as cn, + ind.index_type, + ind.uniqueness, + ind.table_owner as schema_name, + ind.table_name as tn, + ind.table_type as table_type + from sys.all_indexes ind + inner join sys.all_ind_columns ind_col on ind.owner = ind_col.index_owner + and ind.index_name = ind_col.index_name + where ind.owner = '${args.databaseName}' AND ind.table_name = '${args.tn}' + order by ind.table_owner, ind.table_name, ind.index_name, ind_col.column_position` + ); + + for (let i = 0; i < response.length; i++) { + let el = response[i]; + el = _.mapKeys(el, (_v, k) => k.toLowerCase()); + el.table = el.tn; + el.non_unique = el.uniqueness === 'NONUNIQUE' ? 1 : 0; + el.non_unique_original = el.uniqueness === 'NONUNIQUE' ? 1 : 0; + el.seq_in_index = el.postion; + response[i] = el; + } + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - indexes + * @property {String} - indexes[].cstn - + * @property {String} - indexes[].cn - + * @property {String} - indexes[].op - + * @property {String} - indexes[].puc - + * @property {String} - indexes[].cst - + */ + async constraintList(args: any = {}) { + const _func = this.constraintList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.user; + + const response = await this.raw( + `SELECT cols.table_name as tn, cols.column_name as cn, cols.position, cons.* + FROM all_constraints cons, all_cons_columns cols + WHERE cols.table_name = '${args.tn}' AND cols.owner = '${args.databaseName}' + AND cons.constraint_type in ('P','R','U') AND cons.constraint_name = cols.constraint_name + AND cons.owner = cols.owner ORDER BY cons.constraint_name, cols.position` + ); + + for (let i = 0; i < response.length; i++) { + let el = response[i]; + el = _.mapKeys(el, (_v, k) => k.toLowerCase()); + el.table = el.tn; + if (el.cst === 'P') el.cst = 'Primary Key'; + if (el.cst === 'U') el.cst = 'Unique'; + if (el.cst === 'R') el.cst = 'Foreign Key'; + + el.op = el.position; + response[i] = el; + } + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - relations + * @property {String} - relations[].tn + * @property {String} - relations[].cstn - + * @property {String} - relations[].tn - + * @property {String} - relations[].cn - + * @property {String} - relations[].rtn - + * @property {String} - relations[].rcn - + * @property {String} - relations[].puc - + * @property {String} - relations[].ur - + * @property {String} - relations[].dr - + * @property {String} - relations[].mo - + */ + async relationList(args: any = {}) { + const _func = this.relationList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.user; + + const response = await this.raw(`SELECT + a.table_name as tn, + a.column_name as cn, + a.constraint_name cstn, + c.constraint_type cst, + a.position, + c_pk.table_name r_table_name, + c_pk.constraint_name r_pk, + cc_pk.column_name r_column_name, + c.delete_rule dr + FROM + all_cons_columns a + JOIN all_constraints c ON + ( a.owner = c.owner + AND a.constraint_name = c.constraint_name ) + LEFT JOIN all_constraints c_pk ON + ( c.r_owner = c_pk.owner + AND c.r_constraint_name = c_pk.constraint_name ) + LEFT JOIN all_cons_columns cc_pk ON + ( cc_pk.owner = c_pk.owner + AND cc_pk.constraint_name = c_pk.constraint_name ) + WHERE a.owner = '${args.databaseName}' + AND c.constraint_type = 'R' + + `); + + for (let i = 0; i < response.length; i++) { + let el = response[i]; + el = _.mapKeys(el, (_v, k) => k.toLowerCase()); + el.rtn = el.r_table_name; + el.rcn = el.r_column_name; + el.non_unique = el.uniqueness === 'NONUNIQUE'; + el.puc = el.position; + response[i] = el; + } + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async relationListAll(args: any = {}) { + const _func = this.relationList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.user; + + const response = await this.raw(`SELECT + a.table_name as tn, + a.column_name as cn, + a.constraint_name cstn, + c.constraint_type cst, + a.position puc, + c_pk.table_name r_table_name, + c_pk.constraint_name r_pk, + cc_pk.column_name r_column_name, + c.delete_rule dr + FROM + all_cons_columns a + JOIN all_constraints c ON + ( a.owner = c.owner + AND a.constraint_name = c.constraint_name ) + LEFT JOIN all_constraints c_pk ON + ( c.r_owner = c_pk.owner + AND c.r_constraint_name = c_pk.constraint_name ) + LEFT JOIN all_cons_columns cc_pk ON + ( cc_pk.owner = c_pk.owner + AND cc_pk.constraint_name = c_pk.constraint_name ) + WHERE a.owner = '${args.databaseName}' + AND c.constraint_type = 'R' + + `); + + for (let i = 0; i < response.length; i++) { + let el = response[i]; + el = _.mapKeys(el, (_v, k) => k.toLowerCase()); + el.rtn = el.r_table_name; + el.rcn = el.r_column_name; + el.non_unique = el.uniqueness === 'NONUNIQUE'; + // el.puc = el.puc; + response[i] = el; + } + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - triggers + * @property {String} - triggers[].trigger + * @property {String} - triggers[].event + * @property {String} - triggers[].table + * @property {String} - triggers[].statement + * @property {String} - triggers[].timing + * @property {String} - triggers[].created + * @property {String} - triggers[].sql_mode + * @property {String} - triggers[].definer + * @property {String} - triggers[].character_set_client + * @property {String} - triggers[].collation_connection + * @property {String} - triggers[].database collation + */ + async triggerList(args: any = {}) { + const _func = this.triggerList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.user; + + const response = await this.sqlClient + .raw(`select owner as trigger_schema_name, trigger_name, trigger_type, + triggering_event, table_owner as schema_name, table_name as object_name, base_object_type as object_type, + status, trigger_body as script from sys.all_triggers + -- excluding some Oracle maintained schemas + where owner = '${args.databaseName}' order by trigger_name, table_owner, table_name, base_object_type`); + + for (let i = 0; i < response.length; i++) { + let el = response[i]; + el = _.mapKeys(el, (_v, k) => k.toLowerCase()); + el.trigger = el.trigger_name; + el.table = el.object_name; + el.event = el.triggering_event; + el.timing = el.trigger_type; + el.statement = el.script; + response[i] = el; + } + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @returns {Object[]} - functions + * @property {String} - functions[].function_name + * @property {String} - functions[].type + * @property {String} - functions[].definer + * @property {String} - functions[].modified + * @property {String} - functions[].created + * @property {String} - functions[].security_type + * @property {String} - functions[].comment + * @property {String} - functions[].character_set_client + * @property {String} - functions[].collation_connection + * @property {String} - functions[].database collation + */ + async functionList(args: any = {}) { + const _func = this.functionList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.user; + + const response = await this.raw( + `SELECT * FROM ALL_OBJECTS WHERE owner = '${args.databaseName}' and OBJECT_TYPE IN ('FUNCTION','PROCEDURE','PACKAGE')` + ); + + for (let i = 0; i < response.length; i++) { + let el = response[i]; + el = _.mapKeys(el, (_v, k) => k.toLowerCase()); + el.function_name = el.object_name; + el.type = el.object_type; + response[i] = el; + } + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - For future reasons + * @returns {Object[]} - procedures + * @property {String} - procedures[].procedure_name + * @property {String} - procedures[].type + * @property {String} - procedures[].definer + * @property {String} - procedures[].modified + * @property {String} - procedures[].created + * @property {String} - procedures[].security_type + * @property {String} - procedures[].comment + * @property {String} - procedures[].definer + * @property {String} - procedures[].character_set_client + * @property {String} - procedures[].collation_connection + * @property {String} - procedures[].database collation + */ + async procedureList(args: any = {}) { + const _func = this.procedureList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.user; + + const response = await this.raw( + `SELECT * FROM ALL_OBJECTS WHERE owner = '${args.databaseName}' and OBJECT_TYPE IN ('FUNCTION','PROCEDURE','PACKAGE')` + ); + + for (let i = 0; i < response.length; i++) { + let el = response[i]; + el = _.mapKeys(el, (_v, k) => k.toLowerCase()); + el.procedure_name = el.object_name; + el.type = el.object_type; + response[i] = el; + } + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @returns {Object[]} - views + * @property {String} - views[].sql_mode + * @property {String} - views[].create_function + * @property {String} - views[].database collation + * @property {String} - views[].collation_connection + * @property {String} - views[].character_set_client + */ + async viewList(args: any = {}) { + const _func = this.viewList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.user; + + const response = await this.raw( + `SELECT * FROM all_views WHERE owner='${args.databaseName}'` + ); + + for (let i = 0; i < response.length; i++) { + let el = response[i]; + el = _.mapKeys(el, (_v, k) => k.toLowerCase()); + el.view_name = el.object_name; + response[i] = el; + } + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.function_name - + * @returns {Object[]} - functions + * @property {String} - sql_mode + * @property {String} - create_function + * @property {String} - database collation + * @property {String} - collation_connection + * @property {String} - character_set_client + */ + async functionRead(args: any = {}) { + const _func = this.functionRead.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.user; + + const response = await this.raw( + `SELECT * FROM all_source WHERE TYPE = 'FUNCTION' AND OWNER = '${args.databaseName}' AND NAME = '${args.function_name}' ORDER BY line` + ); + const rows = []; + if (response.length > 0) { + let script = ''; + for (let i = 0; i < response.length; i++) { + let el = response[i]; + el = _.mapKeys(el, (_v, k) => k.toLowerCase()); + el.function_name = el.name; + response[i] = el; + script += el.text; + } + response[0].create_function = script; + rows.push(response[0]); + } + + result.data.list = rows; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.procedure_name - + * @returns {Object[]} - functions + * @property {String} - sql_mode + * @property {String} - create_function + * @property {String} - database collation + * @property {String} - collation_connection + * @property {String} - character_set_client + */ + async procedureRead(args: any = {}) { + const _func = this.procedureRead.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.user; + + const response = await this.raw( + `SELECT * FROM all_source WHERE TYPE = 'PROCEDURE' AND OWNER = '${args.databaseName}' AND NAME = '${args.procedure_name}' ORDER BY line` + ); + const rows = []; + if (response.length > 0) { + let script = ''; + for (let i = 0; i < response.length; i++) { + let el = response[i]; + el = _.mapKeys(el, (_v, k) => k.toLowerCase()); + el.procedure_name = el.name; + response[i] = el; + script += el.text; + } + response[0].create_procedure = script; + rows.push(response[0]); + } + + result.data.list = rows; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.view_name - + * @returns {Object[]} - views + * @property {String} - views[].tn + */ + async viewRead(args: any = {}) { + const _func = this.viewRead.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.user; + + const response = await this.raw( + `SELECT * FROM all_views WHERE owner='${args.databaseName}' and view_name='${args.view_name}'` + ); + + for (let i = 0; i < response.length; i++) { + let el = response[i]; + el = _.mapKeys(el, (_v, k) => k.toLowerCase()); + el.view_name = el.object_name; + el.view_definition = el.text; + response[i] = el; + } + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async triggerRead(args: any = {}) { + const _func = this.triggerRead.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.user; + + const response = await this.sqlClient + .raw(`select owner as trigger_schema_name, trigger_name, trigger_type, + triggering_event, table_owner as schema_name, table_name as object_name, + base_object_type as object_type, status, trigger_body as script from sys.all_triggers + -- excluding some Oracle maintained schemas + where owner = '${args.databaseName}' and trigger_name = '${args.trigger_name}' order by trigger_name, table_owner, table_name, base_object_type`); + if (!response[0]) return []; + + for (let i = 0; i < response.length; i++) { + let el = response[i]; + el = _.mapKeys(el, (_v, k) => k.toLowerCase()); + el.trigger = el.trigger_name; + el.table = el.object_name; + el.event = el.triggering_event; + el.timing = el.trigger_type; + el.trigger_definition = el.script; + response[i] = el; + } + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async schemaCreate(args: any = {}) { + const _func = this.schemaCreate.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + await this.raw(`create database ${args.database_name}`); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async schemaDelete(args: any = {}) { + const _func = this.schemaDelete.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + await this.raw(`drop database ${args.database_name}`); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async triggerDelete(args: any = {}) { + const _func = this.triggerDelete.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + await this.raw(`DROP TRIGGER IF EXISTS ${args.trigger_name}`); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async functionDelete(args: any = {}) { + const _func = this.functionDelete.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + await this.raw(`DROP FUNCTION IF EXISTS ${args.function_name}`); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async procedureDelete(args: any = {}) { + const _func = this.procedureDelete.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + await this.raw(`DROP PROCEDURE IF EXISTS ${args.procedure_name}`); + } catch (e) { + log.ppe(e, _func); + throw e; + } + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.function_name + * @param {String} - args.event + * @param {String} - args.timing + * @returns {Object[]} - result rows + */ + async functionCreate(args: any = {}) { + const func = this.functionCreate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const rows = await this.sqlClient.raw( + `CREATE TRIGGER \`${args.function_name}\` \n${args.timing} ${args.event}\nON ${args.tn} FOR EACH ROW\n${args.statement}` + ); + result.data.list = rows; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.function_name + * @param {String} - args.event + * @param {String} - args.timing + * @returns {Object[]} - result rows + */ + async functionUpdate(args: any = {}) { + const func = this.functionUpdate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + await this.sqlClient.raw(`DROP TRIGGER ${args.function_name}`); + const rows = await this.sqlClient.raw( + `CREATE TRIGGER \`${args.function_name}\` \n${args.timing} ${args.event}\nON ${args.tn} FOR EACH ROW\n${args.statement}` + ); + result.data.list = rows; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.procedure_name + * @param {String} - args.event + * @param {String} - args.timing + * @returns {Object[]} - result rows + */ + async procedureCreate(args: any = {}) { + const func = this.procedureCreate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const rows = await this.sqlClient.raw( + `CREATE TRIGGER \`${args.procedure_name}\` \n${args.timing} ${args.event}\nON ${args.tn} FOR EACH ROW\n${args.statement}` + ); + result.data.list = rows; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.procedure_name + * @param {String} - args.event + * @param {String} - args.timing + * @returns {Object[]} - result rows + */ + async procedureUpdate(args: any = {}) { + const func = this.procedureUpdate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + await this.sqlClient.raw(`DROP TRIGGER ${args.procedure_name}`); + const rows = await this.sqlClient.raw( + `CREATE TRIGGER \`${args.procedure_name}\` \n${args.timing} ${args.event}\nON ${args.tn} FOR EACH ROW\n${args.statement}` + ); + result.data.list = rows; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.trigger_name + * @param {String} - args.event + * @param {String} - args.timing + * @returns {Object[]} - result rows + */ + async triggerCreate(args: any = {}) { + const func = this.triggerCreate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const query = `CREATE TRIGGER \`${args.trigger_name}\` \n${args.timing} ${args.event}\nON ${args.tn} FOR EACH ROW\n${args.statement}`; + await this.sqlClient.raw(query); + result.data.object = { + upStatement: query, + downStatement: `DROP TRIGGER ${args.trigger_name}` + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.trigger_name + * @param {String} - args.event + * @param {String} - args.timing + * @param {String} - args.oldStatement + * @returns {Object[]} - result rows + */ + async triggerUpdate(args: any = {}) { + const func = this.triggerUpdate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + await this.sqlClient.raw(`DROP TRIGGER ${args.trigger_name}`); + await this.sqlClient.raw( + `CREATE TRIGGER \`${args.trigger_name}\` \n${args.timing} ${args.event}\nON ${args.tn} FOR EACH ROW\n${args.statement}` + ); + + result.data.object = { + upStatement: `DROP TRIGGER ${args.trigger_name};\nCREATE TRIGGER \`${args.trigger_name}\` \n${args.timing} ${args.event}\nON ${args.tn} FOR EACH ROW\n${args.statement}`, + downStatement: `CREATE TRIGGER \`${args.trigger_name}\` \n${args.timing} ${args.event}\nON ${args.tn} FOR EACH ROW\n${args.oldStatement}` + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.view_name + * @param {String} - args.view_definition + * @returns {Object} - up and down statements + */ + async viewCreate(args: any = {}) { + const func = this.viewCreate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const query = `CREATE VIEW ${args.view_name} AS \n${args.view_definition}`; + + await this.sqlClient.raw(query); + result.data.object = { + upStatement: query, + downStatement: `DROP VIEW ${args.view_name}` + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.view_name + * @param {String} - args.view_definition + * @param {String} - args.oldViewDefination + * @returns {Object} - up and down statements + */ + async viewUpdate(args: any = {}) { + const func = this.viewUpdate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const query = `CREATE OR REPLACE VIEW ${args.view_name} AS \n${args.view_definition}`; + + await this.sqlClient.raw(query); + result.data.object = { + upStatement: query, + downStatement: `CREATE VIEW ${args.view_name} AS \n${args.oldViewDefination}` + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.view_name + * @param {String} - args.view_definition + * @param {String} - args.oldViewDefination + * @returns {Object} - up and down statements + */ + async viewDelete(args: any = {}) { + const func = this.viewDelete.name; + const result = new Result(); + log.api(`${func}:args:`, args); + // `DROP TRIGGER ${args.view_name}` + try { + const query = `DROP VIEW ${args.view_name}`; + + await this.sqlClient.raw(query); + + result.data.object = { + upStatement: query, + downStatement: `CREATE VIEW ${args.view_name} AS \n${args.oldViewDefination}` + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args + * @param {String} - args.tn + * @param {Object[]} - args.columns + * @param {String} - args.columns[].tn + * @param {String} - args.columns[].cn + * @param {String} - args.columns[].dt + * @param {String} - args.columns[].np + * @param {String} - args.columns[].ns - + * @param {String} - args.columns[].clen - + * @param {String} - args.columns[].dp - + * @param {String} - args.columns[].cop - + * @param {String} - args.columns[].pk - + * @param {String} - args.columns[].nrqd - + * @param {String} - args.columns[].not_nullable - + * @param {String} - args.columns[].ct - + * @param {String} - args.columns[].un - + * @param {String} - args.columns[].ai - + * @param {String} - args.columns[].unique - + * @param {String} - args.columns[].cdf - + * @param {String} - args.columns[].cc - + * @param {String} - args.columns[].csn - + * @param {String} - args.columns[].dtx + * - value will be 'specificType' for all cols except ai + * - for ai it will be integer, bigInteger + * - tiny, small and medium Int auto increement is not supported + * @param {String} - args.columns[].dtxp - to use in UI + * @param {String} - args.columns[].dtxs - to use in UI + * @returns {Promise<{upStatement, downStatement}>} + */ + async tableCreate(args) { + const _func = this.tableCreate.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + args.table = args.tn; + args.sqlClient = this.sqlClient; + + /**************** create table ****************/ + const upQuery = createTable(args.tn, args); + await this.sqlClient.raw(upQuery); + + const downStatement = this.sqlClient.schema.dropTable(args.table).toSQL(); + + this.emit(`Success : ${upQuery}`); + + /**************** return files *************** */ + result.data.object = { + upStatement: [{ sql: upQuery }], + downStatement + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param {Object} - args + * @param {String} - args.table + * @param {String} - args.table + * @param {Object[]} - args.columns + * @param {String} - args.columns[].tn + * @param {String} - args.columns[].cn + * @param {String} - args.columns[].dt + * @param {String} - args.columns[].np + * @param {String} - args.columns[].ns - + * @param {String} - args.columns[].clen - + * @param {String} - args.columns[].dp - + * @param {String} - args.columns[].cop - + * @param {String} - args.columns[].pk - + * @param {String} - args.columns[].nrqd - + * @param {String} - args.columns[].not_nullable - + * @param {String} - args.columns[].ct - + * @param {String} - args.columns[].un - + * @param {String} - args.columns[].ai - + * @param {String} - args.columns[].unique - + * @param {String} - args.columns[].cdf - + * @param {String} - args.columns[].cc - + * @param {String} - args.columns[].csn - + * @param {Number} - args.columns[].altered - 1,2,4 = addition,edited,deleted + * @returns {Promise<{upStatement, downStatement}>} + */ + async tableUpdate(args) { + const _func = this.tableUpdate.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + args.table = args.tn; + const originalColumns = args.originalColumns; + args.connectionConfig = this._connectionConfig; + args.sqlClient = this.sqlClient; + + let upQuery = ''; + let downQuery = ''; + + for (let i = 0; i < args.columns.length; ++i) { + const oldColumn = lodash.find(originalColumns, { + cn: args.columns[i].cno + }); + + if (args.columns[i].altered & 4) { + // col remove + upQuery += alterTableRemoveColumn( + args.table, + args.columns[i], + oldColumn + // upQuery + ); + downQuery += alterTableAddColumn( + args.table, + oldColumn, + args.columns[i], + downQuery + ); + } else if (args.columns[i].altered & 2 || args.columns[i].altered & 8) { + // col edit + upQuery += alterTableChangeColumn( + args.table, + args.columns[i], + oldColumn, + upQuery + ); + downQuery += alterTableChangeColumn( + args.table, + oldColumn, + args.columns[i], + downQuery + ); + } else if (args.columns[i].altered & 1) { + // col addition + upQuery += alterTableAddColumn( + args.table, + args.columns[i], + oldColumn, + upQuery + ); + downQuery += alterTableRemoveColumn( + args.table, + args.columns[i], + oldColumn + // downQuery + ); + } + } + + //upQuery += alterTablePK(args.columns, args.originalColumns, upQuery); + //downQuery += alterTablePK(args.originalColumns, args.columns, downQuery); + + if (upQuery) { + //upQuery = `ALTER TABLE ${args.columns[0].tn} ${upQuery};`; + //downQuery = `ALTER TABLE ${args.columns[0].tn} ${downQuery};`; + } + + await this.sqlClient.raw(upQuery); + + console.log(upQuery); + + result.data.object = { + upStatement: [{ sql: upQuery }], + downStatement: [{ sql: downQuery }] + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param {Object} - args + * @param args.tn + * @returns {Promise<{upStatement, downStatement}>} + */ + async tableDelete(args) { + const _func = this.tableDelete.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + // const { columns } = args; + args.sqlClient = this.sqlClient; + + /** ************** create up & down statements *************** */ + const upStatement = this.sqlClient.schema.dropTable(args.tn).toSQL(); + const downQuery = createTable(args.tn, args); + + this.emit(`Success : ${upStatement}`); + + /** ************** drop tn *************** */ + await this.sqlClient.schema.dropTable(args.tn); + + /** ************** return files *************** */ + result.data.object = { + upStatement, + downStatement: [{ sql: downQuery }] + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableCreateStatement(args) { + const _func = this.tableCreateStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + result.data = ';'; + const response = await this.sqlClient.raw( + `show create table ${args.tn};` + ); + if (response.length === 2) { + result.data = response[0][0]['Create Table']; + } + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableInsertStatement(args) { + const _func = this.tableCreateStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + let values = ' VALUES ('; + result.data = `INSERT INTO \`${args.tn}\` (`; + const response = await this.columnList(args); + if (response.data && response.data.list) { + for (let i = 0; i < response.data.list.length; ++i) { + if (!i) { + result.data += `\n\`${response.data.list[i].cn}\`\n\t`; + values += `\n<${response.data.list[i].cn}>\n\t`; + } else { + result.data += `, \`${response.data.list[i].cn}\`\n\t`; + values += `, <${response.data.list[i].cn}>\n\t`; + } + } + } + + result.data += `)`; + values += `);`; + result.data += values; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableUpdateStatement(args) { + const _func = this.tableUpdateStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + result.data = `UPDATE \`${args.tn}\` \nSET\n`; + const response = await this.columnList(args); + if (response.data && response.data.list) { + for (let i = 0; i < response.data.list.length; ++i) { + if (!i) { + result.data += `\`${response.data.list[i].cn}\` = <\`${response.data.list[i].cn}\`>\n\t`; + } else { + result.data += `,\`${response.data.list[i].cn}\` = <\`${response.data.list[i].cn}\`>\n\t`; + } + } + } + + result.data += ';'; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableDeleteStatement(args) { + const _func = this.tableDeleteStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + result.data = `DELETE FROM ${args.tn};`; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableTruncateStatement(args) { + const _func = this.tableTruncateStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + result.data = `TRUNCATE TABLE ${args.tn};`; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableSelectStatement(args) { + const _func = this.tableSelectStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + result.data = `SELECT `; + const response = await this.columnList(args); + if (response.data && response.data.list) { + for (let i = 0; i < response.data.list.length; ++i) { + if (!i) { + result.data += `${response.data.list[i].cn}\n\t`; + } else { + result.data += `, ${response.data.list[i].cn}\n\t`; + } + } + } + + result.data += ` FROM ${args.tn};`; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param {Object} args + * @returns {Object} result + * @returns {Number} code + * @returns {String} message + */ + async totalRecords(_args: any = {}) { + // @ts-ignore + const func = this.totalRecords.name; + throw new Error('Function not supported for oracle yet'); + } +} + +function alterTablePK(n, o, _existingQuery, createTable = false) { + const numOfPksInOriginal = []; + const numOfPksInNew = []; + let pksChanged = 0; + + for (let i = 0; i < n.length; ++i) { + if (n[i].pk) { + if (n[i].altered !== 4) numOfPksInNew.push(n[i].cn); + } + } + + for (let i = 0; i < o.length; ++i) { + if (o[i].pk) { + numOfPksInOriginal.push(o[i].cn); + } + } + + if (numOfPksInNew.length === numOfPksInOriginal.length) { + for (let i = 0; i < numOfPksInNew.length; ++i) { + if (numOfPksInOriginal[i] !== numOfPksInNew[i]) { + pksChanged = 1; + break; + } + } + } else { + pksChanged = numOfPksInNew.length - numOfPksInOriginal.length; + } + + let query = ''; + if (!numOfPksInNew.length && !numOfPksInOriginal.length) { + // do nothing + } else if (pksChanged) { + query += numOfPksInOriginal.length ? ',DROP PRIMARY KEY' : ''; + + if (numOfPksInNew.length) { + if (createTable) { + query += `, PRIMARY KEY(${numOfPksInNew.join(',')})`; + } else { + query += `, ADD PRIMARY KEY(${numOfPksInNew.join(',')})`; + } + } + } + + return query; +} + +function alterTableRemoveColumn(n, _o, existingQuery) { + let query = existingQuery ? ',' : ''; + query += ` DROP COLUMN \`${n.cn}\``; + return query; +} + +function createTableColumn(t, n, o, existingQuery) { + return alterTableColumn(t, n, o, existingQuery, 0); +} + +function alterTableAddColumn(t, n, o, existingQuery) { + return alterTableColumn(t, n, o, existingQuery, 1); +} + +function alterTableChangeColumn(t, n, o, existingQuery) { + return alterTableColumn(t, n, o, existingQuery, 2); +} + +function createTable(table, args) { + let query = ''; + + for (let i = 0; i < args.columns.length; ++i) { + query += createTableColumn(table, args.columns[i], null, query); + } + + query += alterTablePK(args.columns, [], query, true); + + query = `CREATE TABLE ${args.tn} (` + query + ')'; + + return query; +} + +function alterTableColumn(t, n, o, existingQuery, change = 2) { + // CREATE TABLE test1 ( id integer NOT NULL, title varchar NULL); + + // CREATE TABLE CHINOOK.NEWTABLE_1 ( + // ID INTEGER NOT NULL, + // TITLE VARCHAR(100), + // CONSTRAINT NEWTABLE_1_PK PRIMARY KEY (ID) + // ); + + const scale = parseInt(n.dtxs) ? parseInt(n.dtxs) : null; + + let query = existingQuery ? ',' : ''; + + const defaultValue = getDefaultValue(n); + + if (change === 0) { + if (n.ai) { + switch (n.dt) { + case 'int4': + default: + query += ` ${n.cn} serial`; + break; + } + } else { + query += ` ${n.cn} ${n.dt}`; + } + + query += n.dtxp && n.dtxp !== ' ' ? `(${n.dtxp}` : ''; + query += scale ? `,${scale}` : ''; + query += n.dtxp && n.dtxp !== ' ' ? ')' : ''; + + query += n.rqd ? ' NOT NULL' : ' NULL'; + query += defaultValue ? ` DEFAULT ${defaultValue}` : ''; + } else if (change === 1) { + query += ` ADD ${n.cn} ${n.dt}`; + query += n.rqd ? ' NOT NULL' : ' NULL'; + query += defaultValue ? ` DEFAULT ${defaultValue}` : ' '; + query = `ALTER TABLE ${t} ${query};`; + } else { + if (n.cn !== o.cno) { + query += `\nALTER TABLE ${t} RENAME COLUMN ${n.cno} TO ${n.cn};\n`; + } + + if (n.dt !== o.dt) { + query += `\nALTER TABLE ${t} ALTER COLUMN ${n.cn} TYPE ${n.dt};\n`; + } + + if (n.rqd !== o.rqd) { + query += `\nALTER TABLE ${t} ALTER COLUMN ${n.cn} `; + query += n.rqd ? ` SET NOT NULL;\n` : ` DROP NOT NULL;\n`; + } + + if (n.cdf !== o.cdf) { + query += `\nALTER TABLE ${t} ALTER COLUMN ${n.cn} `; + query += n.cdf ? ` SET DEFAULT ${n.cdf};\n` : ` DROP DEFAULT;\n`; + } + } + + return query; +} + +function getDefaultValue(n) { + if (n.cdf === undefined || n.cdf === null) return n.cdf; + switch (n.dt) { + case 'bfile': + return n.cdf; + break; + case 'binary rowid': + return n.cdf; + break; + case 'binary double': + return n.cdf; + break; + case 'binary_float': + return n.cdf; + break; + case 'blob': + return n.cdf; + break; + case 'canoical': + return n.cdf; + break; + case 'cfile': + return n.cdf; + break; + case 'char': + return n.cdf; + break; + case 'clob': + return n.cdf; + break; + case 'content pointer': + return n.cdf; + break; + case 'contigous array': + return n.cdf; + break; + case 'date': + return n.cdf; + break; + case 'decimal': + return n.cdf; + break; + case 'double precision': + return n.cdf; + break; + case 'float': + return n.cdf; + break; + case 'integer': + return n.cdf; + break; + case 'interval day to second': + return n.cdf; + break; + case 'interval year to month': + return n.cdf; + break; + case 'lob pointer': + return n.cdf; + break; + case 'long': + return n.cdf; + break; + case 'long raw': + return n.cdf; + break; + case 'named collection': + return n.cdf; + break; + case 'named object': + return n.cdf; + break; + case 'nchar': + return n.cdf; + break; + case 'nclob': + return n.cdf; + break; + case 'number': + return n.cdf; + break; + case 'nvarchar2': + return n.cdf; + break; + case 'octet': + return n.cdf; + break; + case 'oid': + return n.cdf; + break; + case 'pointer': + return n.cdf; + break; + case 'raw': + return n.cdf; + break; + case 'real': + return n.cdf; + break; + case 'ref': + return n.cdf; + break; + case 'ref cursor': + return n.cdf; + break; + case 'rowid': + return n.cdf; + break; + case 'signed binary integer': + return n.cdf; + break; + case 'smallint': + return n.cdf; + break; + case 'table': + return n.cdf; + break; + case 'time': + return n.cdf; + break; + case 'time with tz': + return n.cdf; + break; + case 'timestamp': + return n.cdf; + break; + case 'timestamp with local time zone': + return n.cdf; + break; + case 'timestamp with local tz': + return n.cdf; + break; + case 'timestamp with timezone': + return n.cdf; + break; + case 'timestamp with tz': + return n.cdf; + break; + case 'unsigned binary integer': + return n.cdf; + break; + case 'urowid': + return n.cdf; + break; + case 'varchar': + return n.cdf; + break; + case 'varchar2': + return n.cdf; + break; + case 'varray': + return n.cdf; + break; + case 'varying array': + return n.cdf; + break; + } +} + +export default OracleClient; diff --git a/packages/nocodb/src/lib/db/sql-client/lib/oracle/oracle.queries.ts b/packages/nocodb/src/lib/db/sql-client/lib/oracle/oracle.queries.ts new file mode 100644 index 0000000000..6f0b78511f --- /dev/null +++ b/packages/nocodb/src/lib/db/sql-client/lib/oracle/oracle.queries.ts @@ -0,0 +1,25 @@ +// https://docs.oracle.com/cd/E11882_01/appdev.112/e10646/oci03typ.htm#LNOCI16276 + +const oracleQueries = { + varchar2: {}, + number: {}, + integer: {}, + float: {}, + string: {}, + varnum: {}, + decimal: {}, + long: {}, + varchar: {}, + date: {}, + varraw: {}, + raw: {}, + 'long raw': {}, + un: {}, + 'long varchar': {}, + 'long varraw': {}, + char: {}, + charz: {}, + rowiddescriptor: {} +}; + +export default oracleQueries; diff --git a/packages/nocodb/src/lib/db/sql-client/lib/order.ts b/packages/nocodb/src/lib/db/sql-client/lib/order.ts new file mode 100644 index 0000000000..dce19566b0 --- /dev/null +++ b/packages/nocodb/src/lib/db/sql-client/lib/order.ts @@ -0,0 +1,111 @@ +//define class +class Order { + private matrix: any; + private squareLen: any; + constructor(squareMatrix) { + if (squareMatrix) { + this.matrix = squareMatrix; + this.squareLen = this.matrix.length; + } + } + + _hasColumnZeroes(index) { + for (let i = 0; i < this.matrix.length; ++i) { + if (this.matrix[i][index] !== 0) { + return false; + } + } + + return true; + } + + _makeAllElementsInRowAsZero(index) { + for (let i = 0; i < this.matrix.length; ++i) { + this.matrix[index][i] = 0; + } + } + + _selfPointing() { + const self = {}; + for (let i = 0; i < this.matrix.length; ++i) { + if (this.matrix[i][i] === 1) { + this.matrix[i][i] = 0; + self[i] = i; + } + } + return self; + } + + _CycleBetweenNodes() { + const two = []; + + for (let i = 0; i < this.matrix.length; ++i) { + for (let j = i + 1; j < this.matrix.length; ++j) { + if (i !== j && this.matrix[i][j] === 1 && this.matrix[j][i] === 1) { + two.push([i, j]); + console.log(i, j); + } + } + } + + return two; + } + + /*** + * + * @returns {order:[],one:{},two:[],cycle:{}} + */ + getOrder() { + console.time('getOrder'); + + const visited = {}; + const cycle = {}; + const order = []; + const one = this._selfPointing(); + const two = this._CycleBetweenNodes(); + + for (let i = 0; i < this.squareLen; ++i) { + cycle[i] = i; + } + + for (let i = 0; i < this.squareLen; ++i) { + for (let j = 0; j < this.squareLen; ++j) { + //console.log('- - - - - - - - - - - -- '); + + if (j in visited) { + //console.log('Already visited',j); + } else if (this._hasColumnZeroes(j)) { + //console.log('Inserting to visit order --> ',j); + visited[j] = j; + delete cycle[j]; + order.push(j); + this._makeAllElementsInRowAsZero(j); + } else { + //console.log('Column does not have zeroes',j); + } + + if (order.length === this.squareLen) { + console.timeEnd('getOrder'); + return { order, one, cycle }; + } + } + } + + console.timeEnd('getOrder'); + return { order, one, cycle, two }; + } + + setIndex(i, j) { + if (i < this.matrix.length && j < this.matrix.length) { + this.matrix[i][j] = 1; + } + } + + resetIndex(i, j) { + if (i < this.matrix.length && j < this.matrix.length) { + this.matrix[i][j] = 0; + } + } +} + +export default Order; diff --git a/packages/nocodb/src/lib/db/sql-client/lib/pg/PgClient.ts b/packages/nocodb/src/lib/db/sql-client/lib/pg/PgClient.ts new file mode 100644 index 0000000000..66875515c5 --- /dev/null +++ b/packages/nocodb/src/lib/db/sql-client/lib/pg/PgClient.ts @@ -0,0 +1,2829 @@ +import { nanoid } from 'nanoid'; + +import knex from 'knex'; +import _ from 'lodash'; +import KnexClient from '../KnexClient'; +import Debug from '../../../util/Debug'; +import Result from '../../../util/Result'; +import queries from './pg.queries'; +import lodash from 'lodash'; +const log = new Debug('PGClient'); + +class PGClient extends KnexClient { + private queries: any; + private _version: any; + constructor(connectionConfig) { + super(connectionConfig); + // this.sqlClient = null; + this.queries = queries; + this._version = {}; + } + + /** + * + * + * @param {Object} args + * @returns {Object} result + * @returns {Number} code + * @returns {String} message + */ + async schemaCreateWithCredentials(args) { + const func = this.schemaCreateWithCredentials.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + if (!args.schema) { + args.schema = `nc${nanoid(8)}`; + } + if (!args.user) { + args.user = `nc${nanoid(8)}`; + } + if (!args.password) { + args.password = nanoid(16); + } + + // const connectionParamsWithoutDb = JSON.parse( + // JSON.stringify(this.connectionConfig) + // ); + // + // delete connectionParamsWithoutDb.connection.database; + // + // const tempSqlClient = knex(connectionParamsWithoutDb); + + const data = await this.sqlClient.raw('create database ?', [args.schema]); + + // postgres=# create database mydb; + // postgres=# create user myuser with encrypted password 'mypass'; + // postgres=# grant all privileges on database mydb to myuser; + + await this.sqlClient.raw(`create user ? with encrypted password ?`, [ + args.user, + args.password + ]); + await this.sqlClient.raw(`grant all privileges on database ?? to ?`, [ + args.schema, + args.user + ]); + + log.debug('Create database if not exists', data); + + // create new knex client + // this.sqlClient = knex(this.connectionConfig); + // tempSqlClient.destroy(); + result.object = args; + } catch (e) { + // log.ppe(e); + result.code = -1; + result.message = e.message; + result.object = e; + } + return result; + } + + /** + * + * @param {Object} - args + * @param args.sequence_name + * @returns {Promise<{upStatement, downStatement}>} + */ + async sequenceDelete(args: any = {}) { + const _func = this.sequenceDelete.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const query = `${this.querySeparator()}DROP SEQUENCE ${ + args.sequence_name + }`; + await this.sqlClient.raw(query); + result.data.object = { + upStatement: [{ sql: query }], + downStatement: [ + { + sql: `${this.querySeparator()}CREATE SEQUENCE ${args.sequence_name}` + } + ] + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @returns {Object[]} - sequences + * @property {String} - sequences[].sequence_name + * @property {String} - sequences[].type + * @property {String} - sequences[].definer + * @property {String} - sequences[].modified + * @property {String} - sequences[].created + * @property {String} - sequences[].security_type + * @property {String} - sequences[].comment + * @property {String} - sequences[].character_set_client + * @property {String} - sequences[].collation_connection + * @property {String} - sequences[].database collation + */ + async sequenceList(args: any = {}) { + const _func = this.sequenceList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.database; + const { rows } = await this.raw(`select * + from INFORMATION_SCHEMA.sequences;`); + + result.data.list = rows.map(seq => { + return { + ...seq, + original_sequence_name: seq.sequence_name + }; + }); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.sequence_name + * @param {String} - args.start_value + * @param {String} - args.min_value + * @param {String} - args.max_value + * @param {String} - args.increment_by + * @returns {Object} - result + */ + async sequenceCreate(args: any = {}) { + const func = this.sequenceCreate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const query = + this.querySeparator() + `CREATE SEQUENCE ${args.sequence_name}`; + await this.sqlClient.raw(query); + result.data.object = { + upStatement: [{ sql: query }], + downStatement: [ + { sql: this.querySeparator() + `DROP SEQUENCE ${args.sequence_name}` } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.sequence_name + * @param {String} - args.start_value + * @param {String} - args.min_value + * @param {String} - args.max_value + * @param {String} - args.increment_by + * @returns {Object} - result + */ + async sequenceUpdate(args: any = {}) { + const func = this.sequenceUpdate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const upQuery = + this.querySeparator() + + `ALTER SEQUENCE ${args.original_sequence_name} RENAME TO ${args.sequence_name};`; + const downQuery = + this.querySeparator() + + `ALTER SEQUENCE ${args.sequence_name} RENAME TO ${args.original_sequence_name};`; + + await this.sqlClient.raw(upQuery); + result.data.object = { + upStatement: [{ sql: upQuery }], + downStatement: [{ sql: downQuery }] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * @param {Object} args + * @returns {Object} result + * @returns {Number} code + * @returns {String} message + */ + async testConnection(args: any = {}) { + const _func = this.testConnection.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + await this.raw('SELECT 1+1 as data'); + } catch (e1) { + const connectionParamsWithoutDb = JSON.parse( + JSON.stringify(this.connectionConfig) + ); + connectionParamsWithoutDb.connection.database = 'postgres'; + const tempSqlClient = knex(connectionParamsWithoutDb); + try { + await tempSqlClient.raw('SELECT 1+1 as data'); + await tempSqlClient.destroy(); + } catch (e) { + if (!/^database "[\w\d_]+" does not exist$/.test(e.message)) { + log.ppe(e); + result.code = -1; + // send back original error message + result.message = e1.message; + } + } + } finally { + log.api(`${_func}:result:`, result); + } + + return result; + } + + getKnexDataTypes() { + const result = new Result(); + + // result.data.list = [ + // "integer", + // "bigInteger", + // "text", + // "string", + // "float", + // "decimal", + // "boolean", + // "date", + // // "datetime", + // "time", + // "timestamp", + // "binary", + // "enu", + // "json", + // "specificType" + // ]; + + result.data.list = [ + 'int', + 'integer', + 'bigint', + 'bigserial', + 'char', + 'int2', + 'int4', + 'int8', + 'int4range', + 'int8range', + 'serial', + 'serial2', + 'serial8', + 'character', + 'bit', + 'bool', + 'boolean', + 'date', + 'double precision', + 'event_trigger', + 'fdw_handler', + 'float4', + 'float8', + 'uuid', + 'smallint', + 'smallserial', + 'character varying', + 'text', + 'real', + 'time', + 'time without time zone', + 'timestamp', + 'timestamp without time zone', + 'timestamptz', + 'timestamp with time zone', + 'timetz', + 'time with time zone', + 'daterange', + 'json', + 'jsonb', + 'gtsvector', + 'index_am_handler', + 'anyenum', + 'anynonarray', + 'anyrange', + 'box', + 'bpchar', + 'bytea', + 'cid', + 'cidr', + 'circle', + 'cstring', + 'inet', + 'internal', + 'interval', + 'language_handler', + 'line', + 'lsec', + 'macaddr', + 'money', + 'name', + 'numeric', + 'numrange', + 'oid', + 'opaque', + 'path', + 'pg_ddl_command', + 'pg_lsn', + 'pg_node_tree', + 'point', + 'polygon', + 'record', + 'refcursor', + 'regclass', + 'regconfig', + 'regdictionary', + 'regnamespace', + 'regoper', + 'regoperator', + 'regproc', + 'regpreocedure', + 'regrole', + 'regtype', + 'reltime', + 'smgr', + 'tid', + 'tinterval', + 'trigger', + 'tsm_handler', + 'tsquery', + 'tsrange', + 'tstzrange', + 'tsvector', + 'txid_snapshot', + 'unknown', + 'void', + 'xid', + 'xml' + ]; + + return result; + } + + /** + * + * + * @param {Object} args + * @returns {Object} result + * @returns {Number} code + * @returns {String} message + * @returns {Object} object - {version, primary, major, minor} + */ + async version(args: any = {}) { + const _func = this.version.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + result.data.object = {}; + const data = await this.sqlClient.raw('SHOW server_version'); + log.debug(data.rows[0]); + result.data.object.version = data.rows[0].server_version; + const versions = data.rows[0].server_version.split('.'); + + if (versions.length && (versions.length === 3 || versions.length === 2)) { + result.data.object.primary = versions[0]; + result.data.object.major = versions[1]; + result.data.object.minor = + versions.length > 2 ? versions[2] : versions[1]; + result.data.object.key = versions[0] + versions[1]; + } else { + result.code = -1; + result.message = `Invalid version : ${data.rows[0].server_version}`; + } + } catch (e) { + log.ppe(e); + result.code = -1; + result.message = e.message; + } finally { + log.api(`${_func} :result: %o`, result); + } + return result; + } + + /** + * + * @param {Object} args + * @param {String} args.database + * @returns {Result} + */ + async createDatabaseIfNotExists(args: any = {}) { + const _func = this.createDatabaseIfNotExists.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + let tempSqlClient; + + try { + const connectionParamsWithoutDb = JSON.parse( + JSON.stringify(this.connectionConfig) + ); + let rows = []; + try { + connectionParamsWithoutDb.connection.database = 'postgres'; + tempSqlClient = knex(connectionParamsWithoutDb); + + log.debug('checking if db exists'); + rows = ( + await tempSqlClient.raw( + `SELECT datname as database FROM pg_database WHERE datistemplate = false and datname = '${args.database}'` + ) + ).rows; + } catch (e) { + log.debug('checking if db exists'); + rows = ( + await this.sqlClient.raw( + `SELECT datname as database FROM pg_database WHERE datistemplate = false and datname = '${args.database}'` + ) + ).rows; + } + if (rows.length === 0) { + log.debug('creating database:', args); + await tempSqlClient.raw(`CREATE DATABASE ??`, [args.database]); + } + + // if (this.connectionConfig.searchPath && this.connectionConfig.searchPath[0]) { + await this.sqlClient.raw( + ` CREATE SCHEMA IF NOT EXISTS ?? AUTHORIZATION ?? `, + [ + (this.connectionConfig.searchPath && + this.connectionConfig.searchPath[0]) || + 'public', + this.connectionConfig.connection.user + ] + ); + // } + + // this.sqlClient = knex(this.connectionConfig); + } catch (e) { + log.ppe(e, _func); + throw e; + } finally { + if (tempSqlClient) { + await tempSqlClient.destroy(); + } + } + + log.api(`${_func}: result`, result); + return result; + } + + async dropDatabase(args) { + const _func = this.dropDatabase.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const connectionParamsWithoutDb = JSON.parse( + JSON.stringify(this.connectionConfig) + ); + connectionParamsWithoutDb.connection.database = 'postgres'; + const tempSqlClient = knex(connectionParamsWithoutDb); + await this.sqlClient.destroy(); + this.sqlClient = tempSqlClient; + + await tempSqlClient.raw(`ALTER DATABASE ${args.database} WITH CONNECTION LIMIT 0; + SELECT pg_terminate_backend(sa.pid) FROM pg_stat_activity sa WHERE + sa.pid <> pg_backend_pid() AND sa.datname = '${args.database}';`); + + log.debug('dropping database:', args); + await tempSqlClient.raw(`DROP DATABASE ${args.database};`); + } catch (e) { + log.ppe(e, _func); + // throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + /** + * + * @param args {tn} + * @returns + */ + async createTableIfNotExists(args) { + const _func = this.createTableIfNotExists.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + /** ************** START : create _evolution table if not exists *************** */ + const exists = await this.sqlClient.raw( + `SELECT table_schema,table_name as tn, table_catalog FROM information_schema.tables where table_schema=? and + table_name = '${args.tn}' and table_catalog = '${this.connectionConfig.connection.database}'`, + [this.schema] + ); + + if (exists.rows.length === 0) { + const data = await this.sqlClient.schema.createTable(args.tn, function( + table + ) { + table.increments(); + table.string('title').notNullable(); + table.string('titleDown').nullable(); + table.string('description').nullable(); + table.integer('batch').nullable(); + table.string('checksum').nullable(); + table.integer('status').nullable(); + table.dateTime('created'); + table.timestamps(); + }); + log.debug('Table created:', `${args.tn}`, data); + } else { + log.debug(`${args.tn} tables exists`); + } + /** ************** END : create _evolution table if not exists *************** */ + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + // async startTransaction() { + // let err = await this.sqlClient.raw("SET autocommit = 0"); + // log.debug("SET autocommit = 0:", err); + // err = await this.sqlClient.raw("start transaction"); + // log.debug("start transaction:", err); + // } + + // async commit() { + // const err = await this.sqlClient.raw("commit"); + // log.debug("commit:", err); + // await this.sqlClient.raw("SET autocommit = 1"); + // log.debug("SET autocommit = 1:", err); + // } + + // async rollback() { + // const err = await this.sqlClient.raw("rollback"); + // log.debug("rollback:", err); + // await this.sqlClient.raw("SET autocommit = 1"); + // log.debug("SET autocommit = 1:", err); + // } + + async hasTable(args: any = {}) { + const _func = this.hasTable.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const { + rows + } = await this.sqlClient.raw( + `SELECT table_schema,table_name as tn, table_catalog FROM information_schema.tables where table_schema=? and table_name = '${args.tn}' and table_catalog = '${this.connectionConfig.connection.database}'`, + [this.schema] + ); + result.data.value = rows.length > 0; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async hasDatabase(args: any = {}) { + const _func = this.hasDatabase.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const { rows } = await this.sqlClient.raw( + `SELECT datname as database FROM pg_database WHERE datistemplate = false and datname = '${args.databaseName}'` + ); + result.data.value = rows.length > 0; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - for future reasons + * @returns {Object[]} - databases + * @property {String} - databases[].database_name + */ + async databaseList(args: any = {}) { + const _func = this.databaseList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const { rows } = await this.sqlClient.raw( + `SELECT datname as database_name + FROM pg_database + WHERE datistemplate = false;` + ); + result.data.list = rows; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - for future reasons + * @returns {Object[]} - tables + * @property {String} - tables[].tn + */ + async tableList(args: any = {}) { + const _func = this.tableList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + const { rows } = await this.raw( + `SELECT table_schema as ts, table_name as tn,table_type + FROM information_schema.tables + where table_schema = ? + ORDER BY table_schema, table_name`, + [this.schema] + ); + + result.data.list = rows.filter( + ({ table_type }) => table_type.toLowerCase() === 'base table' + ); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async schemaList(args: any = {}) { + const _func = this.schemaList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const { rows } = await this + .raw(`SELECT datname as schema_name FROM pg_database + WHERE + datistemplate = false + order by schema_name;`); + + result.data.list = rows; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - columns + * @property {String} - columns[].tn + * @property {String} - columns[].cn + * @property {String} - columns[].dt + * @property {String} - columns[].dtx + * @property {String} - columns[].np + * @property {String} - columns[].ns - + * @property {String} - columns[].clen - + * @property {String} - columns[].dp - + * @property {String} - columns[].cop - + * @property {String} - columns[].pk - + * @property {String} - columns[].nrqd - + * @property {String} - columns[].not_nullable - + * @property {String} - columns[].ct - + * @property {String} - columns[].un - + * @property {String} - columns[].ai - + * @property {String} - columns[].unique - + * @property {String} - columns[].cdf - + * @property {String} - columns[].cc - + * @property {String} - columns[].csn - + */ + + async columnList(args: any = {}) { + const _func = this.columnList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.database; + const response = await this.sqlClient.raw( + `select + c.table_name as tn, c.column_name as cn, c.data_type as dt, + (CASE WHEN trg.trigger_name is NULL THEN false else true end) as au, + pk.constraint_type as ck, + c.character_maximum_length as clen, + c.numeric_precision as np, + c.numeric_scale as ns, + c.datetime_precision as dp, + c.ordinal_position as cop, + c.is_nullable as nrqd, + c.column_default as cdf, + c.generation_expression, + c.character_octet_length, + c.character_set_name as csn, + -- c.collation_name as clnn, + pk.ordinal_position as pk_ordinal_position, pk.constraint_name as pk_constraint_name, + c.udt_name, + + (SELECT count(*) + FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc1 + inner join INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE cu + on cu.CONSTRAINT_NAME = tc1.CONSTRAINT_NAME + where + tc1.CONSTRAINT_TYPE = 'UNIQUE' + and tc1.TABLE_NAME = c.TABLE_NAME + and cu.COLUMN_NAME = c.COLUMN_NAME + and tc1.TABLE_SCHEMA=c.TABLE_SCHEMA) IsUnique, + (SELECT + string_agg(enumlabel, ',') + FROM "pg_enum" "e" + INNER JOIN "pg_type" "t" ON "t"."oid" = "e"."enumtypid" + INNER JOIN "pg_namespace" "n" ON "n"."oid" = "t"."typnamespace" + WHERE "n"."nspname" = table_schema AND "t"."typname"=udt_name + ) enum_values + + + from information_schema.columns c + left join + ( select kc.constraint_name, kc.table_name,kc.column_name, kc.ordinal_position,tc.constraint_type + from information_schema.key_column_usage kc + inner join information_schema.table_constraints as tc + on kc.constraint_name = tc.constraint_name + and kc.constraint_schema=tc.constraint_schema and tc.constraint_type in ('PRIMARY KEY') + where kc.table_catalog='${args.databaseName}' and kc.table_schema= ? + order by table_name,ordinal_position ) pk + on + pk.table_name = c.table_name and pk.column_name=c.column_name + left join information_schema.triggers trg on trg.event_object_table = c.table_name and trg.trigger_name = CONCAT('xc_trigger_' , '${args.tn}' , '_' , c.column_name) + where c.table_catalog='${args.databaseName}' and c.table_schema=? and c.table_name='${args.tn}' + order by c.table_name, c.ordinal_position`, + [this.schema, this.schema] + ); + + const columns = []; + + for (let i = 0; i < response.rows.length; ++i) { + const column: any = {}; + + column.tn = response.rows[i].tn; + column.cn = response.rows[i].cn; + column.cno = response.rows[i].cn; + column.dt = response.rows[i].dt; + column.np = response.rows[i].np; + column.ns = response.rows[i].ns; + column.clen = response.rows[i].clen; + column.dp = response.rows[i].dp; + column.cop = response.rows[i].cop; + + // todo : there are lot of types in pg - handle them + //column.dtx = this.getKnexDataType(column.dt); + column.dtx = response.rows[i].dt; + column.pk = response.rows[i].pk_constraint_name !== null; + + column.nrqd = response.rows[i].nrqd !== 'NO'; + column.not_nullable = !column.nrqd; + column.rqd = !column.nrqd; + + // todo: there is no type of unsigned in postgres + response.rows[i].ct = response.rows[i].dt || ''; + column.un = response.rows[i].ct.indexOf('unsigned') !== -1; + + column.ai = false; + if (response.rows[i].cdf) { + column.ai = response.rows[i].cdf.indexOf('nextval') !== -1; + } + + // todo : need to find if column is unique or not + // column['unique'] = response.rows[i]['cst'].indexOf('UNIQUE') === -1 ? false : true; + + column.cdf = response.rows[i].cdf; + // ? response.rows[i].cdf.split("::")[0].replace(/'/g, "") + // : response.rows[i].cdf; + + // todo : need to find column comment + column.cc = response.rows[i].cc; + + column.csn = response.rows[i].csn; + column.dtxp = + response.rows[i].clen || response.rows[i].np || response.rows[i].dp; + column.dtxs = response.rows[i].ns; + column.au = response.rows[i].au; + column.data_type_custom = response.rows[i].udt_name; + if (column.dt === 'USER-DEFINED') { + column.dtxp = response.rows[i].enum_values; + } + + columns.push(column); + } + + result.data.list = columns; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - indexes + * @property {String} - indexes[].table - + * @property {String} - indexes[].cn - + * @property {String} - indexes[].key_name - + * @property {String} - indexes[].non_unique - + * @property {String} - indexes[].seq_in_index - + * @property {String} - indexes[].collation - + * @property {String} - indexes[].cardinality - + * @property {String} - indexes[].sub_part - + * @property {String} - indexes[].packed - + * @property {String} - indexes[].null - + * @property {String} - indexes[].index_type - + * @property {String} - indexes[].comment - + * @property {String} - indexes[].index_comment - + * @property {String} - indexes[].cstn - + * @property {String} - indexes[].cst - c = check constraint, f = foreign key constraint, p = primary key constraint, u = unique constraint, t = constraint trigger, x = exclusion constraint + */ + async indexList(args: any = {}) { + const _func = this.indexList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + const { rows } = await this.sqlClient.raw( + `SELECT + f.attname AS cn, + i.relname as key_name, + ix.indnatts, ix.indkey, f.attnum as seq_in_index, + pg_catalog.format_type(f.atttypid,f.atttypmod) AS type, + f.attnotnull as rqd, + p.contype as cst, + p.conname as cstn, + CASE + WHEN i.oid<>0 THEN true + ELSE false + END AS is_index, + CASE + WHEN p.contype = 'p' THEN true + ELSE false + END AS primarykey, + CASE + WHEN p.contype = 'u' THEN 0 + WHEN p.contype = 'p' THEN 0 + ELSE 1 + END AS non_unique_original, + CASE + WHEN p.contype = 'p' THEN true + ELSE false + END AS primarykey, + CASE + WHEN p.contype = 'u' THEN 0 + WHEN p.contype = 'p' THEN 0 + ELSE 1 + END AS non_unique, + CASE + WHEN f.atthasdef = 't' THEN pg_get_expr(d.adbin, d.adrelid) + END AS default FROM pg_attribute f + JOIN pg_class c ON c.oid = f.attrelid + JOIN pg_type t ON t.oid = f.atttypid + LEFT JOIN pg_attrdef d ON d.adrelid = c.oid AND d.adnum = f.attnum + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace + LEFT JOIN pg_constraint p ON p.conrelid = c.oid AND f.attnum = ANY (p.conkey) + LEFT JOIN pg_class AS g ON p.confrelid = g.oid + LEFT JOIN pg_index AS ix ON f.attnum = ANY(ix.indkey) and c.oid = f.attrelid and c.oid = ix.indrelid + LEFT JOIN pg_class AS i ON ix.indexrelid = i.oid + WHERE + c.relkind = 'r'::char + AND n.nspname = ? + AND c.relname = ? + and i.oid<>0 + AND f.attnum > 0 + ORDER BY i.relname, f.attnum;`, + [this.schema, args.tn] + ); + result.data.list = rows; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - indexes + * @property {String} - indexes[].cstn - + * @property {String} - indexes[].cn - + * @property {String} - indexes[].op - + * @property {String} - indexes[].puc - + * @property {String} - indexes[].cst - + */ + async constraintList(args: any = {}) { + const _func = this.constraintList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + const response = await this.sqlClient.raw(` + SELECT c.conname AS cstn, + CASE + WHEN c.contype = 'u' THEN 'UNIQUE' + WHEN c.contype = 'p' THEN 'PRIMARY KEY' + ELSE 'FOREIGN KEY' + END AS cst, + col.attnum, + sch.nspname AS "schema", + tbl.relname AS "table", + ARRAY_AGG(col.attname ORDER BY u.attposition) AS columns, + pg_get_constraintdef(c.oid) AS definition + FROM pg_constraint c + JOIN LATERAL UNNEST(c.conkey) WITH ORDINALITY AS u(attnum, attposition) ON TRUE + JOIN pg_class tbl ON tbl.oid = c.conrelid + JOIN pg_namespace sch ON sch.oid = tbl.relnamespace + JOIN pg_attribute col ON (col.attrelid = tbl.oid AND col.attnum = u.attnum) + where tbl.relname='${args.tn}' + GROUP BY constraint_name, col.attnum, constraint_type, "schema", "table", definition + ORDER BY "schema", "table"; `); + + const rows = []; + for (let i = 0, rowCount = 0; i < response.rows.length; ++i, ++rowCount) { + response.rows[i].columns = response.rows[i].columns.replace('{', ''); + response.rows[i].columns = response.rows[i].columns.replace('}', ''); + response.rows[i].columns = response.rows[i].columns.split(','); + + if (response.rows[i].columns.length === 1) { + rows[rowCount] = response.rows[i]; + rows[rowCount].columns = response.rows[i].columns[0]; + rows[rowCount].seq_in_index = 1; + } else { + const cols = response.rows[i].columns.slice(); + for (let j = 0; j < cols.length; ++j, ++rowCount) { + rows[rowCount] = JSON.parse(JSON.stringify(response.rows[i])); + rows[rowCount].columns = cols[j]; + rows[rowCount].seq_in_index = j; + } + rowCount--; + } + } + + result.data.list = rows; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - relations + * @property {String} - relations[].tn + * @property {String} - relations[].cstn - + * @property {String} - relations[].tn - + * @property {String} - relations[].cn - + * @property {String} - relations[].rtn - + * @property {String} - relations[].rcn - + * @property {String} - relations[].puc - + * @property {String} - relations[].ur - + * @property {String} - relations[].dr - + * @property {String} - relations[].mo - + */ + async relationList(args: any = {}) { + const _func = this.relationList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + const { rows } = await this.sqlClient.raw( + `SELECT distinct + tc.table_schema as ts, + tc.constraint_name as cstn, + tc.table_name as tn, + kcu.column_name as cn, + ccu.table_schema AS foreign_table_schema, + ccu.table_name AS rtn, + ccu.column_name AS rcn, + pc.confupdtype as ur, pc.confdeltype as dr + FROM + information_schema.table_constraints AS tc + JOIN information_schema.key_column_usage AS kcu + ON tc.constraint_name = kcu.constraint_name + AND tc.table_schema = kcu.table_schema + JOIN information_schema.constraint_column_usage AS ccu + ON ccu.constraint_name = tc.constraint_name + AND ccu.table_schema = tc.table_schema + join (select conname,confupdtype,confdeltype from pg_catalog.pg_constraint) pc + on pc.conname = tc.constraint_name + WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_schema=? and tc.table_name='${args.tn}' + order by tc.table_name;`, + [this.schema] + ); + + const ruleMapping = { + a: 'NO ACTION', + c: 'CASCADE', + r: 'RESTRICT', + n: 'SET NULL', + d: 'SET DEFAULT' + }; + + for (const row of rows) { + row.ur = ruleMapping[row.ur]; + row.dr = ruleMapping[row.dr]; + } + + result.data.list = rows; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - relations + * @property {String} - relations[].tn + * @property {String} - relations[].cstn - + * @property {String} - relations[].tn - + * @property {String} - relations[].cn - + * @property {String} - relations[].rtn - + * @property {String} - relations[].rcn - + * @property {String} - relations[].puc - + * @property {String} - relations[].ur - + * @property {String} - relations[].dr - + * @property {String} - relations[].mo - + */ + async relationListAll(args: any = {}) { + const _func = this.relationList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + const { rows } = await this.sqlClient.raw( + `SELECT DISTINCT tc.table_schema as ts, + tc.constraint_name as cstn, + tc.table_name as tn, + kcu.column_name as cn, + ccu.table_schema AS foreign_table_schema, + ccu.table_name AS rtn, + ccu.column_name AS rcn, + pc.confupdtype as ur, + pc.confdeltype as dr + FROM information_schema.table_constraints AS tc + JOIN information_schema.key_column_usage AS kcu + ON tc.constraint_name = kcu.constraint_name + AND tc.table_schema = kcu.table_schema + JOIN information_schema.constraint_column_usage AS ccu + ON ccu.constraint_name = tc.constraint_name + AND ccu.table_schema = tc.table_schema + join (select conname, confupdtype, confdeltype + from pg_catalog.pg_constraint) pc + on pc.conname = tc.constraint_name + WHERE tc.constraint_type = 'FOREIGN KEY' + AND tc.table_schema = ? + order by tc.table_name;`, + [this.schema] + ); + + const ruleMapping = { + a: 'NO ACTION', + c: 'CASCADE', + r: 'RESTRICT', + n: 'SET NULL', + d: 'SET DEFAULT' + }; + + for (const row of rows) { + row.ur = ruleMapping[row.ur]; + row.dr = ruleMapping[row.dr]; + } + + result.data.list = rows; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - triggers + * @property {String} - triggers[].trigger + * @property {String} - triggers[].event + * @property {String} - triggers[].table + * @property {String} - triggers[].statement + * @property {String} - triggers[].timing + * @property {String} - triggers[].created + * @property {String} - triggers[].sql_mode + * @property {String} - triggers[].definer + * @property {String} - triggers[].character_set_client + * @property {String} - triggers[].collation_connection + * @property {String} - triggers[].database collation + */ + async triggerList(args: any = {}) { + const _func = this.triggerList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.database; + + const { + rows + } = await this.sqlClient.raw( + `select * from information_schema.triggers where trigger_schema=? and event_object_table='${args.tn}'`, + [this.schema] + ); + + for (let i = 0; i < rows.length; ++i) { + rows[i].statement = rows[i].action_statement; + rows[i].table = rows[i].event_object_table; + rows[i].trigger = rows[i].trigger_name; + rows[i].event = rows[i].event_manipulation; + rows[i].timing = rows[i].action_timing; + } + + result.data.list = rows; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @returns {Object[]} - functions + * @property {String} - functions[].function_name + * @property {String} - functions[].type + * @property {String} - functions[].definer + * @property {String} - functions[].modified + * @property {String} - functions[].created + * @property {String} - functions[].security_type + * @property {String} - functions[].comment + * @property {String} - functions[].character_set_client + * @property {String} - functions[].collation_connection + * @property {String} - functions[].database collation + */ + async functionList(args: any = {}) { + const _func = this.functionList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.database; + + const { rows } = await this.raw( + `SELECT * + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_proc p + ON pronamespace = n.oid + WHERE nspname = ?;`, + [this.schema] + ); + const functionRows = []; + for (let i = 0; i < rows.length; ++i) { + if (!('prokind' in rows[i]) || rows[i].prokind !== 'p') + functionRows.push({ + create_function: rows[i].prosrc, + function_name: rows[i].proname + }); + } + + result.data.list = functionRows; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * @todo Remove the function - pg doesn't support procedure + * + * @param {Object} - args - For future reasons + * @returns {Object[]} - procedures + * @property {String} - procedures[].procedure_name + * @property {String} - procedures[].type + * @property {String} - procedures[].definer + * @property {String} - procedures[].modified + * @property {String} - procedures[].created + * @property {String} - procedures[].security_type + * @property {String} - procedures[].comment + * @property {String} - procedures[].definer + * @property {String} - procedures[].character_set_client + * @property {String} - procedures[].collation_connection + * @property {String} - procedures[].database collation + */ + async procedureList(args: any = {}) { + const _func = this.procedureList.name; + const result = new Result(); + result.data.list = []; + log.api(`${_func}:args:`, args); + + // todo: update query - https://dataedo.com/kb/query/postgresql/list-stored-procedures + try { + args.databaseName = this.connectionConfig.connection.database; + + const { rows } = await this.raw( + `SELECT * + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_proc p + ON pronamespace = n.oid + WHERE nspname = ?;`, + [this.schema] + ); + const procedureRows = []; + for (let i = 0; i < rows.length; ++i) { + if ('prokind' in rows[i] && rows[i].prokind === 'p') + procedureRows.push({ + create_procedure: rows[i].prosrc, + procedure_name: rows[i].proname + }); + } + + result.data.list = procedureRows; + } catch (e) { + // todo: enable log + // log.ppe(e, _func); + // throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @returns {Object[]} - views + * @property {String} - views[].sql_mode + * @property {String} - views[].create_function + * @property {String} - views[].database collation + * @property {String} - views[].collation_connection + * @property {String} - views[].character_set_client + */ + async viewList(args: any = {}) { + const _func = this.viewList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + const { rows } = await this.sqlClient.raw( + `select * + from INFORMATION_SCHEMA.views + WHERE table_schema = ANY (current_schemas(false));` + ); + + for (let i = 0; i < rows.length; ++i) { + rows[i].view_name = rows[i].tn || rows[i].table_name; + // rows[i].view_definition = rows[i].view_definition; + } + + result.data.list = rows; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.function_name - + * @returns {Object[]} - functions + * @property {String} - create_function + * @property {String} - function_declaration + */ + async functionRead(args: any = {}) { + const _func = this.functionRead.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.database; + + const { rows } = await this.sqlClient.raw( + `SELECT format('%I.%I(%s)', ns.nspname, p.proname, oidvectortypes(p.proargtypes)) as function_declaration, pg_get_functiondef(p.oid) as create_function + FROM pg_proc p INNER JOIN pg_namespace ns ON (p.pronamespace = ns.oid) + WHERE ns.nspname = ? and p.proname = '${args.function_name}';`, + [this.schema] + ); + + // log.debug(response); + + for (let i = 0; i < rows.length; ++i) { + rows[i].function_name = args.function_name; + } + + result.data.list = rows; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * @todo Remove the function - pg doesn't support procedure + * + * @param {Object} - args - Input arguments + * @param {Object} - args.procedure_name - + * @returns {Object[]} - functions + * @property {String} - sql_mode + * @property {String} - create_function + * @property {String} - database collation + * @property {String} - collation_connection + * @property {String} - character_set_client + */ + async procedureRead(args: any = {}) { + const _func = this.procedureRead.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + `show create procedure ${args.procedure_name};` + ); + const rows = []; + + if (response.length === 2) { + for (let i = 0; i < response[0].length; ++i) { + let procedure = response[0][i]; + procedure = _.mapKeys(procedure, (_v, k) => k.toLowerCase()); + procedure.create_procedure = procedure['create procedure']; + rows.push(procedure); + } + } else log.debug('Unknown response for tableList:', response); + + result.data.list = rows; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.view_name - + * @returns {Object[]} - views + * @property {String} - views[].tn + */ + async viewRead(args: any = {}) { + const _func = this.viewRead.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.database; + + const { rows } = await this.sqlClient.raw( + `select * from INFORMATION_SCHEMA.views WHERE table_name='${args.view_name}' and table_schema = ANY (current_schemas(false));` + ); + + for (let i = 0; i < rows.length; ++i) { + rows[i].view_name = rows[i].tn; + // rows[i].view_definition = rows[i].view_definition; + } + + result.data.list = rows; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async triggerRead(args: any = {}) { + const _func = this.triggerRead.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + `SHOW FULL TABLES IN ${args.databaseName} WHERE TABLE_TYPE LIKE 'VIEW';` + ); + let rows = []; + + if (response.length === 2) { + const views = []; + + for (let i = 0; i < response[0].length; ++i) { + const view = response[0][i]; + view.view_name = view[`Tables_in_${args.databaseName}`]; + views.push(view); + } + + rows = views; + } else log.debug('Unknown response for tableList:', response); + + result.data.list = rows; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async schemaCreate(args: any = {}) { + const _func = this.schemaCreate.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + await this.sqlClient.raw(`create database ${args.database_name}`); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async schemaDelete(args: any = {}) { + const _func = this.schemaDelete.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + await this.sqlClient.raw(`drop database ${args.database_name}`); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async triggerDelete(args: any = {}) { + const _func = this.triggerDelete.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + const upQuery = this.genQuery(`DROP TRIGGER IF EXISTS ?? ON ??`, [ + args.trigger_name, + args.tn + ]); + await this.sqlClient.raw(upQuery); + result.data.object = { + upStatement: [{ sql: this.querySeparator() + upQuery }], + downStatement: [ + { + sql: + this.querySeparator() + + this.genQuery( + `CREATE TRIGGER ?? \n${args.timing} ${args.event}\nON ?? FOR EACH ROW\n${args.statement}`, + [args.trigger_name, args.tn] + ) + } + ] + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.function_name + * @param {String} - args.function_declaration + * @param {String} - args.create_function + * @returns {Object[]} - result rows + */ + async functionDelete(args: any = {}) { + const _func = this.functionDelete.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + const upQuery = + this.querySeparator() + + `DROP FUNCTION IF EXISTS ${args.function_declaration}`; + const downQuery = this.querySeparator() + args.create_function; + try { + await this.sqlClient.raw(upQuery); + result.data.object = { + upStatement: [{ sql: upQuery }], + downStatement: [{ sql: downQuery }] + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + // @todo Remove the function - pg doesn't support procedure + async procedureDelete(args: any = {}) { + const _func = this.procedureDelete.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + await this.sqlClient.raw( + `DROP PROCEDURE IF EXISTS ${args.procedure_name}` + ); + } catch (e) { + log.ppe(e, _func); + throw e; + } + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} args + * @param {String} func : function name + * @returns {Result} + * @returns {Object} - Result.data + * @returns {String} - Result.data.value - sql query + */ + async _getQuery(args) { + try { + if (_.isEmpty(this._version)) { + const result = await this.version(); + this._version = result.data.object; + log.debug( + `Version was empty for ${args.func}: population version for database as`, + this._version + ); + } + + // log.debug(this._version, args); + + if (this._version.key in this.queries[args.func]) { + return this.queries[args.func][this._version.key].sql; + } + return this.queries[args.func].default.sql; + } catch (error) { + log.ppe(error, this._getQuery.name); + throw error; + } + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.function_name + * @param {String} - args.create_function + * @returns {Object[]} - result rows + */ + async functionCreate(args: any = {}) { + const func = this.functionCreate.name; + const result = new Result(); + + log.api(`${func}:args:`, args); + + try { + const upQuery = this.querySeparator() + args.create_function; + + await this.sqlClient.raw(upQuery); + + const functionCreated = await this.functionRead({ + function_name: args.function_name + }); + + const downQuery = + this.querySeparator() + + `DROP FUNCTION IF EXISTS ${functionCreated.data.list[0].function_declaration}`; + + result.data.object = { + upStatement: [{ sql: upQuery }], + downStatement: [{ sql: downQuery }] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.function_name + * @param {String} - args.event + * @param {String} - args.timing + * @returns {Object[]} - result rows + */ + async functionUpdate(args: any = {}) { + const func = this.functionUpdate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const upQuery = this.querySeparator() + args.create_function; + let downQuery = this.querySeparator() + args.oldCreateFunction; + + await this.sqlClient.raw( + `DROP FUNCTION IF EXISTS ${args.function_declaration};` + ); + await this.sqlClient.raw(upQuery); + + const functionCreated = await this.functionRead({ + function_name: args.function_name + }); + + downQuery = + `DROP FUNCTION IF EXISTS ${functionCreated.data.list[0].function_declaration};` + + downQuery; + + result.data.object = { + upStatement: [{ sql: upQuery }], + downStatement: [{ sql: downQuery }] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * @todo Remove the function - pg doesn't support procedure + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.procedure_name + * @param {String} - args.event + * @param {String} - args.timing + * @returns {Object[]} - result rows + * + */ + async procedureCreate(args: any = {}) { + const func = this.procedureCreate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const upQuery = + this.querySeparator() + + `CREATE TRIGGER \`${args.procedure_name}\` \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}`; + await this.sqlClient.raw(upQuery); + const downQuery = + this.querySeparator() + + `DROP PROCEDURE IF EXISTS ${args.procedure_name}`; + result.data.object = { + upStatement: [{ sql: upQuery }], + downStatement: [{ sql: downQuery }] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * @todo Remove the function - pg doesn't support procedure + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.procedure_name + * @param {String} - args.event + * @param {String} - args.timing + * @returns {Object[]} - result rows + */ + async procedureUpdate(args: any = {}) { + const func = this.procedureUpdate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const query = + this.querySeparator() + `DROP TRIGGER ${args.procedure_name}`; + const upQuery = + this.querySeparator() + + `CREATE TRIGGER \`${args.procedure_name}\` \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}`; + + await this.sqlClient.raw(query); + await this.sqlClient.raw(upQuery); + + result.data.object = { + upStatement: [{ sql: upQuery }], + downStatement: [{ sql: ';' }] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.trigger_name + * @param {String} - args.event + * @param {String} - args.timing + * @returns {Object[]} - result rows + */ + async triggerCreate(args: any = {}) { + const func = this.triggerCreate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const upQuery = + this.querySeparator() + + `CREATE TRIGGER ${args.trigger_name} \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}`; + await this.sqlClient.raw(upQuery); + result.data.object = { + upStatement: [{ sql: upQuery }], + downStatement: [ + { sql: this.querySeparator() + `DROP TRIGGER ${args.trigger_name}` } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.trigger_name + * @param {String} - args.event + * @param {String} - args.timing + * @param {String} - args.oldStatement + * @returns {Object[]} - result rows + */ + async triggerUpdate(args: any = {}) { + const func = this.triggerUpdate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + await this.sqlClient.raw( + `DROP TRIGGER ${args.trigger_name} ON ${args.tn}` + ); + await this.sqlClient.raw( + `CREATE TRIGGER ${args.trigger_name} \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}` + ); + + result.data.object = { + upStatement: + this.querySeparator() + + `DROP TRIGGER ${args.trigger_name} ON ${ + args.tn + };${this.querySeparator()}CREATE TRIGGER ${args.trigger_name} \n${ + args.timing + } ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}`, + downStatement: + this.querySeparator() + + `CREATE TRIGGER ${args.trigger_name} \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.oldStatement}` + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.view_name + * @param {String} - args.view_definition + * @returns {Object} - up and down statements + */ + async viewCreate(args: any = {}) { + const func = this.viewCreate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const query = args.view_definition; + + await this.sqlClient.raw(query); + result.data.object = { + upStatement: [{ sql: this.querySeparator() + query }], + downStatement: [ + { sql: this.querySeparator() + `DROP VIEW "${args.view_name}"` } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.view_name + * @param {String} - args.view_definition + * @param {String} - args.oldViewDefination + * @returns {Object} - up and down statements + */ + async viewUpdate(args: any = {}) { + const func = this.viewUpdate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const query = `CREATE OR REPLACE VIEW "${args.view_name}" AS \n${args.view_definition}`; + + await this.sqlClient.raw(query); + result.data.object = { + upStatement: this.querySeparator() + query, + downStatement: + this.querySeparator() + + `CREATE VIEW "${args.view_name}" AS \n${args.oldViewDefination}` + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.view_name + * @param {String} - args.view_definition + * @param {String} - args.oldViewDefination + * @returns {Object} - up and down statements + */ + async viewDelete(args: any = {}) { + const func = this.viewDelete.name; + const result = new Result(); + log.api(`${func}:args:`, args); + // `DROP TRIGGER ${args.view_name}` + try { + const query = `DROP VIEW ${args.view_name}`; + + await this.sqlClient.raw(query); + + result.data.object = { + upStatement: [{ sql: this.querySeparator() + query }], + downStatement: [ + { + sql: + this.querySeparator() + + `CREATE VIEW "${args.view_name}" AS \n${args.oldViewDefination}` + } + ] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args + * @param {String} - args.tn + * @param {Object[]} - args.columns + * @param {String} - args.columns[].tn + * @param {String} - args.columns[].cn + * @param {String} - args.columns[].dt + * @param {String} - args.columns[].np + * @param {String} - args.columns[].ns - + * @param {String} - args.columns[].clen - + * @param {String} - args.columns[].dp - + * @param {String} - args.columns[].cop - + * @param {String} - args.columns[].pk - + * @param {String} - args.columns[].nrqd - + * @param {String} - args.columns[].not_nullable - + * @param {String} - args.columns[].ct - + * @param {String} - args.columns[].un - + * @param {String} - args.columns[].ai - + * @param {String} - args.columns[].unique - + * @param {String} - args.columns[].cdf - + * @param {String} - args.columns[].cc - + * @param {String} - args.columns[].csn - + * @param {String} - args.columns[].dtx + * - value will be 'specificType' for all cols except ai + * - for ai it will be integer, bigInteger + * - tiny, small and medium Int auto increement is not supported + * @param {String} - args.columns[].dtxp - to use in UI + * @param {String} - args.columns[].dtxs - to use in UI + * @returns {Promise<{upStatement, downStatement}>} + */ + async tableCreate(args) { + const _func = this.tableCreate.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + args.table = args.tn; + args.sqlClient = this.sqlClient; + + /**************** create table ****************/ + const upQuery = this.querySeparator() + this.createTable(args.tn, args); + await this.sqlClient.raw(upQuery); + + const downStatement = + this.querySeparator() + + this.sqlClient.schema.dropTable(args.table).toString(); + + this.emit(`Success : ${upQuery}`); + + const triggerStatements = await this.afterTableCreate(args); + + /**************** return files *************** */ + result.data.object = { + upStatement: [{ sql: upQuery }, ...triggerStatements.upStatement], + downStatement: [ + ...triggerStatements.downStatement, + { sql: downStatement } + ] + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + async afterTableCreate(args) { + const result = { upStatement: [], downStatement: [] }; + let upQuery = ''; + let downQuery = ''; + + for (let i = 0; i < args.columns.length; i++) { + const column = args.columns[i]; + if (column.au) { + const triggerFnName = `xc_au_${args.tn}_${column.cn}`; + const triggerName = `xc_trigger_${args.tn}_${column.cn}`; + + const triggerFnQuery = this.genQuery( + `CREATE OR REPLACE FUNCTION ??() + RETURNS TRIGGER AS $$ + BEGIN + NEW.?? = NOW(); + RETURN NEW; + END; + $$ LANGUAGE plpgsql;`, + [triggerFnName, column.cn] + ); + + upQuery += + this.querySeparator() + + triggerFnQuery + + this.querySeparator() + + this.genQuery( + `CREATE TRIGGER ?? + BEFORE UPDATE ON ?? + FOR EACH ROW + EXECUTE PROCEDURE ??();`, + [triggerName, args.tn, triggerFnName] + ); + + downQuery += + this.querySeparator() + + this.genQuery(`DROP TRIGGER IF EXISTS ?? ON ??;`, [ + triggerName, + args.tn + ]) + + this.querySeparator() + + this.genQuery(`DROP FUNCTION IF EXISTS ??()`, [triggerFnName]); + } + } + await this.sqlClient.raw(upQuery); + result.upStatement[0] = { sql: upQuery }; + result.downStatement[0] = { sql: downQuery }; + + return result; + } + + async afterTableUpdate(args) { + const result = { upStatement: [], downStatement: [] }; + let upQuery = ''; + let downQuery = ''; + + for (let i = 0; i < args.columns.length; i++) { + const column = args.columns[i]; + if (column.au && column.altered === 1) { + const triggerFnName = `xc_au_${args.tn}_${column.cn}`; + const triggerName = `xc_trigger_${args.tn}_${column.cn}`; + + const triggerFnQuery = this.genQuery( + `CREATE OR REPLACE FUNCTION ??() + RETURNS TRIGGER AS $$ + BEGIN + NEW.?? = NOW(); + RETURN NEW; + END; + $$ LANGUAGE plpgsql;`, + [triggerFnName, column.cn] + ); + + upQuery += + this.querySeparator() + + triggerFnQuery + + this.querySeparator() + + this.genQuery( + `CREATE TRIGGER ?? + BEFORE UPDATE ON ?? + FOR EACH ROW + EXECUTE PROCEDURE ??();`, + [triggerName, args.tn, triggerFnName] + ); + + downQuery += + this.querySeparator() + + this.genQuery(`DROP TRIGGER IF EXISTS ?? ON ??;`, [ + triggerName, + args.tn + ]) + + this.querySeparator() + + this.genQuery(`DROP FUNCTION IF EXISTS ??()`, [triggerFnName]); + } + } + await this.sqlClient.raw(upQuery); + result.upStatement[0] = { sql: upQuery }; + result.downStatement[0] = { sql: downQuery }; + + return result; + } + + /** + * + * @param {Object} - args + * @param {String} - args.table + * @param {String} - args.table + * @param {Object[]} - args.columns + * @param {String} - args.columns[].tn + * @param {String} - args.columns[].cn + * @param {String} - args.columns[].dt + * @param {String} - args.columns[].np + * @param {String} - args.columns[].ns - + * @param {String} - args.columns[].clen - + * @param {String} - args.columns[].dp - + * @param {String} - args.columns[].cop - + * @param {String} - args.columns[].pk - + * @param {String} - args.columns[].nrqd - + * @param {String} - args.columns[].not_nullable - + * @param {String} - args.columns[].ct - + * @param {String} - args.columns[].un - + * @param {String} - args.columns[].ai - + * @param {String} - args.columns[].unique - + * @param {String} - args.columns[].cdf - + * @param {String} - args.columns[].cc - + * @param {String} - args.columns[].csn - + * @param {Number} - args.columns[].altered - 1,2,4 = addition,edited,deleted + * @returns {Promise<{upStatement, downStatement}>} + */ + async tableUpdate(args) { + const _func = this.tableUpdate.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + args.table = args.tn; + const originalColumns = args.originalColumns; + args.connectionConfig = this._connectionConfig; + args.sqlClient = this.sqlClient; + + let upQuery = ''; + let downQuery = ''; + + for (let i = 0; i < args.columns.length; ++i) { + const oldColumn = lodash.find(originalColumns, { + cn: args.columns[i].cno + }); + + if (args.columns[i].altered & 4) { + // col remove + upQuery += this.alterTableRemoveColumn( + args.table, + args.columns[i], + oldColumn, + upQuery + ); + downQuery += this.alterTableAddColumn( + args.table, + oldColumn, + args.columns[i], + downQuery + ); + } else if (args.columns[i].altered & 2 || args.columns[i].altered & 8) { + // col edit + upQuery += this.alterTableChangeColumn( + args.table, + args.columns[i], + oldColumn, + upQuery + ); + downQuery += this.alterTableChangeColumn( + args.table, + oldColumn, + args.columns[i], + downQuery + ); + } else if (args.columns[i].altered & 1) { + // col addition + upQuery += this.alterTableAddColumn( + args.table, + args.columns[i], + oldColumn, + upQuery + ); + downQuery += this.alterTableRemoveColumn( + args.table, + args.columns[i], + oldColumn, + downQuery + ); + } + } + + upQuery += + (upQuery ? ';' : '') + + this.alterTablePK( + args.table, + args.columns, + args.originalColumns, + upQuery + ); + downQuery += + (downQuery ? ';' : '') + + this.alterTablePK( + args.table, + args.originalColumns, + args.columns, + downQuery + ); + + if (upQuery) { + //upQuery = `ALTER TABLE "${args.columns[0].tn}" ${upQuery};`; + //downQuery = `ALTER TABLE "${args.columns[0].tn}" ${downQuery};`; + } + + await this.sqlClient.raw(upQuery); + + // console.log(upQuery); + + const afterUpdate = await this.afterTableUpdate(args); + + result.data.object = { + upStatement: [ + { sql: this.querySeparator() + upQuery }, + ...afterUpdate.upStatement + ], + downStatement: [ + ...afterUpdate.downStatement, + { sql: this.querySeparator() + downQuery } + ] + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param {Object} - args + * @param args.tn + * @returns {Promise<{upStatement, downStatement}>} + */ + async tableDelete(args) { + const _func = this.tableDelete.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + // const { columns } = args; + args.sqlClient = this.sqlClient; + + /** ************** create up & down statements *************** */ + const upStatement = + this.querySeparator() + + this.sqlClient.schema.dropTable(args.tn).toString(); + let downQuery = this.createTable(args.tn, args); + + /** + + columnList + relationList + indexesList + createAggregatedIndexes + filterOutPkAndFk + + downQuery + create table - via columnList - we are doing this + + create fks - via relationList + + create indexes - slightly tricky + + */ + + let relationsList: any = await this.relationList(args); + + relationsList = relationsList.data.list; + + for (const relation of relationsList) { + downQuery += + this.querySeparator() + + (await this.sqlClient.schema + .table(relation.tn, function(table) { + table = table + .foreign(relation.cn, null) + .references(relation.rcn) + .on(relation.rtn); + + if (relation.ur) { + table = table.onUpdate(relation.ur); + } + if (relation.dr) { + table = table.onDelete(relation.dr); + } + }) + .toQuery()); + } + + let indexList: any = await this.indexList(args); + + indexList = indexList.data.list.filter( + ({ cst }) => cst !== 'p' && cst !== 'f' + ); + + const indexesMap: { [key: string]: any } = {}; + + for (const { key_name, non_unique, cn } of indexList) { + if (!(key_name in indexesMap)) { + indexesMap[key_name] = { + tn: args.tn, + indexName: key_name, + non_unique, + columns: [] + }; + } + indexesMap[key_name].columns.push(cn); + } + + for (const { non_unique, tn, columns, indexName } of Object.values( + indexesMap + )) { + downQuery += + this.querySeparator() + + this.sqlClient.schema + .table(tn, function(table) { + if (non_unique) { + table.index(columns, indexName); + } else { + table.unique(columns, indexName); + } + }) + .toQuery(); + } + + this.emit(`Success : ${upStatement}`); + + /** ************** drop tn *************** */ + await this.sqlClient.schema.dropTable(args.tn); + + /** ************** return files *************** */ + result.data.object = { + upStatement: [{ sql: upStatement }], + downStatement: [{ sql: this.querySeparator() + downQuery }] + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableCreateStatement(args) { + const _func = this.tableCreateStatement.name; + let result = new Result(); + log.api(`${_func}:args:`, args); + try { + result = await this.columnList(args); + const upQuery = this.createTable(args.tn, { + tn: args.tn, + columns: result.data.list + }); + result.data = upQuery; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableInsertStatement(args) { + const _func = this.tableCreateStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + result.data = `INSERT INTO \`${args.tn}\` (`; + let values = ' VALUES ('; + const response = await this.columnList(args); + if (response.data && response.data.list) { + for (let i = 0; i < response.data.list.length; ++i) { + if (!i) { + result.data += `\n"${response.data.list[i].cn}"\n\t`; + values += `\n<${response.data.list[i].cn}>\n\t`; + } else { + result.data += `, \`"${response.data.list[i].cn}"\`\n\t`; + values += `, <${response.data.list[i].cn}>\n\t`; + } + } + } + + result.data += `)`; + values += `);`; + result.data += values; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableUpdateStatement(args) { + const _func = this.tableUpdateStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + result.data = `UPDATE "${args.tn}" \nSET\n`; + const response = await this.columnList(args); + if (response.data && response.data.list) { + for (let i = 0; i < response.data.list.length; ++i) { + if (!i) { + result.data += `"${response.data.list[i].cn}" = <\`${response.data.list[i].cn}\`>\n\t`; + } else { + result.data += `,"${response.data.list[i].cn}" = <\`${response.data.list[i].cn}\`>\n\t`; + } + } + } + + result.data += ';'; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableDeleteStatement(args) { + const _func = this.tableDeleteStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + result.data = `DELETE FROM "${args.tn}" where ;`; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableTruncateStatement(args) { + const _func = this.tableTruncateStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + result.data = `TRUNCATE TABLE "${args.tn}";`; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableSelectStatement(args) { + const _func = this.tableSelectStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + result.data = `SELECT `; + const response = await this.columnList(args); + if (response.data && response.data.list) { + for (let i = 0; i < response.data.list.length; ++i) { + if (!i) { + result.data += `"${response.data.list[i].cn}"\n\t`; + } else { + result.data += `, "${response.data.list[i].cn}"\n\t`; + } + } + } + + result.data += ` FROM "${args.tn}";`; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + alterTablePK(t, n, o, _existingQuery, createTable = false) { + const numOfPksInOriginal = []; + const numOfPksInNew = []; + let pksChanged = 0; + + for (let i = 0; i < n.length; ++i) { + if (n[i].pk) { + if (n[i].altered !== 4) numOfPksInNew.push(n[i].cn); + } + } + + for (let i = 0; i < o.length; ++i) { + if (o[i].pk) { + numOfPksInOriginal.push(o[i].cn); + } + } + + if (numOfPksInNew.length === numOfPksInOriginal.length) { + for (let i = 0; i < numOfPksInNew.length; ++i) { + if (numOfPksInOriginal[i] !== numOfPksInNew[i]) { + pksChanged = 1; + break; + } + } + } else { + pksChanged = numOfPksInNew.length - numOfPksInOriginal.length; + } + + let query = ''; + if (!numOfPksInNew.length && !numOfPksInOriginal.length) { + // do nothing + } else if (pksChanged) { + query += numOfPksInOriginal.length + ? this.genQuery(`alter TABLE ?? drop constraint IF EXISTS ??;`, [ + t, + `${t}_pkey` + ]) + : ''; + if (numOfPksInNew.length) { + if (createTable) { + query += this.genQuery(`, PRIMARY KEY(??)`, [numOfPksInNew]); + } else { + query += this.genQuery( + `alter TABLE ?? add constraint ?? PRIMARY KEY(??);`, + [t, `${t}_pkey`, numOfPksInNew] + ); + } + } + } + + return query; + } + + alterTableRemoveColumn(t, n, _o, existingQuery) { + const shouldSanitize = true; + let query = existingQuery ? ',' : ''; + query += this.genQuery( + `ALTER TABLE ?? DROP COLUMN ??`, + [t, n.cn], + shouldSanitize + ); + return query; + } + + createTableColumn(t, n, o, existingQuery) { + return this.alterTableColumn(t, n, o, existingQuery, 0); + } + + alterTableAddColumn(t, n, o, existingQuery) { + return this.alterTableColumn(t, n, o, existingQuery, 1); + } + + alterTableChangeColumn(t, n, o, existingQuery) { + return this.alterTableColumn(t, n, o, existingQuery, 2); + } + + createTable(table, args) { + let query = ''; + + for (let i = 0; i < args.columns.length; ++i) { + query += this.createTableColumn(table, args.columns[i], null, query); + } + + query += this.alterTablePK(table, args.columns, [], query, true); + + query = this.genQuery(`CREATE TABLE ?? (${query});`, [args.tn]); + + return query; + } + + alterTableColumn(t, n, o, existingQuery, change = 2) { + let query = ''; + + const defaultValue = getDefaultValue(n); + const shouldSanitize = true; + + if (change === 0) { + query = existingQuery ? ',' : ''; + if (n.ai) { + if (n.dt === 'int8' || n.dt.indexOf('bigint') > -1) { + query += this.genQuery(` ?? bigserial`, [n.cn], shouldSanitize); + } else if (n.dt === 'int2' || n.dt.indexOf('smallint') > -1) { + query += this.genQuery(` ?? smallserial`, [n.cn], shouldSanitize); + } else { + query += this.genQuery(` ?? serial`, [n.cn], shouldSanitize); + } + } else { + query += this.genQuery(` ?? ${n.dt}`, [n.cn], shouldSanitize); + query += n.rqd ? ' NOT NULL' : ' NULL'; + query += defaultValue ? ` DEFAULT ${defaultValue}` : ''; + } + } else if (change === 1) { + query += this.genQuery(` ADD ?? ${n.dt}`, [n.cn], shouldSanitize); + query += n.rqd ? ' NOT NULL' : ' NULL'; + query += defaultValue ? ` DEFAULT ${defaultValue}` : ''; + query = this.genQuery(`ALTER TABLE ?? ${query};`, [t], shouldSanitize); + } else { + if (n.cn !== o.cn) { + query += this.genQuery( + `\nALTER TABLE ?? RENAME COLUMN ?? TO ?? ;\n`, + [t, o.cn, n.cn], + shouldSanitize + ); + } + + if (n.dt !== o.dt) { + query += this.genQuery( + `\nALTER TABLE ?? ALTER COLUMN ?? TYPE ${n.dt} USING ??::${n.dt};\n`, + [t, n.cn, n.cn], + shouldSanitize + ); + } + + if (n.rqd !== o.rqd) { + query += this.genQuery( + `\nALTER TABLE ?? ALTER COLUMN ?? `, + [t, n.cn], + shouldSanitize + ); + query += n.rqd ? ` SET NOT NULL;\n` : ` DROP NOT NULL;\n`; + } + + if (n.cdf !== o.cdf) { + query += this.genQuery( + `\nALTER TABLE ?? ALTER COLUMN ?? `, + [t, n.cn], + shouldSanitize + ); + query += n.cdf ? ` SET DEFAULT ${n.cdf};\n` : ` DROP DEFAULT;\n`; + } + } + return query; + } + + get schema() { + return ( + (this.connectionConfig && + this.connectionConfig.searchPath && + this.connectionConfig.searchPath[0]) || + 'public' + ); + } + + /** + * + * @param {Object} args + * @returns {Object} result + * @returns {Number} code + * @returns {String} message + */ + async totalRecords(args: any = {}) { + const func = this.totalRecords.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + const data = await this.sqlClient.raw( + `SELECT SUM(n_live_tup) as TotalRecords FROM pg_stat_user_tables;` + ); + result.data = data.rows[0]; + } catch (e) { + result.code = -1; + result.message = e.message; + result.object = e; + } finally { + log.api(`${func} :result: ${result}`); + } + return result; + } +} + +function getDefaultValue(n) { + if (n.cdf === undefined || n.cdf === null) return n.cdf; + switch (n.dt) { + case 'serial': + case 'bigserial': + case 'smallserial': + return ''; + break; + case 'boolean': + case 'bool': + case 'tinyint': + case 'int': + case 'samllint': + case 'bigint': + case 'integer': + case 'mediumint': + case 'int2': + case 'int4': + case 'int8': + case 'long': + case 'number': + case 'float': + case 'double': + case 'decimal': + case 'numeric': + case 'real': + case 'double precision': + case 'money': + case 'smallmoney': + case 'dec': + return n.cdf; + break; + + case 'datetime': + case 'timestamp': + case 'date': + case 'time': + if ( + n.cdf.indexOf('CURRENT_TIMESTAMP') > -1 || + /\(([\d\w'", ]*)\)$/.test(n.cdf) + ) { + return n.cdf; + } + // return JSON.stringify(n.cdf); + break; + default: + // return JSON.stringify(n.cdf); + break; + } + return n.cdf; +} + +export default PGClient; diff --git a/packages/nocodb/src/lib/db/sql-client/lib/pg/YugabyteClient.ts b/packages/nocodb/src/lib/db/sql-client/lib/pg/YugabyteClient.ts new file mode 100644 index 0000000000..3dd206007e --- /dev/null +++ b/packages/nocodb/src/lib/db/sql-client/lib/pg/YugabyteClient.ts @@ -0,0 +1,140 @@ +import PGClient from './PgClient'; +import Debug from '../../../util/Debug'; +import Result from '../../../util/Result'; + +const log = new Debug('YBClient'); + +class YBClient extends PGClient { + constructor(connectionConfig) { + super(connectionConfig); + } + /** + * + * @param {Object} - args - for future reasons + * @returns {Object[]} - tables + * @property {String} - tables[].tn + */ + async tableList(args: any = {}) { + const _func = this.tableList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const { rows } = await this.raw( + `select tablename as tn, * from pg_catalog.pg_tables where schemaname != 'information_schema' and schemaname != 'pg_catalog'` + ); + + result.data.list = rows; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - columns + * @property {String} - columns[].tn + * @property {String} - columns[].cn + * @property {String} - columns[].dt + * @property {String} - columns[].dtx + * @property {String} - columns[].np + * @property {String} - columns[].ns - + * @property {String} - columns[].clen - + * @property {String} - columns[].dp - + * @property {String} - columns[].cop - + * @property {String} - columns[].pk - + * @property {String} - columns[].nrqd - + * @property {String} - columns[].not_nullable - + * @property {String} - columns[].ct - + * @property {String} - columns[].un - + * @property {String} - columns[].ai - + * @property {String} - columns[].unique - + * @property {String} - columns[].cdf - + * @property {String} - columns[].cc - + * @property {String} - columns[].csn - + */ + + async columnList(args: any = {}) { + const _func = this.columnList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.raw(` + select c.relname as tn, a.attname as cn, pg_catalog.format_type(a.atttypid, a.atttypmod) as "dt",a.attnotnull as "not_nullable", + pg_catalog.pg_get_expr(ad.adbin, ad.adrelid, true) as cdf, dsc.description as comment,a.attnum as cop, + coalesce(i.indisprimary,false) as pk, + a.*,ad.oid as attr_id + FROM pg_catalog.pg_attribute a + INNER JOIN pg_catalog.pg_class c ON (a.attrelid=c.oid) + LEFT OUTER JOIN pg_catalog.pg_attrdef ad ON (a.attrelid=ad.adrelid AND a.attnum = ad.adnum) + LEFT OUTER JOIN pg_catalog.pg_description dsc ON (c.oid=dsc.objoid AND a.attnum = dsc.objsubid) + LEFT JOIN pg_index i ON (a.attnum = any(i.indkey) and a.attrelid = i.indrelid and i.indrelid = '${args.tn}'::regclass AND i.indisprimary) + WHERE NOT a.attisdropped AND c.relname = '${args.tn}' and a.attnum > 0 ORDER BY a.attnum`); + + const columns = []; + + for (let i = 0; i < response.rows.length; ++i) { + const column: any = {}; + + column.tn = args.tn; + column.cn = response.rows[i].cn; + column.dt = response.rows[i].dt; + column.np = response.rows[i].np; + column.ns = response.rows[i].ns; + column.clen = response.rows[i].clen; + column.dp = response.rows[i].dp; + column.cop = response.rows[i].cop; + + // todo : there are lot of types in pg - handle them + column.dtx = this.getKnexDataType(column.dt); + column.pk = response.rows[i].pk_constraint_name !== null; + + column.not_nullable = response.rows[i].not_nullable; + column.nrqd = !column.not_nullable; + + // todo: there is no type of unsigned in postgres + response.rows[i].ct = response.rows[i].dt || ''; + column.un = response.rows[i].ct.indexOf('unsigned') !== -1; + + column.ai = false; + if (response.rows[i].cdf) { + column.ai = response.rows[i].cdf.indexOf('nextval') !== -1; + } + + // todo : need to find if column is unique or not + // column['unique'] = response.rows[i]['cst'].indexOf('UNIQUE') === -1 ? false : true; + + column.cdf = response.rows[i].cdf + ? response.rows[i].cdf.split('::')[0].replace(/'/g, '') + : response.rows[i].cdf; + + // todo : need to find column comment + column.cc = response.rows[i].comment; + + column.csn = response.rows[i].csn; + + columns.push(column); + } + + result.data.list = columns; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } +} + +export default YBClient; diff --git a/packages/nocodb/src/lib/db/sql-client/lib/pg/pg.queries.ts b/packages/nocodb/src/lib/db/sql-client/lib/pg/pg.queries.ts new file mode 100644 index 0000000000..7fe9512b92 --- /dev/null +++ b/packages/nocodb/src/lib/db/sql-client/lib/pg/pg.queries.ts @@ -0,0 +1,239 @@ +// https://www.postgresql.org/docs/9.5/datatype.html + +const pgQueries = { + columnList: { + default: { + sql: `SELECT + c.table_name as tn, + c.column_name as cn, + c.data_type as dt, + c.column_type as ct, + c.character_maximum_length as clen, + c.numeric_precision as np, + c.numeric_scale as ns, + c.datetime_precision as dp, + c.ordinal_position as cop, + c.column_key as ck, + c.extra as ext, -- gives ai + c.column_default as cdf, + c.is_nullable as nrqd, + c.privileges as priv, + c.column_comment as cc, + c.generation_expression, + c.character_set_name as csn, + c.collation_name as clnn, + ct.CONSTRAINT_TYPE as cst + FROM + information_schema.COLUMNS as c + Left join( + select + tc.CONSTRAINT_NAME, + tc.TABLE_NAME, + tc.CONSTRAINT_SCHEMA, + tc.CONSTRAINT_TYPE, + s.COLUMN_NAME + from + information_schema.TABLE_CONSTRAINTS as tc + LEFT JOIN information_schema.STATISTICS as s ON + s.table_schema = tc.CONSTRAINT_SCHEMA + and s.TABLE_NAME = tc.TABLE_NAME + and s.INDEX_NAME = tc.CONSTRAINT_NAME + where + tc.CONSTRAINT_SCHEMA = ? + and s.TABLE_NAME = ? + ) ct on + c.TABLE_SCHEMA = ct.CONSTRAINT_SCHEMA + AND c.TABLE_NAME = ct.TABLE_NAME + AND c.COLUMN_NAME = ct.COLUMN_NAME + WHERE + c.table_schema = ? + and c.TABLE_NAME = ? + ORDER BY + c.table_name, + c.ordinal_position`, + paramsHints: ['databaseName', 'tn', 'databaseName', 'tn'] + } + }, + constraintList: { + default: { + sql: `SELECT + k.constraint_name as cstn, + k.column_name as cn, + k.ordinal_position as op, + k.position_in_unique_constraint as puc, + t.constraint_type as cst +FROM information_schema.table_constraints t +LEFT JOIN information_schema.key_column_usage k +USING(constraint_name,table_schema,table_name) +WHERE +t.table_schema=? +AND t.table_name=?;`, + paramsHints: ['database', 'tn'] + } + }, + createDatabaseIfNotExists: { + default: { + sql: `create database if not exists ??`, + paramsHints: ['database'] + } + }, + createTableIfNotExists: { + default: { + sql: ``, + paramsHints: [] + } + }, + + dropDatabase: { + default: { + sql: `drop database ??`, + paramsHints: ['database'] + } + }, + databaseList: { + default: { + sql: `SHOW databases`, + paramsHints: [] + } + }, + + hasDatabase: { + default: { + sql: `SHOW DATABASES LIKE ??`, + paramsHints: ['databaseName'] + } + }, + indexList: { + default: { + sql: `show index from ??`, + paramsHints: ['tn'] + } + }, + + functionList: { + default: { + sql: `show function status where db=?`, + paramsHints: ['databaseName'] + } + }, + functionRead: { + default: { + sql: `SHOW CREATE FUNCTION ??`, + paramsHints: ['function_name'] + } + }, + functionDelete: { + default: { + sql: `DROP FUNCTION IF EXISTS ??`, + paramsHints: ['function_name'] + } + }, + + procedureList: { + default: { + sql: `show procedure status where db=?`, + paramsHints: ['databaseName'] + } + }, + procedureRead: { + default: { + sql: `show create procedure ??`, + paramsHints: ['procedure_name'] + } + }, + procedureDelete: { + default: { + sql: `DROP PROCEDURE IF EXISTS ??`, + paramsHints: ['procedure_name'] + } + }, + + relationList: { + default: { + sql: `SELECT + kcu.CONSTRAINT_NAME as cstn, + kcu.TABLE_NAME as tn, + kcu.COLUMN_NAME as cn, + kcu.POSITION_IN_UNIQUE_CONSTRAINT as puc, + kcu.REFERENCED_TABLE_NAME as rtn, + kcu.REFERENCED_COLUMN_NAME as rcn, + rc.MATCH_OPTION as mo, + rc.UPDATE_RULE as ur, + rc.DELETE_RULE as dr + FROM + information_schema.\`KEY_COLUMN_USAGE\` AS kcu + INNER JOIN information_schema.REFERENTIAL_CONSTRAINTS AS rc ON + kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME + WHERE + kcu.table_schema = ? + AND kcu.referenced_column_name IS NOT NULL + AND kcu.table_name=?`, + paramsHints: ['database', 'tn'] + } + }, + + schemaCreate: { + default: { + sql: `create database ??`, + paramsHints: ['database_name'] + } + }, + schemaDelete: { + default: { + sql: `drop database ??`, + paramsHints: ['database_name'] + } + }, + triggerList: { + default: { + sql: `SHOW TRIGGERS like ?`, + paramsHints: ['tn'] + } + }, + tableList: { + default: { + sql: ``, + paramsHints: [] + } + }, + testConnection: { + default: { + sql: ``, + paramsHints: [] + } + }, + triggerRead: { + default: { + sql: `SHOW FULL TABLES IN ?? WHERE TABLE_TYPE LIKE 'VIEW';`, + paramsHints: ['databaseName'] + } + }, + triggerDelete: { + default: { + sql: `DROP TRIGGER ??`, + paramsHints: ['trigger_name'] + } + }, + version: { + default: { + sql: ``, + paramsHints: [] + } + }, + viewRead: { + default: { + sql: `select * FROM INFORMATION_SCHEMA.VIEWS WHERE + TABLE_SCHEMA = ? AND TABLE_NAME = ?`, + paramsHints: ['databaseName', 'view_name'] + } + }, + // + viewList: { + default: { + sql: `SHOW FULL TABLES IN ?? WHERE TABLE_TYPE LIKE 'VIEW'`, + paramsHints: ['databaseName'] + } + } +}; + +export default pgQueries; diff --git a/packages/nocodb/src/lib/db/sql-client/lib/sqlite/SqliteClient.ts b/packages/nocodb/src/lib/db/sql-client/lib/sqlite/SqliteClient.ts new file mode 100644 index 0000000000..b0ad830a8a --- /dev/null +++ b/packages/nocodb/src/lib/db/sql-client/lib/sqlite/SqliteClient.ts @@ -0,0 +1,2087 @@ +import { promisify } from 'util'; +import knex from 'knex'; +import fs from 'fs'; +import KnexClient from '../KnexClient'; +import Debug from '../../../util/Debug'; +import Result from '../../../util/Result'; +import queries from './sqlite.queries'; +import lodash from 'lodash'; +import _ from 'lodash'; +const log = new Debug('SqliteClient'); + +class SqliteClient extends KnexClient { + private queries: any; + private _version: any; + constructor(connectionConfig) { + super(connectionConfig); + this.sqlClient = knex(connectionConfig.connection); + this.queries = queries; + this._version = {}; + } + + getKnexDataTypes() { + const result = new Result(); + + result.data.list = [ + 'int', + 'integer', + 'tinyint', + 'smallint', + 'mediumint', + 'bigint', + 'int2', + 'int8', + 'character', + 'blob sub_type text', + 'numeric', + 'blob', + 'real', + 'double', + 'double precision', + 'float', + 'numeric', + 'boolean', + 'date', + 'datetime', + 'text', + 'varchar', + 'timestamp' + ]; + + return result; + } + + /** + * + * + * @param {Object} args + * @returns {Object} result + * @returns {Number} code + * @returns {String} message + */ + async testConnection(args: any = {}) { + const _func = this.testConnection.name; + const result = new Result(); + + log.api(`${_func}:args:`, args); + + try { + await this.raw('SELECT 1+1 as data'); + } catch (e) { + log.ppe(e); + result.code = -1; + result.message = e.message; + } finally { + log.api(`${_func} :result: ${result}`); + } + return result; + } + + /** + * + * + * @param {Object} args + * @returns {Object} result + * @returns {Number} code + * @returns {String} message + * @returns {Object} object - {version, primary, major, minor} + */ + async version(args?: any) { + const _func = this.version.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + result.data.object = {}; + const data = await this.raw('select sqlite_version() as version'); + log.debug(data[0]); + result.data.object.version = data[0].version; + const versions = data[0].version.split('.'); + + if (versions.length && versions.length === 3) { + result.data.object.primary = versions[0]; + result.data.object.major = versions[1]; + result.data.object.minor = versions[2]; + result.data.object.key = versions[0] + versions[1]; + } else { + result.code = -1; + result.message = `Invalid version : ${data[0].version}`; + } + } catch (e) { + log.ppe(e); + result.code = -1; + result.message = e.message; + } finally { + log.api(`${_func} :result: %o`, result); + } + return result; + } + + async createDatabaseIfNotExists(args) { + const _func = this.createDatabaseIfNotExists.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const exists = await promisify(fs.exists)(args.database); + + if (!exists) { + log.debug('sqlite file do no exists - create one'); + const fd = await promisify(fs.open)(args.database, 'w'); + const close = await promisify(fs.close)(fd); + log.debug('sqlite file is created', fd, close); + } + + // create new knex client + this.sqlClient = knex(this.connectionConfig.connection); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + async dropDatabase(args) { + const _func = this.dropDatabase.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + log.debug('dropping database:', args); + await promisify(fs.unlink)(args.database); + log.debug('dropped database:'); + } catch (e) { + log.ppe(e, _func); + // throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + /** + * + * @param args {tn} + * @returns + */ + async createTableIfNotExists(args: any) { + const _func = this.createTableIfNotExists.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + /** ************** START : create _evolution table if not exists *************** */ + const exists = await this.hasTable({ tn: args.tn }); + + if (!exists.data.value) { + const data = await this.sqlClient.schema.createTable(args.tn, function( + table + ) { + table.increments(); + table.string('title').notNullable(); + table.string('titleDown').nullable(); + table.string('description').nullable(); + table.integer('batch').nullable(); + table.string('checksum').nullable(); + table.integer('status').nullable(); + table.dateTime('created'); + table.timestamps(); + }); + log.debug('Table created:', `${args.tn}`, data); + } else { + log.debug(`${args.tn} tables exists`); + } + /** ************** END : create _evolution table if not exists *************** */ + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + // async startTransaction() { + // // let err = await this.sqlClient.raw("SET autocommit = 0"); + // // log.debug("SET autocommit = 0:", err); + // // err = await this.sqlClient.raw("start transaction"); + // // log.debug("start transaction:", err); + // } + + // async commit() { + // // const err = await this.sqlClient.raw("commit"); + // // log.debug("commit:", err); + // // await this.sqlClient.raw("SET autocommit = 1"); + // // log.debug("SET autocommit = 1:", err); + // } + + // async rollback() { + // // const err = await this.sqlClient.raw("rollback"); + // // log.debug("rollback:", err); + // // await this.sqlClient.raw("SET autocommit = 1"); + // // log.debug("SET autocommit = 1:", err); + // } + + async hasTable(args) { + const _func = this.hasTable.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + // let query = `SELECT name FROM sqlite_master WHERE type='${tn}'` + // log.debug(query,this.connectionConfig,this.sqlClient); + // let tables = await this.sqlClient.raw(query); + await this.sqlClient.raw(`select * from "${args.tn}"`); + result.data.value = true; + } catch (e) { + // log.ppe(e, _func); + result.data.value = false; + // throw e; + } + + log.api(`${_func}: result`, result); + + return result; + } + + async hasDatabase(args: any) { + const _func = this.hasDatabase.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + let exists = false; + log.debug('sqlite databaseName:', args.databaseName); + exists = await promisify(fs.exists)(args.databaseName); + result.data.value = exists; + return result; + } + + /** + * + * @param {Object} - args - for future reasons + * @returns {Object[]} - databases + * @property {String} - databases[].database_name + */ + async databaseList(_args: any) { + return []; + } + + /** + * + * @param {Object} - args - for future reasons + * @returns {Object[]} - tables + * @property {String} - tables[].tn + */ + async tableList(args: any = {}) { + const _func = this.createDatabaseIfNotExists.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const response = await this.sqlClient.raw( + `SELECT name as tn FROM sqlite_master where type = 'table'` + ); + + result.data.list = []; + + for (let i = 0; i < response.length; ++i) { + if (response[i].tn.toLocaleLowerCase() !== 'sqlite_sequence') { + result.data.list.push(response[i]); + } + } + + //result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + async schemaList(args: any) { + const _func = this.createDatabaseIfNotExists.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + throw new Error('SchemaList : Not supported for sqlite'); + + log.api(`${_func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - columns + * @property {String} - columns[].tn + * @property {String} - columns[].cn + * @property {String} - columns[].dt + * @property {String} - columns[].dtx + * @property {String} - columns[].np + * @property {String} - columns[].ns - + * @property {String} - columns[].clen - + * @property {String} - columns[].dp - + * @property {String} - columns[].cop - + * @property {String} - columns[].pk - + * @property {String} - columns[].nrqd - + * @property {String} - columns[].not_nullable - + * @property {String} - columns[].ct - + * @property {String} - columns[].un - + * @property {String} - columns[].ai - + * @property {String} - columns[].unique - + * @property {String} - columns[].cdf - + * @property {String} - columns[].cc - + * @property {String} - columns[].csn - + */ + async columnList(args: any) { + const _func = this.columnList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const response = await this.sqlClient.raw( + `PRAGMA table_info("${args.tn}")` + ); + + const triggerList = (await this.triggerList(args)).data.list; + + for (let i = 0; i < response.length; ++i) { + response[i].cn = response[i].name; + response[i].cno = response[i].cn; + response[i].dt = response[i].type.toLocaleLowerCase(); + switch (response[i].dt) { + case 'integer': + case 'int': + case 'text': + case 'varchar': + case 'numeric': + case 'blob': + case 'blob sub_type text': + case 'real': + case 'timestamp': + case 'tinyint': + case 'smallint': + case 'mediumint': + case 'bigint': + case 'int2': + case 'int8': + case 'character': + case 'double': + case 'double precision': + case 'float': + case 'boolean': + case 'date': + case 'datetime': + case 'time': + break; + + default: + /* there is length info available within () - we are extracting this*/ + if (response[i].dt[0] === 't' || response[i].dt[0] === 'i') { + const matches = /(\w+)\(([^)]+)\)/.exec(response[i].dt); + if (matches && matches.length && matches.length > 1) { + response[i].dtxp = matches[2]; + response[i].dt = matches[1]; + } + } + break; + } + response[i].nrqd = response[i].notnull !== 1; + response[i].not_nullable = response[i].notnull === 1; + response[i].rqd = response[i].notnull === 1; + response[i].cdf = response[i].dflt_value; + response[i].pk = response[i].pk === 1; + response[i].cop = response[i].cid; + + // https://stackoverflow.com/a/7906029 + response[i].ai = response[i].pk && response[i].dt === 'integer'; + response[i].dtx = this.getKnexDataType(response[i].dt); + + response[i].dtxp = ''; + response[i].dtxs = ''; + + response[i].au = !!triggerList.find( + ({ trigger }) => trigger === `xc_trigger_${args.tn}_${response[i].cn}` + ); + } + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - indexes + * @property {String} - indexes[].table - + * @property {String} - indexes[].cn - + * @property {String} - indexes[].key_name - + * @property {String} - indexes[].non_unique - + * @property {String} - indexes[].seq_in_index - + * @property {String} - indexes[].collation - + * @property {String} - indexes[].cardinality - + * @property {String} - indexes[].sub_part - + * @property {String} - indexes[].packed - + * @property {String} - indexes[].null - + * @property {String} - indexes[].index_type - + * @property {String} - indexes[].comment - + * @property {String} - indexes[].index_comment - + */ + async indexList(args: any) { + const _func = this.indexList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + // PRAGMA index_list('film') + // + // PRAGMA index_info('idx_fk_original_language_id'); + // + // PRAGMA index_xinfo('idx_fk_original_language_id'); + + const response = await this.sqlClient.raw( + `PRAGMA index_list("${args.tn}")` + ); + + const rows = []; + + for (let i = 0, rowCount = 0; i < response.length; ++i, ++rowCount) { + response[i].key_name = response[i].name; + response[i].non_unique = response[i].unique === 0 ? 1 : 0; + response[i].non_unique_original = response[i].unique === 0 ? 1 : 0; + response[i].unique = response[i].unique === 1 ? 1 : 0; + + const colsInIndex = await this.sqlClient.raw( + `PRAGMA index_info('${response[i].key_name}')` + ); + + if (colsInIndex.length === 1) { + rows[rowCount] = response[i]; + + rows[rowCount].cn = colsInIndex[0].name; + rows[rowCount].seq_in_index = 1; + } else { + for (let j = 0; j < colsInIndex.length; ++j, ++rowCount) { + rows[rowCount] = JSON.parse(JSON.stringify(response[i])); + rows[rowCount].cn = colsInIndex[j].name; + rows[rowCount].seq_in_index = j; + } + rowCount--; + } + } + + result.data.list = rows; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - relations + * @property {String} - relations[].tn + * @property {String} - relations[].cstn - + * @property {String} - relations[].tn - + * @property {String} - relations[].cn - + * @property {String} - relations[].rtn - + * @property {String} - relations[].rcn - + * @property {String} - relations[].puc - + * @property {String} - relations[].ur - + * @property {String} - relations[].dr - + * @property {String} - relations[].mo - + */ + async relationList(args: any) { + const _func = this.relationList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + `PRAGMA foreign_key_list('${args.tn}')` + ); + + for (let i = 0; i < response.length; ++i) { + response[i].tn = args.tn; + response[i].cn = response[i].from; + response[i].rtn = response[i].table; + response[i].rcn = response[i].to; + response[i].ur = response[i].on_update; + response[i].dr = response[i].on_delete; + response[i].mo = response[i].match; + } + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - relations + * @property {String} - relations[].tn + * @property {String} - relations[].cstn - + * @property {String} - relations[].tn - + * @property {String} - relations[].cn - + * @property {String} - relations[].rtn - + * @property {String} - relations[].rcn - + * @property {String} - relations[].puc - + * @property {String} - relations[].ur - + * @property {String} - relations[].dr - + * @property {String} - relations[].mo - + */ + async relationListAll(args: any) { + const _func = this.relationList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + let tables: any = await this.tableList(); + tables = tables.data.list; + const fks = []; + + for (let i = 0; i < tables.length; ++i) { + const response = await this.sqlClient.raw( + `PRAGMA foreign_key_list('${tables[i].tn}')` + ); + + for (let j = 0; j < response.length; ++j) { + response[j].tn = tables[i].tn; + response[j].cn = response[j].from; + response[j].rtn = response[j].table; + response[j].rcn = response[j].to; + response[j].ur = response[j].on_update; + response[j].dr = response[j].on_delete; + response[j].mo = response[j].match; + fks.push(response[j]); + } + } + + result.data.list = fks; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.tn - + * @returns {Object[]} - triggers + * @property {String} - triggers[].trigger + * @property {String} - triggers[].event + * @property {String} - triggers[].table + * @property {String} - triggers[].statement + * @property {String} - triggers[].timing + * @property {String} - triggers[].created + * @property {String} - triggers[].sql_mode + * @property {String} - triggers[].definer + * @property {String} - triggers[].character_set_client + * @property {String} - triggers[].collation_connection + * @property {String} - triggers[].database collation + */ + async triggerList(args: any) { + const _func = this.triggerList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + `select *, name as trigger_name from sqlite_master where type = 'trigger' and tbl_name='${args.tn}';` + ); + + for (let i = 0; i < response.length; ++i) { + response[i].trigger = response[i].name; + response[i].table = response[i].tbl_name; + response[i].statement = response[i].sql; + } + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @returns {Object[]} - functions + * @property {String} - functions[].function_name + * @property {String} - functions[].type + * @property {String} - functions[].definer + * @property {String} - functions[].modified + * @property {String} - functions[].created + * @property {String} - functions[].security_type + * @property {String} - functions[].comment + * @property {String} - functions[].character_set_client + * @property {String} - functions[].collation_connection + * @property {String} - functions[].database collation + */ + async functionList(args: any) { + const _func = this.functionList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + `show function status where db='${args.databaseName}'` + ); + + if (response.length === 2) { + const functions = []; + + for (let i = 0; i < response[0].length; ++i) { + let fn = response[0][i]; + fn = _.mapKeys(fn, function(_v, k) { + return k.toLowerCase(); + }); + fn.function_name = fn.name; + functions.push(fn); + } + + result.data.list = functions; + } else { + log.debug('Unknown response for databaseList:', response); + result.data.list = []; + } + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - For future reasons + * @returns {Object[]} - procedures + * @property {String} - procedures[].procedure_name + * @property {String} - procedures[].type + * @property {String} - procedures[].definer + * @property {String} - procedures[].modified + * @property {String} - procedures[].created + * @property {String} - procedures[].security_type + * @property {String} - procedures[].comment + * @property {String} - procedures[].definer + * @property {String} - procedures[].character_set_client + * @property {String} - procedures[].collation_connection + * @property {String} - procedures[].database collation + */ + async procedureList(args: any) { + const _func = this.procedureList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + `show procedure status where db='${args.databaseName}'` + ); + + if (response.length === 2) { + const procedures = []; + + for (let i = 0; i < response[0].length; ++i) { + let procedure = response[0][i]; + procedure = _.mapKeys(procedure, function(_v, k) { + return k.toLowerCase(); + }); + procedure.procedure_name = procedure.name; + procedures.push(procedure); + } + + result.data.list = procedures; + } else { + log.debug('Unknown response for databaseList:', response); + result.data.list = []; + } + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @returns {Object[]} - views + * @property {String} - views[].view_name + * @property {String} - views[].view_definition + */ + async viewList(args: any) { + const _func = this.viewList.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + `SELECT * FROM sqlite_master WHERE type = 'view'` + ); + + for (let i = 0; i < response.length; ++i) { + response[i].view_name = response[i].name; + response[i].view_definition = response[i].sql; + } + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.function_name - + * @returns {Object[]} - functions + * @property {String} - sql_mode + * @property {String} - create_function + * @property {String} - database collation + * @property {String} - collation_connection + * @property {String} - character_set_client + */ + async functionRead(args: any) { + const _func = this.functionRead.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + `SHOW CREATE FUNCTION ${args.function_name};` + ); + + if (response.length === 2) { + const _functions = []; + + for (let i = 0; i < response[0].length; ++i) { + let _function = response[0][i]; + + _function = _.mapKeys(_function, function(_v, k) { + return k.toLowerCase(); + }); + + _function.create_function = _function['create function']; + + _functions.push(_function); + } + + result.data.list = _functions; + } else { + log.debug('Unknown response for databaseList:', response); + result.data.list = []; + } + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.procedure_name - + * @returns {Object[]} - functions + * @property {String} - sql_mode + * @property {String} - create_function + * @property {String} - database collation + * @property {String} - collation_connection + * @property {String} - character_set_client + */ + async procedureRead(args: any) { + const _func = this.procedureRead.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + `show create procedure ${args.procedure_name};` + ); + + if (response.length === 2) { + const procedures = []; + + for (let i = 0; i < response[0].length; ++i) { + let procedure = response[0][i]; + + procedure = _.mapKeys(procedure, function(_v, k) { + return k.toLowerCase(); + }); + + procedure.create_procedure = procedure['create procedure']; + + procedures.push(procedure); + } + + result.data.list = procedures; + } else { + log.debug('Unknown response for databaseList:', response); + result.data.list = []; + } + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {Object} - args.view_name - + * @returns {Object[]} - views + * @property {String} - views[].tn + */ + async viewRead(args: any) { + const _func = this.viewRead.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + const response = await this.sqlClient.raw( + `SELECT * FROM sqlite_master WHERE type = 'view' AND name = '${args.view_name}'` + ); + + for (let i = 0; i < response.length; ++i) { + response[i].view_name = response[i].name; + response[i].view_definition = response[i].sql; + } + + result.data.list = response; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + async triggerRead(args: any) { + const _func = this.triggerRead.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + args.databaseName = this.connectionConfig.connection.database; + + const response = await this.sqlClient.raw( + `SHOW FULL TABLES IN ${args.databaseName} WHERE TABLE_TYPE LIKE 'VIEW';` + ); + + if (response.length === 2) { + const views = []; + + for (let i = 0; i < response[0].length; ++i) { + const view = response[0][i]; + view.view_name = view[`Tables_in_${args.databaseName}`]; + views.push(view); + } + + result.data.list = views; + } else { + log.debug('Unknown response for databaseList:', response); + result.data.list = []; + } + } catch (e) { + log.ppe(e, _func); + throw e; + } + + log.api(`${_func}: result`, result); + return result; + } + + async schemaCreate(args: any) { + const _func = this.schemaCreate.name; + // const result = new Result(); + log.api(`${_func}:args:`, args); + + const rows = await this.sqlClient.raw( + `create database ${args.database_name}` + ); + return rows; + } + + async schemaDelete(args: any) { + const _func = this.schemaDelete.name; + // const result = new Result(); + log.api(`${_func}:args:`, args); + + const rows = await this.sqlClient.raw( + `drop database ${args.database_name}` + ); + return rows; + } + + /** ************** END : sql queries *************** */ + async triggerDelete(args: any) { + const _func = this.triggerDelete.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + const query = this.querySeparator() + `DROP TRIGGER ${args.trigger_name}`; + await this.sqlClient.raw(query); + result.data.object = { + upStatement: [{ sql: query }], + downStatement: [{ sql: `;` }] + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + return result; + } + + async functionDelete(args: any) { + const _func = this.createDatabaseIfNotExists.name; + // const result = new Result(); + log.api(`${_func}:args:`, args); + + const rows = await this.sqlClient.raw( + `DROP FUNCTION IF EXISTS ${args.function_name}` + ); + return rows; + } + + async procedureDelete(args: any) { + const _func = this.procedureDelete.name; + // const result = new Result(); + log.api(`${_func}:args:`, args); + + const rows = await this.sqlClient.raw( + `DROP PROCEDURE IF EXISTS ${args.procedure_name}` + ); + return rows; + } + + /** + * + * @param {Object} args + * @param {String} func : function name + * @returns {Result} + * @returns {Object} - Result.data + * @returns {String} - Result.data.value - sql query + */ + async _getQuery(args) { + try { + if (_.isEmpty(this._version)) { + const result = await this.version(); + this._version = result.data.object; + log.debug( + `Version was empty for ${args.func}: population version for database as`, + this._version + ); + } + + // log.debug(this._version, args); + + if (this._version.key in this.queries[args.func]) { + return this.queries[args.func][this._version.key].sql; + } + return this.queries[args.func].default.sql; + } catch (error) { + log.ppe(error, this._getQuery.name); + throw error; + } + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.function_name + * @param {String} - args.event + * @param {String} - args.timing + * @returns {Object[]} - result rows + */ + async functionCreate(args: any) { + const func = this.functionCreate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const rows = await this.sqlClient.raw( + `CREATE TRIGGER \`${args.function_name}\` \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}` + ); + result.data.list = rows; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.function_name + * @param {String} - args.event + * @param {String} - args.timing + * @returns {Object[]} - result rows + */ + async functionUpdate(args: any) { + const func = this.functionUpdate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + await this.sqlClient.raw(`DROP TRIGGER ${args.function_name}`); + const rows = await this.sqlClient.raw( + `CREATE TRIGGER \`${args.function_name}\` \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}` + ); + result.data.list = rows; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.procedure_name + * @param {String} - args.event + * @param {String} - args.timing + * @returns {Object[]} - result rows + */ + async procedureCreate(args: any) { + const func = this.procedureCreate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const rows = await this.sqlClient.raw( + `CREATE TRIGGER \`${args.procedure_name}\` \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}` + ); + result.data.list = rows; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.procedure_name + * @param {String} - args.event + * @param {String} - args.timing + * @returns {Object[]} - result rows + */ + async procedureUpdate(args: any) { + const func = this.procedureUpdate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + await this.sqlClient.raw(`DROP TRIGGER ${args.procedure_name}`); + const rows = await this.sqlClient.raw( + `CREATE TRIGGER \`${args.procedure_name}\` \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}` + ); + result.data.list = rows; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.trigger_name + * @param {String} - args.event + * @param {String} - args.timing + * @returns {Object[]} - result rows + */ + async triggerCreate(args: any) { + const func = this.triggerCreate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const query = + this.querySeparator() + + `CREATE TRIGGER \`${args.trigger_name}\` \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}`; + await this.sqlClient.raw(query); + result.data.object = { + upStatement: [{ sql: query }], + downStatement: [{ sql: ';' }] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.tn + * @param {String} - args.trigger_name + * @param {String} - args.event + * @param {String} - args.timing + * @param {String} - args.oldStatement + * @returns {Object[]} - result rows + */ + async triggerUpdate(args: any) { + const func = this.triggerUpdate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + await this.sqlClient.raw(`DROP TRIGGER ${args.trigger_name}`); + await this.sqlClient.raw( + `CREATE TRIGGER \`${args.trigger_name}\` \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}` + ); + + const upQuery = `DROP TRIGGER ${args.trigger_name};\nCREATE TRIGGER \`${args.trigger_name}\` \n${args.timing} ${args.event}\nON "${args.tn}" FOR EACH ROW\n${args.statement}`; + + result.data.object = { + upStatement: [{ sql: upQuery }], + downStatement: [{ sql: ';' }] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.view_name + * @param {String} - args.view_definition + * @returns {Object} - up and down statements + */ + async viewCreate(args: any) { + const func = this.viewCreate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const query = args.view_definition + ';'; + + await this.sqlClient.raw(query); + result.data.object = { + upStatement: [{ sql: this.querySeparator() + query }], + downStatement: [{ sql: `;` }] + //downStatement: [{sql:`DROP VIEW ${args.view_name}`}] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.view_name + * @param {String} - args.view_definition + * @param {String} - args.oldViewDefination + * @returns {Object} - up and down statements + */ + async viewUpdate(args: any) { + const func = this.viewUpdate.name; + const result = new Result(); + log.api(`${func}:args:`, args); + try { + const query = args.view_definition; + + await this.sqlClient.raw(query); + result.data.object = { + upStatement: [{ sql: this.querySeparator() + query }], + downStatement: [{ sql: ';' }] + // downStatement: [{`CREATE VIEW ${args.view_name} AS \n${ + // args.oldViewDefination + // }`}] + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args - Input arguments + * @param {String} - args.view_name + * @param {String} - args.view_definition + * @param {String} - args.oldViewDefination + * @returns {Object} - up and down statements + */ + async viewDelete(args: any) { + const func = this.viewDelete.name; + const result = new Result(); + log.api(`${func}:args:`, args); + // `DROP TRIGGER ${args.view_name}` + try { + const query = `DROP VIEW ${args.view_name};`; + + await this.sqlClient.raw(query); + + result.data.object = { + upStatement: [{ sql: this.querySeparator() + query }], + downStatement: [{ sql: ';' }] + // downStatement: `CREATE VIEW ${args.view_name} AS \n${ + // args.oldViewDefination + // }` + }; + } catch (e) { + log.ppe(e, func); + throw e; + } + + log.api(`${func}: result`, result); + return result; + } + + /** + * + * @param {Object} - args + * @param {String} - args.tn + * @param {Object[]} - args.columns + * @param {String} - args.columns[].tn + * @param {String} - args.columns[].cn + * @param {String} - args.columns[].dt + * @param {String} - args.columns[].np + * @param {String} - args.columns[].ns - + * @param {String} - args.columns[].clen - + * @param {String} - args.columns[].dp - + * @param {String} - args.columns[].cop - + * @param {String} - args.columns[].pk - + * @param {String} - args.columns[].nrqd - + * @param {String} - args.columns[].not_nullable - + * @param {String} - args.columns[].ct - + * @param {String} - args.columns[].un - + * @param {String} - args.columns[].ai - + * @param {String} - args.columns[].unique - + * @param {String} - args.columns[].cdf - + * @param {String} - args.columns[].cc - + * @param {String} - args.columns[].csn - + * @param {String} - args.columns[].dtx + * - value will be 'specificType' for all cols except ai + * - for ai it will be integer, bigInteger + * - tiny, small and medium Int auto increement is not supported + * @param {String} - args.columns[].dtxp - to use in UI + * @param {String} - args.columns[].dtxs - to use in UI + * @returns {Promise<{upStatement, downStatement}>} + */ + async tableCreate(args) { + const _func = this.tableCreate.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + args.table = args.tn; + args.sqlClient = this.sqlClient; + + /**************** create table ****************/ + const upQuery = this.querySeparator() + this.createTable(args.tn, args); + await this.sqlClient.raw(upQuery); + + //const downStatement = this.sqlClient.schema.dropTable(args.table).toSQL(); + const downStatement = [{ sql: ';' }]; + + this.emit(`Success : ${upQuery}`); + + const triggerStatements = await this.afterTableCreate(args); + + /**************** return files *************** */ + result.data.object = { + upStatement: [{ sql: upQuery }, ...triggerStatements.upStatement], + downStatement: downStatement + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + async afterTableCreate(args) { + const result = { upStatement: [], downStatement: [] }; + let upQuery = ''; + const downQuery = ''; + + const pk = args.columns.find(c => c.pk); + if (!pk) return result; + + for (let i = 0; i < args.columns.length; i++) { + const column = args.columns[i]; + if (column.au) { + const triggerName = `xc_trigger_${args.tn}_${column.cn}`; + const triggerCreateQuery = `${this.querySeparator()}CREATE TRIGGER ${triggerName} + AFTER UPDATE + ON "${args.tn}" FOR EACH ROW + BEGIN + UPDATE "${args.tn}" SET ${column.cn} = current_timestamp + WHERE ${pk.cn} = old.${pk.cn}; + END;`; + + upQuery += triggerCreateQuery; + + await this.sqlClient.raw(triggerCreateQuery); + } + } + result.upStatement[0] = { sql: upQuery }; + result.downStatement[0] = { sql: downQuery }; + + return result; + } + + async afterTableUpdate(args) { + const result = { upStatement: [], downStatement: [] }; + let upQuery = ''; + const downQuery = ''; + + const pk = args.columns.find(c => c.pk); + if (!pk) return result; + + for (let i = 0; i < args.columns.length; i++) { + const column = args.columns[i]; + if (column.au && column.altered === 1) { + const triggerName = `xc_trigger_${args.tn}_${column.cn}`; + const triggerCreateQuery = `${this.querySeparator()}CREATE TRIGGER ${triggerName} + AFTER UPDATE + ON "${args.tn}" FOR EACH ROW + BEGIN + UPDATE "${args.tn}" SET ${column.cn} = current_timestamp + WHERE ${pk.cn} = old.${pk.cn}; + END;`; + + upQuery += triggerCreateQuery; + + await this.sqlClient.raw(triggerCreateQuery); + } + } + result.upStatement[0] = { sql: upQuery }; + result.downStatement[0] = { sql: downQuery }; + + return result; + } + + /** + * + * @param {Object} - args + * @param {String} - args.table + * @param {String} - args.table + * @param {Object[]} - args.columns + * @param {String} - args.columns[].tn + * @param {String} - args.columns[].cn + * @param {String} - args.columns[].dt + * @param {String} - args.columns[].np + * @param {String} - args.columns[].ns - + * @param {String} - args.columns[].clen - + * @param {String} - args.columns[].dp - + * @param {String} - args.columns[].cop - + * @param {String} - args.columns[].pk - + * @param {String} - args.columns[].nrqd - + * @param {String} - args.columns[].not_nullable - + * @param {String} - args.columns[].ct - + * @param {String} - args.columns[].un - + * @param {String} - args.columns[].ai - + * @param {String} - args.columns[].unique - + * @param {String} - args.columns[].cdf - + * @param {String} - args.columns[].cc - + * @param {String} - args.columns[].csn - + * @param {Number} - args.columns[].altered - 1,2,4 = addition,edited,deleted + * @returns {Promise<{upStatement, downStatement}>} + */ + async tableUpdate(args) { + const _func = this.tableUpdate.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + args.table = args.tn; + const originalColumns = args.originalColumns; + args.connectionConfig = this._connectionConfig; + args.sqlClient = this.sqlClient; + + let upQuery = ''; + let downQuery = ''; + + for (let i = 0; i < args.columns.length; ++i) { + const oldColumn = lodash.find(originalColumns, { + cn: args.columns[i].cno + }); + + if (args.columns[i].altered & 4) { + // col remove + upQuery += await this.alterTableRemoveColumn( + args.table, + args.columns[i], + oldColumn, + upQuery + ); + downQuery += this.alterTableAddColumn( + args.table, + oldColumn, + args.columns[i], + downQuery + ); + } else if (args.columns[i].altered & 2 || args.columns[i].altered & 8) { + // col edit + upQuery += this.alterTableChangeColumn( + args.table, + args.columns[i], + oldColumn, + upQuery + ); + downQuery += ';'; + // downQuery += this.alterTableChangeColumn( + // args.table, + // oldColumn, + // args.columns[i], + // downQuery, + // this.sqlClient + // ); + } else if (args.columns[i].altered & 1) { + // col addition + upQuery += this.alterTableAddColumn( + args.table, + args.columns[i], + oldColumn, + upQuery + ); + downQuery += ';'; + // downQuery += alterTableRemoveColumn( + // args.table, + // args.columns[i], + // oldColumn, + // downQuery, + // this.sqlClient + // ); + } + } + + upQuery += this.alterTablePK( + args.columns, + args.originalColumns, + upQuery, + this.sqlClient + ); + //downQuery += alterTablePK(args.originalColumns, args.columns, downQuery); + + if (upQuery) { + //upQuery = `ALTER TABLE ${args.columns[0].tn} ${upQuery};`; + //downQuery = `ALTER TABLE ${args.columns[0].tn} ${downQuery};`; + } + + await Promise.all( + upQuery.split(';').map(async query => { + if (query.trim().length) await this.sqlClient.raw(query); + }) + ); + + // await this.sqlClient.raw(upQuery); + + console.log(upQuery); + + const afterUpdate = await this.afterTableUpdate(args); + + result.data.object = { + upStatement: [ + { sql: this.querySeparator() + upQuery }, + ...afterUpdate.upStatement + ], + downStatement: [{ sql: ';' }] + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + return result; + } + + /** + * + * @param {Object} - args + * @param args.tn + * @returns {Promise<{upStatement, downStatement}>} + */ + async tableDelete(args) { + const _func = this.tableDelete.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + // const { columns } = args; + args.sqlClient = this.sqlClient; + + /** ************** create up & down statements *************** */ + const upStatement = + this.querySeparator() + + this.sqlClient.schema.dropTable(args.tn).toString(); + //const downQuery = createTable(args.tn, args); + const downStatement = [{ sql: ';' }]; + this.emit(`Success : ${upStatement}`); + + /** ************** drop tn *************** */ + await this.sqlClient.schema.dropTable(args.tn); + + /** ************** return files *************** */ + result.data.object = { + upStatement: [{ sql: upStatement }], + downStatement + }; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableCreateStatement(args) { + const _func = this.tableCreateStatement.name; + let result = new Result(); + log.api(`${_func}:args:`, args); + try { + result = await this.columnList(args); + const upQuery = this.createTable(args.tn, { + tn: args.tn, + columns: result.data.list + }); + result.data = upQuery; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableInsertStatement(args) { + const _func = this.tableCreateStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + result.data = `INSERT INTO "${args.tn}" (`; + let values = ' VALUES ('; + const response = await this.columnList(args); + if (response.data && response.data.list) { + for (let i = 0; i < response.data.list.length; ++i) { + if (!i) { + result.data += `\n${response.data.list[i].cn}\n\t`; + values += `\n<${response.data.list[i].cn}>\n\t`; + } else { + result.data += `, \`${response.data.list[i].cn}\`\n\t`; + values += `, <${response.data.list[i].cn}>\n\t`; + } + } + } + + result.data += `)`; + values += `);`; + result.data += values; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableUpdateStatement(args) { + const _func = this.tableUpdateStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + result.data = `UPDATE "${args.tn}" \nSET\n`; + const response = await this.columnList(args); + if (response.data && response.data.list) { + for (let i = 0; i < response.data.list.length; ++i) { + if (!i) { + result.data += `${response.data.list[i].cn} = <\`${response.data.list[i].cn}\`>\n\t`; + } else { + result.data += `,${response.data.list[i].cn} = <\`${response.data.list[i].cn}\`>\n\t`; + } + } + } + + result.data += ';'; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableDeleteStatement(args) { + const _func = this.tableDeleteStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + result.data = `DELETE FROM "${args.tn}" where ;`; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableTruncateStatement(args) { + const _func = this.tableTruncateStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + try { + result.data = `TRUNCATE TABLE "${args.tn}";`; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * + * @param args + * @param args.tn + * @returns {Object} Result + * @returns {String} result.data + */ + async tableSelectStatement(args) { + const _func = this.tableSelectStatement.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + result.data = `SELECT `; + const response = await this.columnList(args); + if (response.data && response.data.list) { + for (let i = 0; i < response.data.list.length; ++i) { + if (!i) { + result.data += `${response.data.list[i].cn}\n\t`; + } else { + result.data += `, ${response.data.list[i].cn}\n\t`; + } + } + } + + result.data += ` FROM "${args.tn}";`; + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + /** + * @param args + * @param args.tn + * @param args.folder + * @returns {Object} Result + * @returns {Promise} + */ + async sequelizeModelCreate(args) { + const _func = this.sequelizeModelCreate.name; + const result = new Result(); + log.api(`${_func}:args:`, args); + + try { + console.time(_func); + + /* Get tables */ + let tables: any = []; + if (!args.tn) { + tables = await this.tableList(args); + tables = tables.data.list; + } else { + tables.push({ tn: args.tn }); + } + + /* Get all relations */ + let relations: any = await this.relationList(args); + relations = relations.data.list; + + /* Filter relations for current table */ + if (args.tn) { + relations = relations.filter( + r => r.tn === args.tn || r.rtn === args.tn + ); + } + + /* Get Columnlist for each table */ + for (let i = 0; i < tables.length; ++i) { + let columns: any = await this.columnList({ tn: tables[i].tn }); + columns = columns.data.list; + console.log( + `Sequelize model created: ${tables[i].tn}(${columns.length})\n` + ); + + // let SqliteSequelizeRender = require('./SqliteSequelizeRender'); + // + // let modelRenderer = new SqliteSequelizeRender({ + // dir: path.join(args.folder,'models','sqlite','sequelize','xc'), + // filename: `${tables[i].tn}.model.js`, + // ctx: {tn: tables[i].tn, columns, relations} + // }) + // + // await modelRenderer.render(); + + // /**************** START : Model render sequelize ****************/ + // let SqliteTypeormRender = require('./SqliteTypeormRender'); + // + // let modelTypeormRenderer = new SqliteTypeormRender({ + // dir: path.join(args.folder,'models','sqlite','sequelize','xc'), + // filename: `${tables[i].tn}.typeorm.js`, + // ctx: {tn: tables[i].tn, columns, relations} + // }); + // + // await modelTypeormRenderer.render(); + // /**************** END : Model render sequelize ****************/ + + console.log('\n\n- - - - - - - - - - - - - '); + } + + console.timeEnd(_func); + } catch (e) { + log.ppe(e, _func); + throw e; + } + + return result; + } + + alterTablePK(n, o, _existingQuery, createTable = false) { + const numOfPksInOriginal = []; + const numOfPksInNew = []; + let pksChanged = 0; + + for (let i = 0; i < n.length; ++i) { + if (n[i].pk) { + if (n[i].altered !== 4) numOfPksInNew.push(n[i].cn); + } + } + + for (let i = 0; i < o.length; ++i) { + if (o[i].pk) { + numOfPksInOriginal.push(o[i].cn); + } + } + + if (numOfPksInNew.length === numOfPksInOriginal.length) { + for (let i = 0; i < numOfPksInNew.length; ++i) { + if (numOfPksInOriginal[i] !== numOfPksInNew[i]) { + pksChanged = 1; + break; + } + } + } else { + pksChanged = numOfPksInNew.length - numOfPksInOriginal.length; + } + + let query = ''; + if (!numOfPksInNew.length && !numOfPksInOriginal.length) { + // do nothing + } else if (pksChanged) { + query += numOfPksInOriginal.length ? ',DROP PRIMARY KEY' : ''; + + if (numOfPksInNew.length) { + if (createTable) { + query += this.genQuery(`, PRIMARY KEY(??)`, [numOfPksInNew]); + } else { + query += this.genQuery(`, ADD PRIMARY KEY(??)`, [numOfPksInNew]); + } + } + } + + return query; + } + + async alterTableRemoveColumn(t, n, _o, _existingQuery) { + // let query = existingQuery ? "," : ""; + // query += ` DROP COLUMN ${n.cn}`; + // query = existingQuery ? query : `ALTER TABLE "${t}" ${query};`; + // return query; + await this.sqlClient.schema.alterTable(t, tb => { + tb.dropColumn(n.cn); + }); + + return ''; + } + + createTableColumn(t, n, o, existingQuery) { + return this.alterTableColumn(t, n, o, existingQuery, 0); + } + + alterTableAddColumn(t, n, o, existingQuery) { + return this.alterTableColumn(t, n, o, existingQuery, 1); + } + + alterTableChangeColumn(t, n, o, existingQuery) { + return this.alterTableColumn(t, n, o, existingQuery, 2); + } + + createTable(table, args) { + let query = ''; + + for (let i = 0; i < args.columns.length; ++i) { + query += this.createTableColumn(table, args.columns[i], null, query); + } + + query += this.alterTablePK(args.columns, [], query, true); + + query = this.genQuery(`CREATE TABLE ?? (${query});`, [args.tn]); + + return query; + } + + alterTableColumn(t, n, o, existingQuery, change = 2) { + let query = ''; + // @ts-ignore + const defaultValue = getDefaultValue(n); + let shouldSanitize = true; + if (change === 2) { + query += this.genQuery( + `ALTER TABLE ?? RENAME COLUMN ?? TO ??`, + [t, o.cn, n.cn], + shouldSanitize + ); + } else if (change === 0) { + query = existingQuery ? ',' : ''; + query += this.genQuery(`?? ${n.dt}`, [n.cn], shouldSanitize); + query += n.dtxp && n.dt !== 'text' ? `(${n.dtxp})` : ''; + query += n.cdf ? ` DEFAULT ${n.cdf}` : ' '; + query += n.rqd ? ` NOT NULL` : ' '; + } else if (change === 1) { + shouldSanitize = true; + query += this.genQuery(` ADD ?? ${n.dt}`, [n.cn], shouldSanitize); + query += n.dtxp && n.dt !== 'text' ? `(${n.dtxp})` : ''; + query += n.cdf ? ` DEFAULT ${n.cdf}` : ' '; + query += n.rqd ? ` NOT NULL` : ' '; + query = this.genQuery(`ALTER TABLE ?? ${query};`, [t], shouldSanitize); + } else { + // if(n.cn!==o.cno) { + // query += `\nALTER TABLE ${t} RENAME COLUMN ${n.cno} TO ${n.cn};\n`; + // } + // + // if(n.dt!==o.dt) { + // query += `\nALTER TABLE ${t} ALTER COLUMN ${n.cn} TYPE ${n.dt};\n`; + // } + // + // + // if(n.rqd!==o.rqd) { + // query += `\nALTER TABLE ${t} ALTER COLUMN ${n.cn} `; + // query += n.rqd ? ` SET NOT NULL;\n` : ` DROP NOT NULL;\n`; + // } + // + // if(n.cdf!==o.cdf) { + // query += `\nALTER TABLE ${t} ALTER COLUMN ${n.cn} `; + // query += n.cdf ? ` SET DEFAULT ${n.cdf};\n` : ` DROP DEFAULT;\n`; + // } + } + + return query; + } + + /** + * + * @param {Object} args + * @returns {Object} result + * @returns {Number} code + * @returns {String} message + */ + async totalRecords(args: any) { + const func = this.totalRecords.name; + const result = new Result(); + log.api(`${func}:args:`, args); + + try { + const tables = await this.sqlClient.raw( + `SELECT name FROM sqlite_master WHERE type='table';` + ); + let count = 0; + for (const tb of tables) { + const tmp = await this.sqlClient.raw( + `SELECT COUNT(*) as ct FROM '${tb.name}';` + ); + if (tmp && tmp.length) { + count += tmp[0].ct; + } + } + result.data.TotalRecords = count; + } catch (e) { + result.code = -1; + result.message = e.message; + result.object = e; + } finally { + log.api(`${func} :result: ${result}`); + } + return result; + } +} + +function getDefaultValue(n) { + if (n.cdf === undefined || n.cdf === null) return n.cdf; + switch (n.dt) { + case 'boolean': + case 'bool': + case 'tinyint': + case 'int': + case 'samllint': + case 'bigint': + case 'integer': + case 'smallint': + case 'mediumint': + case 'int2': + case 'int4': + case 'int8': + case 'long': + case 'serial': + case 'bigserial': + case 'smallserial': + case 'number': + case 'float': + case 'double': + case 'decimal': + case 'numeric': + case 'real': + case 'double precision': + case 'money': + case 'smallmoney': + case 'dec': + return n.cdf; + break; + + case 'datetime': + case 'timestamp': + case 'date': + case 'time': + if ( + n.cdf.indexOf('CURRENT_TIMESTAMP') > -1 || + /\(([\d\w'", ]*)\)$/.test(n.cdf) + ) { + return n.cdf; + } + return JSON.stringify(n.cdf); + break; + default: + return JSON.stringify(n.cdf); + break; + } +} + +export default SqliteClient; diff --git a/packages/nocodb/src/lib/db/sql-client/lib/sqlite/sqlite.queries.ts b/packages/nocodb/src/lib/db/sql-client/lib/sqlite/sqlite.queries.ts new file mode 100644 index 0000000000..3eb58e67dd --- /dev/null +++ b/packages/nocodb/src/lib/db/sql-client/lib/sqlite/sqlite.queries.ts @@ -0,0 +1,34 @@ +// https://www.sqlite.org/datatype3.html +const sqliteQueries = { + int: {}, + integer: {}, + tinyint: {}, + smallint: {}, + mediumint: {}, + bigint: {}, + 'unsigned big int': {}, + int2: {}, + int4: {}, + + character: {}, + varchar: {}, + nchar: {}, + text: {}, + clob: {}, + 'varying character': {}, + 'native character': {}, + + blob: {}, + real: {}, + double: {}, + 'double precision': {}, + float: {}, + + numeric: {}, + decimal: {}, + boolean: {}, + date: {}, + datetime: {} +}; + +export default sqliteQueries; diff --git a/packages/nocodb/src/lib/db/sql-mgr/SqlMgr.ts b/packages/nocodb/src/lib/db/sql-mgr/SqlMgr.ts index 9509a9c3f6..84a6b27ec0 100644 --- a/packages/nocodb/src/lib/db/sql-mgr/SqlMgr.ts +++ b/packages/nocodb/src/lib/db/sql-mgr/SqlMgr.ts @@ -6,16 +6,18 @@ import { Tele } from 'nc-help'; import fsExtra from 'fs-extra'; import importFresh from 'import-fresh'; import inflection from 'inflection'; -import { Debug, Result, SqlClientFactory } from 'nc-help'; import slash from 'slash'; +import SqlClientFactory from '../sql-client/lib/SqlClientFactory'; // import debug from 'debug'; -const log = new Debug('SqlMgr'); import KnexMigrator from '../sql-migrator/lib/KnexMigrator'; // import {XKnex} from "../sql-data-mapper"; import NcConnectionMgr from '../../utils/common/NcConnectionMgr'; import { customAlphabet } from 'nanoid'; +import Debug from '../util/Debug'; +import Result from '../util/Result'; const randomID = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 20); +const log = new Debug('SqlMgr'); const ToolOps = { DB_TABLE_LIST: 'tableList', diff --git a/packages/nocodb/src/lib/db/sql-mgr/code/BaseRender.ts b/packages/nocodb/src/lib/db/sql-mgr/code/BaseRender.ts index e4317c5b82..04757f610d 100644 --- a/packages/nocodb/src/lib/db/sql-mgr/code/BaseRender.ts +++ b/packages/nocodb/src/lib/db/sql-mgr/code/BaseRender.ts @@ -8,11 +8,12 @@ // import fsExtra from "fs-extra"; // import md5 from "md5"; // import dayjs from "dayjs";import Emit from "../../sql-migrator/util/emit"; -import Debug from '../../sql-migrator/util/Debug'; -import Emit from '../../sql-migrator/util/emit'; // const beautify = js_beautify.js; +import Debug from '../../util/Debug'; +import Emit from '../../util/emit'; + class BaseRender { protected dir: any; protected filename: any; diff --git a/packages/nocodb/src/lib/db/sql-mgr/v2/SqlMgrv2.ts b/packages/nocodb/src/lib/db/sql-mgr/v2/SqlMgrv2.ts index ec1a2ba092..29f19d05e6 100644 --- a/packages/nocodb/src/lib/db/sql-mgr/v2/SqlMgrv2.ts +++ b/packages/nocodb/src/lib/db/sql-mgr/v2/SqlMgrv2.ts @@ -1,12 +1,13 @@ -import { Debug, SqlClientFactory } from 'nc-help'; - // import debug from 'debug'; -const log = new Debug('SqlMgr'); // import {XKnex} from "../sql-data-mapper"; import NcConnectionMgrv2 from '../../../utils/common/NcConnectionMgrv2'; +import SqlClientFactory from '../../sql-client/lib/SqlClientFactory'; import KnexMigratorv2 from '../../sql-migrator/lib/KnexMigratorv2'; import Base from '../../../models/Base'; +import Debug from '../../util/Debug'; + +const log = new Debug('SqlMgr'); export default class SqlMgrv2 { protected _migrator: KnexMigratorv2; diff --git a/packages/nocodb/src/lib/db/sql-migrator/lib/KnexMigrator.ts b/packages/nocodb/src/lib/db/sql-migrator/lib/KnexMigrator.ts index 2050439018..d5e7c8e68d 100644 --- a/packages/nocodb/src/lib/db/sql-migrator/lib/KnexMigrator.ts +++ b/packages/nocodb/src/lib/db/sql-migrator/lib/KnexMigrator.ts @@ -5,13 +5,12 @@ import { promisify } from 'util'; import glob from 'glob'; import Handlebars from 'handlebars'; import mkdirp from 'mkdirp'; -import { SqlClientFactory } from 'nc-help'; import rmdir from 'rmdir'; - -import Debug from '../util/Debug'; -import Result from '../util/Result'; -import Emit from '../util/emit'; -import * as fileHelp from '../util/file.help'; +import SqlClientFactory from '../../sql-client/lib/SqlClientFactory'; +import Debug from '../../util/Debug'; +import Emit from '../../util/emit'; +import Result from '../../util/Result'; +import * as fileHelp from '../../util/file.help'; import SqlMigrator from './SqlMigrator'; import NcConfigFactory from '../../../utils/NcConfigFactory'; diff --git a/packages/nocodb/src/lib/db/sql-migrator/lib/KnexMigratorv2.ts b/packages/nocodb/src/lib/db/sql-migrator/lib/KnexMigratorv2.ts index e26b086a16..3f5ca84507 100644 --- a/packages/nocodb/src/lib/db/sql-migrator/lib/KnexMigratorv2.ts +++ b/packages/nocodb/src/lib/db/sql-migrator/lib/KnexMigratorv2.ts @@ -3,13 +3,12 @@ import path from 'path'; import { promisify } from 'util'; import glob from 'glob'; +import SqlClientFactory from '../../sql-client/lib/SqlClientFactory'; +import Debug from '../../util/Debug'; +import Emit from '../../util/emit'; // import Handlebars from 'handlebars'; // import mkdirp from 'mkdirp'; -import { SqlClientFactory } from 'nc-help'; -import Debug from '../util/Debug'; -import Result from '../util/Result'; -import Emit from '../util/emit'; -import * as fileHelp from '../util/file.help'; +import * as fileHelp from '../../util/file.help'; // import SqlMigrator from './SqlMigrator'; // import NcConfigFactory from '../../../utils/NcConfigFactory'; @@ -18,6 +17,7 @@ import { XKnex } from '../../sql-data-mapper'; import Project from '../../../models/Project'; import Base from '../../../models/Base'; import NcConnectionMgrv2 from '../../../utils/common/NcConnectionMgrv2'; +import Result from '../../util/Result'; const evt = new Emit(); const log = new Debug('KnexMigrator'); diff --git a/packages/nocodb/src/lib/db/sql-migrator/util/Debug.ts b/packages/nocodb/src/lib/db/util/Debug.ts similarity index 99% rename from packages/nocodb/src/lib/db/sql-migrator/util/Debug.ts rename to packages/nocodb/src/lib/db/util/Debug.ts index dadd6b0dd5..5459a25d6b 100644 --- a/packages/nocodb/src/lib/db/sql-migrator/util/Debug.ts +++ b/packages/nocodb/src/lib/db/util/Debug.ts @@ -45,7 +45,7 @@ export default class Debug { return log; } - ppe(e, func) { + ppe(e, func?) { return this.ppException(e, func); } } diff --git a/packages/nocodb/src/lib/db/sql-migrator/util/DebugMgr.ts b/packages/nocodb/src/lib/db/util/DebugMgr.ts similarity index 100% rename from packages/nocodb/src/lib/db/sql-migrator/util/DebugMgr.ts rename to packages/nocodb/src/lib/db/util/DebugMgr.ts diff --git a/packages/nocodb/src/lib/db/sql-migrator/util/FileCollection.ts b/packages/nocodb/src/lib/db/util/FileCollection.ts similarity index 100% rename from packages/nocodb/src/lib/db/sql-migrator/util/FileCollection.ts rename to packages/nocodb/src/lib/db/util/FileCollection.ts diff --git a/packages/nocodb/src/lib/db/sql-migrator/util/Result.ts b/packages/nocodb/src/lib/db/util/Result.ts similarity index 100% rename from packages/nocodb/src/lib/db/sql-migrator/util/Result.ts rename to packages/nocodb/src/lib/db/util/Result.ts diff --git a/packages/nocodb/src/lib/db/sql-migrator/util/emit.ts b/packages/nocodb/src/lib/db/util/emit.ts similarity index 100% rename from packages/nocodb/src/lib/db/sql-migrator/util/emit.ts rename to packages/nocodb/src/lib/db/util/emit.ts diff --git a/packages/nocodb/src/lib/db/sql-migrator/util/file.help.ts b/packages/nocodb/src/lib/db/util/file.help.ts similarity index 100% rename from packages/nocodb/src/lib/db/sql-migrator/util/file.help.ts rename to packages/nocodb/src/lib/db/util/file.help.ts diff --git a/packages/nocodb/src/lib/meta/NcMetaMgr.ts b/packages/nocodb/src/lib/meta/NcMetaMgr.ts index 0c950cc671..e239acda63 100644 --- a/packages/nocodb/src/lib/meta/NcMetaMgr.ts +++ b/packages/nocodb/src/lib/meta/NcMetaMgr.ts @@ -11,7 +11,7 @@ import extract from 'extract-zip'; import isDocker from 'is-docker'; import multer from 'multer'; import { customAlphabet, nanoid } from 'nanoid'; -import { SqlClientFactory, Tele } from 'nc-help'; +import { Tele } from 'nc-help'; import slash from 'slash'; import { v4 as uuidv4 } from 'uuid'; import { ncp } from 'ncp'; @@ -19,6 +19,7 @@ import { ncp } from 'ncp'; import IEmailAdapter from '../../interface/IEmailAdapter'; import IStorageAdapter from '../../interface/IStorageAdapter'; import { NcConfig, Result } from '../../interface/config'; +import SqlClientFactory from '../db/sql-client/lib/SqlClientFactory'; import { NcConfigFactory } from '../index'; import ProjectMgr from '../db/sql-mgr/ProjectMgr'; import ExpressXcTsRoutes from '../db/sql-mgr/code/routes/xc-ts/ExpressXcTsRoutes'; diff --git a/packages/nocodb/src/lib/utils/Emit.ts b/packages/nocodb/src/lib/utils/Emit.ts new file mode 100644 index 0000000000..ab84be1581 --- /dev/null +++ b/packages/nocodb/src/lib/utils/Emit.ts @@ -0,0 +1,14 @@ +const Emittery = require('emittery'); + +let emitSingleton = null; + +class Emit { + public readonly evt: any; + + constructor() { + if (emitSingleton) return emitSingleton; + this.evt = new Emittery(); + emitSingleton = this; + } +} +module.exports = Emit; diff --git a/packages/nocodb/src/lib/utils/NcConfigFactory.ts b/packages/nocodb/src/lib/utils/NcConfigFactory.ts index a972cb7289..bd823ca76f 100644 --- a/packages/nocodb/src/lib/utils/NcConfigFactory.ts +++ b/packages/nocodb/src/lib/utils/NcConfigFactory.ts @@ -1,4 +1,3 @@ -import { SqlClientFactory } from 'nc-help'; import fs from 'fs'; import parseDbUrl from 'parse-database-url'; import { URL } from 'url'; @@ -10,6 +9,7 @@ import { NcConfig } from '../../interface/config'; import * as path from 'path'; +import SqlClientFactory from '../db/sql-client/lib/SqlClientFactory'; const { uniqueNamesGenerator, diff --git a/packages/nocodb/src/lib/utils/Result.js b/packages/nocodb/src/lib/utils/Result.js new file mode 100644 index 0000000000..4f6778db37 --- /dev/null +++ b/packages/nocodb/src/lib/utils/Result.js @@ -0,0 +1,13 @@ +class Result { + + constructor(code = 0, message = '', data = {}) { + this.code = code; + this.message = message; + this.data = data; + } + + + +} + +module.exports = Result; diff --git a/packages/nocodb/src/lib/utils/common/NcConnectionMgr.ts b/packages/nocodb/src/lib/utils/common/NcConnectionMgr.ts index c81f2a961b..56b19d34e1 100644 --- a/packages/nocodb/src/lib/utils/common/NcConnectionMgr.ts +++ b/packages/nocodb/src/lib/utils/common/NcConnectionMgr.ts @@ -1,9 +1,9 @@ +import SqlClientFactory from '../../db/sql-client/lib/SqlClientFactory'; import { XKnex } from '../../db/sql-data-mapper'; import { NcConfig } from '../../../interface/config'; import fs from 'fs'; import Knex from 'knex'; -import { SqlClientFactory } from 'nc-help'; import NcMetaIO from '../../meta/NcMetaIO'; import { defaultConnectionConfig } from '../NcConfigFactory'; diff --git a/packages/nocodb/src/lib/utils/common/NcConnectionMgrv2.ts b/packages/nocodb/src/lib/utils/common/NcConnectionMgrv2.ts index 05607a50a7..d6cbde2d71 100644 --- a/packages/nocodb/src/lib/utils/common/NcConnectionMgrv2.ts +++ b/packages/nocodb/src/lib/utils/common/NcConnectionMgrv2.ts @@ -1,9 +1,9 @@ +import SqlClientFactory from '../../db/sql-client/lib/SqlClientFactory'; import { XKnex } from '../../db/sql-data-mapper'; // import { NcConfig } from '../../../interface/config'; // import fs from 'fs'; // import Knex from 'knex'; -import { SqlClientFactory } from 'nc-help'; // import NcMetaIO from '../meta/NcMetaIO'; import { defaultConnectionConfig } from '../NcConfigFactory'; import Base from '../../models/Base'; diff --git a/packages/nocodb/src/lib/v1-legacy/NcProjectBuilder.ts b/packages/nocodb/src/lib/v1-legacy/NcProjectBuilder.ts index 4dc93ab1b6..2416fecf97 100644 --- a/packages/nocodb/src/lib/v1-legacy/NcProjectBuilder.ts +++ b/packages/nocodb/src/lib/v1-legacy/NcProjectBuilder.ts @@ -3,9 +3,10 @@ import path from 'path'; import axios from 'axios'; import { Router } from 'express'; -import { SqlClientFactory, Tele } from 'nc-help'; +import { Tele } from 'nc-help'; import { NcConfig } from '../../interface/config'; +import SqlClientFactory from '../db/sql-client/lib/SqlClientFactory'; import Migrator from '../db/sql-migrator/lib/KnexMigrator'; import Noco from '../Noco'; From 8b307993b11a4e7a17bc296220ba25a8fb9201b3 Mon Sep 17 00:00:00 2001 From: Pranav C <61551451+pranavxc@users.noreply.github.com> Date: Sun, 3 Jul 2022 00:11:00 +0530 Subject: [PATCH 2/2] refactor: migrate sqlclient from nc-help re #336, #2504 Signed-off-by: Pranav C --- .../db/sql-client/lib/sqlite/SqliteClient.ts | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/packages/nocodb/src/lib/db/sql-client/lib/sqlite/SqliteClient.ts b/packages/nocodb/src/lib/db/sql-client/lib/sqlite/SqliteClient.ts index b0ad830a8a..494b798428 100644 --- a/packages/nocodb/src/lib/db/sql-client/lib/sqlite/SqliteClient.ts +++ b/packages/nocodb/src/lib/db/sql-client/lib/sqlite/SqliteClient.ts @@ -166,7 +166,7 @@ class SqliteClient extends KnexClient { * @param args {tn} * @returns */ - async createTableIfNotExists(args: any) { + async createTableIfNotExists(args: any = {}) { const _func = this.createTableIfNotExists.name; const result = new Result(); log.api(`${_func}:args:`, args); @@ -247,7 +247,7 @@ class SqliteClient extends KnexClient { return result; } - async hasDatabase(args: any) { + async hasDatabase(args: any = {}) { const _func = this.hasDatabase.name; const result = new Result(); log.api(`${_func}:args:`, args); @@ -303,7 +303,7 @@ class SqliteClient extends KnexClient { return result; } - async schemaList(args: any) { + async schemaList(args: any = {}) { const _func = this.createDatabaseIfNotExists.name; const result = new Result(); log.api(`${_func}:args:`, args); @@ -339,7 +339,7 @@ class SqliteClient extends KnexClient { * @property {String} - columns[].cc - * @property {String} - columns[].csn - */ - async columnList(args: any) { + async columnList(args: any = {}) { const _func = this.columnList.name; const result = new Result(); log.api(`${_func}:args:`, args); @@ -440,7 +440,7 @@ class SqliteClient extends KnexClient { * @property {String} - indexes[].comment - * @property {String} - indexes[].index_comment - */ - async indexList(args: any) { + async indexList(args: any = {}) { const _func = this.indexList.name; const result = new Result(); log.api(`${_func}:args:`, args); @@ -509,7 +509,7 @@ class SqliteClient extends KnexClient { * @property {String} - relations[].dr - * @property {String} - relations[].mo - */ - async relationList(args: any) { + async relationList(args: any = {}) { const _func = this.relationList.name; const result = new Result(); log.api(`${_func}:args:`, args); @@ -557,7 +557,7 @@ class SqliteClient extends KnexClient { * @property {String} - relations[].dr - * @property {String} - relations[].mo - */ - async relationListAll(args: any) { + async relationListAll(args: any = {}) { const _func = this.relationList.name; const result = new Result(); log.api(`${_func}:args:`, args); @@ -611,7 +611,7 @@ class SqliteClient extends KnexClient { * @property {String} - triggers[].collation_connection * @property {String} - triggers[].database collation */ - async triggerList(args: any) { + async triggerList(args: any = {}) { const _func = this.triggerList.name; const result = new Result(); log.api(`${_func}:args:`, args); @@ -654,7 +654,7 @@ class SqliteClient extends KnexClient { * @property {String} - functions[].collation_connection * @property {String} - functions[].database collation */ - async functionList(args: any) { + async functionList(args: any = {}) { const _func = this.functionList.name; const result = new Result(); log.api(`${_func}:args:`, args); @@ -708,7 +708,7 @@ class SqliteClient extends KnexClient { * @property {String} - procedures[].collation_connection * @property {String} - procedures[].database collation */ - async procedureList(args: any) { + async procedureList(args: any = {}) { const _func = this.procedureList.name; const result = new Result(); log.api(`${_func}:args:`, args); @@ -753,7 +753,7 @@ class SqliteClient extends KnexClient { * @property {String} - views[].view_name * @property {String} - views[].view_definition */ - async viewList(args: any) { + async viewList(args: any = {}) { const _func = this.viewList.name; const result = new Result(); log.api(`${_func}:args:`, args); @@ -791,7 +791,7 @@ class SqliteClient extends KnexClient { * @property {String} - collation_connection * @property {String} - character_set_client */ - async functionRead(args: any) { + async functionRead(args: any = {}) { const _func = this.functionRead.name; const result = new Result(); log.api(`${_func}:args:`, args); @@ -843,7 +843,7 @@ class SqliteClient extends KnexClient { * @property {String} - collation_connection * @property {String} - character_set_client */ - async procedureRead(args: any) { + async procedureRead(args: any = {}) { const _func = this.procedureRead.name; const result = new Result(); log.api(`${_func}:args:`, args); @@ -891,7 +891,7 @@ class SqliteClient extends KnexClient { * @returns {Object[]} - views * @property {String} - views[].tn */ - async viewRead(args: any) { + async viewRead(args: any = {}) { const _func = this.viewRead.name; const result = new Result(); log.api(`${_func}:args:`, args); @@ -916,7 +916,7 @@ class SqliteClient extends KnexClient { return result; } - async triggerRead(args: any) { + async triggerRead(args: any = {}) { const _func = this.triggerRead.name; const result = new Result(); log.api(`${_func}:args:`, args); @@ -951,7 +951,7 @@ class SqliteClient extends KnexClient { return result; } - async schemaCreate(args: any) { + async schemaCreate(args: any = {}) { const _func = this.schemaCreate.name; // const result = new Result(); log.api(`${_func}:args:`, args); @@ -962,7 +962,7 @@ class SqliteClient extends KnexClient { return rows; } - async schemaDelete(args: any) { + async schemaDelete(args: any = {}) { const _func = this.schemaDelete.name; // const result = new Result(); log.api(`${_func}:args:`, args); @@ -974,7 +974,7 @@ class SqliteClient extends KnexClient { } /** ************** END : sql queries *************** */ - async triggerDelete(args: any) { + async triggerDelete(args: any = {}) { const _func = this.triggerDelete.name; const result = new Result(); log.api(`${_func}:args:`, args); @@ -992,7 +992,7 @@ class SqliteClient extends KnexClient { return result; } - async functionDelete(args: any) { + async functionDelete(args: any = {}) { const _func = this.createDatabaseIfNotExists.name; // const result = new Result(); log.api(`${_func}:args:`, args); @@ -1003,7 +1003,7 @@ class SqliteClient extends KnexClient { return rows; } - async procedureDelete(args: any) { + async procedureDelete(args: any = {}) { const _func = this.procedureDelete.name; // const result = new Result(); log.api(`${_func}:args:`, args); @@ -1054,7 +1054,7 @@ class SqliteClient extends KnexClient { * @param {String} - args.timing * @returns {Object[]} - result rows */ - async functionCreate(args: any) { + async functionCreate(args: any = {}) { const func = this.functionCreate.name; const result = new Result(); log.api(`${func}:args:`, args); @@ -1081,7 +1081,7 @@ class SqliteClient extends KnexClient { * @param {String} - args.timing * @returns {Object[]} - result rows */ - async functionUpdate(args: any) { + async functionUpdate(args: any = {}) { const func = this.functionUpdate.name; const result = new Result(); log.api(`${func}:args:`, args); @@ -1109,7 +1109,7 @@ class SqliteClient extends KnexClient { * @param {String} - args.timing * @returns {Object[]} - result rows */ - async procedureCreate(args: any) { + async procedureCreate(args: any = {}) { const func = this.procedureCreate.name; const result = new Result(); log.api(`${func}:args:`, args); @@ -1136,7 +1136,7 @@ class SqliteClient extends KnexClient { * @param {String} - args.timing * @returns {Object[]} - result rows */ - async procedureUpdate(args: any) { + async procedureUpdate(args: any = {}) { const func = this.procedureUpdate.name; const result = new Result(); log.api(`${func}:args:`, args); @@ -1164,7 +1164,7 @@ class SqliteClient extends KnexClient { * @param {String} - args.timing * @returns {Object[]} - result rows */ - async triggerCreate(args: any) { + async triggerCreate(args: any = {}) { const func = this.triggerCreate.name; const result = new Result(); log.api(`${func}:args:`, args); @@ -1196,7 +1196,7 @@ class SqliteClient extends KnexClient { * @param {String} - args.oldStatement * @returns {Object[]} - result rows */ - async triggerUpdate(args: any) { + async triggerUpdate(args: any = {}) { const func = this.triggerUpdate.name; const result = new Result(); log.api(`${func}:args:`, args); @@ -1228,7 +1228,7 @@ class SqliteClient extends KnexClient { * @param {String} - args.view_definition * @returns {Object} - up and down statements */ - async viewCreate(args: any) { + async viewCreate(args: any = {}) { const func = this.viewCreate.name; const result = new Result(); log.api(`${func}:args:`, args); @@ -1258,7 +1258,7 @@ class SqliteClient extends KnexClient { * @param {String} - args.oldViewDefination * @returns {Object} - up and down statements */ - async viewUpdate(args: any) { + async viewUpdate(args: any = {}) { const func = this.viewUpdate.name; const result = new Result(); log.api(`${func}:args:`, args); @@ -1290,7 +1290,7 @@ class SqliteClient extends KnexClient { * @param {String} - args.oldViewDefination * @returns {Object} - up and down statements */ - async viewDelete(args: any) { + async viewDelete(args: any = {}) { const func = this.viewDelete.name; const result = new Result(); log.api(`${func}:args:`, args); @@ -2004,7 +2004,7 @@ class SqliteClient extends KnexClient { * @returns {Number} code * @returns {String} message */ - async totalRecords(args: any) { + async totalRecords(args: any = {}) { const func = this.totalRecords.name; const result = new Result(); log.api(`${func}:args:`, args);