diff --git a/packages/nc-gui/components/dashboard/settings/data-sources/EditBase.vue b/packages/nc-gui/components/dashboard/settings/data-sources/EditBase.vue
index 0798a35964..e4cd16c3c7 100644
--- a/packages/nc-gui/components/dashboard/settings/data-sources/EditBase.vue
+++ b/packages/nc-gui/components/dashboard/settings/data-sources/EditBase.vue
@@ -437,6 +437,40 @@ onMounted(async () => {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/nc-gui/components/template/Editor.vue b/packages/nc-gui/components/template/Editor.vue
index 2c9e98f1f3..5128fdab0c 100644
--- a/packages/nc-gui/components/template/Editor.vue
+++ b/packages/nc-gui/components/template/Editor.vue
@@ -306,7 +306,7 @@ function remapColNames(batchData: any[], columns: ColumnType[]) {
function missingRequiredColumnsValidation(tn: string) {
const missingRequiredColumns = columns.value.filter(
(c: Record) =>
- (c.pk ? !c.ai && !c.cdf : !c.cdf && c.rqd) &&
+ (c.pk ? !c.ai && !c.cdf && !c.meta?.ag : !c.cdf && c.rqd) &&
!srcDestMapping.value[tn].some((r: Record) => r.destCn === c.title),
)
diff --git a/packages/nc-gui/lib/enums.ts b/packages/nc-gui/lib/enums.ts
index 3fbd9746e3..edfa445729 100644
--- a/packages/nc-gui/lib/enums.ts
+++ b/packages/nc-gui/lib/enums.ts
@@ -5,6 +5,7 @@ export enum ClientType {
SQLITE = 'sqlite3',
VITESS = 'vitess',
SNOWFLAKE = 'snowflake',
+ DATABRICKS = 'databricks',
}
export enum Language {
diff --git a/packages/nc-gui/utils/baseCreateUtils.ts b/packages/nc-gui/utils/baseCreateUtils.ts
index cf324a2e38..a86495fbd7 100644
--- a/packages/nc-gui/utils/baseCreateUtils.ts
+++ b/packages/nc-gui/utils/baseCreateUtils.ts
@@ -9,7 +9,7 @@ interface ProjectCreateForm {
title: string
dataSource: {
client: ClientType
- connection: DefaultConnection | SQLiteConnection | SnowflakeConnection
+ connection: DefaultConnection | SQLiteConnection | SnowflakeConnection | DatabricksConnection
searchPath?: string[]
}
inflection: {
@@ -47,6 +47,14 @@ interface SnowflakeConnection {
schema: string
}
+interface DatabricksConnection {
+ token: string
+ host: string
+ path: string
+ database: string
+ schema: string
+}
+
const defaultHost = 'localhost'
const testDataBaseNames = {
@@ -84,12 +92,16 @@ export const clientTypes = [
text: 'Snowflake',
value: ClientType.SNOWFLAKE,
},
+ {
+ text: 'Databricks',
+ value: ClientType.DATABRICKS,
+ },
]
const homeDir = ''
type ConnectionClientType =
- | Exclude
+ | Exclude
| 'tidb'
| 'yugabyte'
| 'citusdb'
@@ -99,7 +111,7 @@ type ConnectionClientType =
const sampleConnectionData: { [key in ConnectionClientType]: DefaultConnection } & { [ClientType.SQLITE]: SQLiteConnection } & {
[ClientType.SNOWFLAKE]: SnowflakeConnection
-} = {
+} & { [ClientType.DATABRICKS]: DatabricksConnection } = {
[ClientType.PG]: {
host: defaultHost,
port: '5432',
@@ -144,6 +156,13 @@ const sampleConnectionData: { [key in ConnectionClientType]: DefaultConnection }
database: 'DATABASE',
schema: 'PUBLIC',
},
+ [ClientType.DATABRICKS]: {
+ token: 'dapiPLACEHOLDER',
+ host: 'PLACEHOLDER.cloud.databricks.com',
+ path: '/sql/1.0/warehouses/PLACEHOLDER',
+ database: 'database',
+ schema: 'default',
+ },
tidb: {
host: defaultHost,
port: '4000',
@@ -215,4 +234,4 @@ enum CertTypes {
key = 'key',
}
-export { SSLUsage, CertTypes, ProjectCreateForm, DefaultConnection, SQLiteConnection, SnowflakeConnection }
+export { SSLUsage, CertTypes, ProjectCreateForm, DefaultConnection, SQLiteConnection, SnowflakeConnection, DatabricksConnection }
diff --git a/packages/nocodb-sdk/src/lib/sqlUi/DatabricksUi.ts b/packages/nocodb-sdk/src/lib/sqlUi/DatabricksUi.ts
new file mode 100644
index 0000000000..d14176f473
--- /dev/null
+++ b/packages/nocodb-sdk/src/lib/sqlUi/DatabricksUi.ts
@@ -0,0 +1,820 @@
+import UITypes from '../UITypes';
+import { IDType } from './index';
+
+const dbTypes = [
+ 'BIGINT',
+ 'BINARY',
+ 'BOOLEAN',
+ 'DATE',
+ 'DECIMAL',
+ 'DOUBLE',
+ 'FLOAT',
+ 'INT',
+ 'INTERVAL',
+ 'VOID',
+ 'SMALLINT',
+ 'STRING',
+ 'TIMESTAMP',
+ 'TIMESTAMP_NTZ',
+ 'TINYINT',
+];
+
+export class DatabricksUi {
+ static getNewTableColumns() {
+ return [
+ {
+ column_name: 'id',
+ title: 'Id',
+ dt: 'int',
+ dtx: 'int',
+ ct: 'int',
+ nrqd: false,
+ rqd: true,
+ ck: false,
+ pk: true,
+ un: false,
+ ai: true,
+ cdf: null,
+ clen: null,
+ np: null,
+ ns: 0,
+ dtxp: '',
+ dtxs: '',
+ altered: 1,
+ uidt: 'ID',
+ uip: '',
+ uicn: '',
+ },
+ {
+ column_name: 'title',
+ title: 'Title',
+ dt: 'string',
+ dtx: 'specificType',
+ ct: 'string',
+ nrqd: true,
+ rqd: false,
+ ck: false,
+ pk: false,
+ un: false,
+ ai: false,
+ cdf: null,
+ clen: 45,
+ np: null,
+ ns: null,
+ dtxp: '',
+ dtxs: '',
+ altered: 1,
+ uidt: 'SingleLineText',
+ uip: '',
+ uicn: '',
+ },
+ {
+ column_name: 'created_at',
+ title: 'CreatedAt',
+ dt: 'TIMESTAMP',
+ dtx: 'specificType',
+ ct: 'TIMESTAMP',
+ nrqd: true,
+ rqd: false,
+ ck: false,
+ pk: false,
+ un: false,
+ ai: false,
+ clen: 45,
+ np: null,
+ ns: null,
+ dtxp: '',
+ dtxs: '',
+ altered: 1,
+ uidt: UITypes.CreatedTime,
+ uip: '',
+ uicn: '',
+ system: true,
+ },
+ {
+ column_name: 'updated_at',
+ title: 'UpdatedAt',
+ dt: 'TIMESTAMP',
+ dtx: 'specificType',
+ ct: 'TIMESTAMP',
+ nrqd: true,
+ rqd: false,
+ ck: false,
+ pk: false,
+ un: false,
+ ai: false,
+ clen: 45,
+ np: null,
+ ns: null,
+ dtxp: '',
+ dtxs: '',
+ altered: 1,
+ uidt: UITypes.LastModifiedTime,
+ uip: '',
+ uicn: '',
+ system: true,
+ },
+ {
+ column_name: 'created_by',
+ title: 'nc_created_by',
+ dt: 'string',
+ dtx: 'specificType',
+ ct: 'string',
+ nrqd: true,
+ rqd: false,
+ ck: false,
+ pk: false,
+ un: false,
+ ai: false,
+ clen: 45,
+ np: null,
+ ns: null,
+ dtxp: '',
+ dtxs: '',
+ altered: 1,
+ uidt: UITypes.CreatedBy,
+ uip: '',
+ uicn: '',
+ system: true,
+ },
+ {
+ column_name: 'updated_by',
+ title: 'nc_updated_by',
+ dt: 'string',
+ dtx: 'specificType',
+ ct: 'string',
+ nrqd: true,
+ rqd: false,
+ ck: false,
+ pk: false,
+ un: false,
+ ai: false,
+ clen: 45,
+ np: null,
+ ns: null,
+ dtxp: '',
+ dtxs: '',
+ altered: 1,
+ uidt: UITypes.LastModifiedBy,
+ uip: '',
+ uicn: '',
+ system: true,
+ },
+ ];
+ }
+
+ static getNewColumn(suffix) {
+ return {
+ column_name: 'title' + suffix,
+ dt: 'string',
+ dtx: 'specificType',
+ ct: 'string',
+ nrqd: true,
+ rqd: false,
+ ck: false,
+ pk: false,
+ un: false,
+ ai: false,
+ cdf: null,
+ clen: 45,
+ np: null,
+ ns: null,
+ dtxp: '',
+ dtxs: '',
+ altered: 1,
+ uidt: 'SingleLineText',
+ uip: '',
+ uicn: '',
+ };
+ }
+
+ static getDefaultLengthForDatatype(_type) {
+ return '';
+ }
+
+ static getDefaultLengthIsDisabled(type): any {
+ switch (type) {
+ case 'decimal':
+ return true;
+
+ case 'text':
+ return false;
+ }
+ }
+
+ static getDefaultValueForDatatype(type): any {
+ switch (type) {
+ case 'integer':
+ return 'eg : ' + 10;
+
+ case 'text':
+ return 'eg : hey';
+
+ case 'numeric':
+ return 'eg : ' + 10;
+
+ case 'real':
+ return 'eg : ' + 10.0;
+
+ case 'blob':
+ return 'eg : ' + 100;
+ }
+ }
+
+ static getDefaultScaleForDatatype(type): any {
+ switch (type) {
+ case 'integer':
+ case 'text':
+ case 'numeric':
+ case 'real':
+ case 'blob':
+ return ' ';
+ }
+ }
+
+ static colPropAIDisabled(col, columns) {
+ // console.log(col);
+ if (col.dt === 'integer') {
+ for (let i = 0; i < columns.length; ++i) {
+ if (columns[i].cn !== col.cn && columns[i].ai) {
+ return true;
+ }
+ }
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ static colPropUNDisabled(_col) {
+ return true;
+ }
+
+ static onCheckboxChangeAI(col) {
+ console.log(col);
+ if (
+ col.dt === 'int' ||
+ col.dt === 'bigint' ||
+ col.dt === 'smallint' ||
+ col.dt === 'tinyint'
+ ) {
+ col.altered = col.altered || 2;
+ }
+ }
+
+ static showScale(_columnObj) {
+ return false;
+ }
+
+ static removeUnsigned(columns) {
+ for (let i = 0; i < columns.length; ++i) {
+ if (
+ columns[i].altered === 1 &&
+ !(
+ columns[i].dt === 'int' ||
+ columns[i].dt === 'bigint' ||
+ columns[i].dt === 'tinyint' ||
+ columns[i].dt === 'smallint' ||
+ columns[i].dt === 'mediumint'
+ )
+ ) {
+ columns[i].un = false;
+ console.log('>> resetting unsigned value', columns[i].cn);
+ }
+ console.log(columns[i].cn);
+ }
+ }
+
+ /*static extractFunctionName(query) {
+ const reg =
+ /^\s*CREATE\s+(?:OR\s+REPLACE\s*)?\s*FUNCTION\s+(?:[\w\d_]+\.)?([\w_\d]+)/i;
+ const match = query.match(reg);
+ return match && match[1];
+ }
+
+ static extractProcedureName(query) {
+ const reg =
+ /^\s*CREATE\s+(?:OR\s+REPLACE\s*)?\s*PROCEDURE\s+(?:[\w\d_]+\.)?([\w_\d]+)/i;
+ const match = query.match(reg);
+ return match && match[1];
+ }*/
+ static columnEditable(_colObj) {
+ return true; // colObj.altered === 1;
+ }
+
+ /*static handleRawOutput(result, headers) {
+ console.log(result);
+ if (Array.isArray(result) && result[0]) {
+ const keys = Object.keys(result[0]);
+ // set headers before settings result
+ for (let i = 0; i < keys.length; i++) {
+ const text = keys[i];
+ headers.push({ text, value: text, sortable: false });
+ }
+ }
+ return result;
+ }
+
+ static splitQueries(query) {
+ /!***
+ * we are splitting based on semicolon
+ * there are mechanism to escape semicolon within single/double quotes(string)
+ *!/
+ return query.match(/\b("[^"]*;[^"]*"|'[^']*;[^']*'|[^;])*;/g);
+ }
+
+ /!**
+ * if sql statement is SELECT - it limits to a number
+ * @param args
+ * @returns {string|*}
+ *!/
+ sanitiseQuery(args) {
+ let q = args.query.trim().split(';');
+
+ if (q[0].startsWith('Select')) {
+ q = q[0] + ` LIMIT 0,${args.limit ? args.limit : 100};`;
+ } else if (q[0].startsWith('select')) {
+ q = q[0] + ` LIMIT 0,${args.limit ? args.limit : 100};`;
+ } else if (q[0].startsWith('SELECT')) {
+ q = q[0] + ` LIMIT 0,${args.limit ? args.limit : 100};`;
+ } else {
+ return args.query;
+ }
+
+ return q;
+ }
+
+ static getColumnsFromJson(json, tn) {
+ const columns = [];
+
+ try {
+ if (typeof json === 'object' && !Array.isArray(json)) {
+ const keys = Object.keys(json);
+ for (let i = 0; i < keys.length; ++i) {
+ const column = {
+ dp: null,
+ tn,
+ column_name: keys[i],
+ cno: keys[i],
+ np: null,
+ ns: null,
+ clen: null,
+ cop: 1,
+ pk: false,
+ nrqd: false,
+ rqd: false,
+ un: false,
+ ct: 'int(11) unsigned',
+ ai: false,
+ unique: false,
+ cdf: null,
+ cc: '',
+ csn: null,
+ dtx: 'specificType',
+ dtxp: null,
+ dtxs: 0,
+ altered: 1,
+ };
+
+ switch (typeof json[keys[i]]) {
+ case 'number':
+ if (Number.isInteger(json[keys[i]])) {
+ if (SqliteUi.isValidTimestamp(keys[i], json[keys[i]])) {
+ Object.assign(column, {
+ dt: 'timestamp',
+ });
+ } else {
+ Object.assign(column, {
+ dt: 'integer',
+ });
+ }
+ } else {
+ Object.assign(column, {
+ dt: 'real',
+ });
+ }
+ break;
+ case 'string':
+ // if (SqliteUi.isValidDate(json[keys[i]])) {
+ // Object.assign(column, {
+ // "dt": "datetime"
+ // });
+ // } else
+ if (json[keys[i]].length <= 255) {
+ Object.assign(column, {
+ dt: 'varchar',
+ });
+ } else {
+ Object.assign(column, {
+ dt: 'text',
+ });
+ }
+ break;
+ case 'boolean':
+ Object.assign(column, {
+ dt: 'integer',
+ });
+ break;
+ case 'object':
+ Object.assign(column, {
+ dt: 'text',
+ np: null,
+ dtxp: null,
+ });
+ break;
+ default:
+ break;
+ }
+ columns.push(column);
+ }
+ }
+ } catch (e) {
+ console.log('Error in getColumnsFromJson', e);
+ }
+
+ return columns;
+ }
+
+ static isValidTimestamp(key, value) {
+ if (typeof value !== 'number') {
+ return false;
+ }
+ return new Date(value).getTime() > 0 && /(?:_|(?=A))[aA]t$/.test(key);
+ }
+
+ static isValidDate(value) {
+ return new Date(value).getTime() > 0;
+ }*/
+
+ static onCheckboxChangeAU(col) {
+ console.log(col);
+ // if (1) {
+ col.altered = col.altered || 2;
+ // }
+
+ // if (!col.ai) {
+ // col.dtx = 'specificType'
+ // } else {
+ // col.dtx = ''
+ // }
+ }
+
+ static colPropAuDisabled(col) {
+ if (col.altered !== 1) {
+ return true;
+ }
+
+ switch (col.dt) {
+ case 'date':
+ case 'datetime':
+ case 'timestamp':
+ case 'time':
+ return false;
+
+ default:
+ return true;
+ }
+ }
+
+ static getAbstractType(col): any {
+ switch (col.dt?.replace(/\(\d+\)$/).toLowerCase()) {
+ case 'bigint':
+ case 'tinyint':
+ case 'int':
+ case 'smallint':
+ return 'integer';
+ case 'decimal':
+ case 'double':
+ case 'float':
+ return 'float';
+ case 'boolean':
+ return 'boolean';
+ case 'timestamp':
+ case 'timestamp_ntz':
+ return 'datetime';
+ case 'date':
+ return 'date';
+ case 'string':
+ return 'string';
+ }
+ return 'string';
+ }
+
+ static getUIType(col): any {
+ switch (this.getAbstractType(col)) {
+ case 'integer':
+ return 'Number';
+ case 'boolean':
+ return 'Checkbox';
+ case 'float':
+ return 'Decimal';
+ case 'date':
+ return 'Date';
+ case 'datetime':
+ return 'CreatedTime';
+ case 'time':
+ return 'Time';
+ case 'year':
+ return 'Year';
+ case 'string':
+ return 'SingleLineText';
+ case 'text':
+ return 'LongText';
+ case 'blob':
+ return 'Attachment';
+ case 'enum':
+ return 'SingleSelect';
+ case 'set':
+ return 'MultiSelect';
+ case 'json':
+ return 'LongText';
+ }
+ }
+
+ static getDataTypeForUiType(col: { uidt: UITypes; }, idType?: IDType) {
+ const colProp: any = {};
+ switch (col.uidt) {
+ case 'ID':
+ {
+ const isAutoIncId = idType === 'AI';
+ const isAutoGenId = idType === 'AG';
+ colProp.dt = isAutoGenId ? 'varchar' : 'integer';
+ colProp.pk = true;
+ colProp.un = isAutoIncId;
+ colProp.ai = isAutoIncId;
+ colProp.rqd = true;
+ colProp.meta = isAutoGenId ? { ag: 'nc' } : undefined;
+ }
+ break;
+ case 'ForeignKey':
+ colProp.dt = 'string';
+ break;
+ case 'SingleLineText':
+ colProp.dt = 'string';
+ break;
+ case 'LongText':
+ colProp.dt = 'string';
+ break;
+ case 'Attachment':
+ colProp.dt = 'string';
+ break;
+ case 'GeoData':
+ colProp.dt = 'string';
+ break;
+ case 'Checkbox':
+ colProp.dt = 'boolean';
+ break;
+ case 'MultiSelect':
+ colProp.dt = 'string';
+ break;
+ case 'SingleSelect':
+ colProp.dt = 'string';
+ break;
+ case 'Collaborator':
+ colProp.dt = 'string';
+ break;
+ case 'Date':
+ colProp.dt = 'date';
+
+ break;
+ case 'Year':
+ colProp.dt = 'number';
+ break;
+ case 'Time':
+ colProp.dt = 'string';
+ break;
+ case 'PhoneNumber':
+ colProp.dt = 'string';
+ colProp.validate = {
+ func: ['isMobilePhone'],
+ args: [''],
+ msg: ['Validation failed : isMobilePhone'],
+ };
+ break;
+ case 'Email':
+ colProp.dt = 'string';
+ colProp.validate = {
+ func: ['isEmail'],
+ args: [''],
+ msg: ['Validation failed : isEmail'],
+ };
+ break;
+ case 'URL':
+ colProp.dt = 'string';
+ colProp.validate = {
+ func: ['isURL'],
+ args: [''],
+ msg: ['Validation failed : isURL'],
+ };
+ break;
+ case 'Number':
+ colProp.dt = 'int';
+ break;
+ case 'Decimal':
+ colProp.dt = 'decimal';
+ break;
+ case 'Currency':
+ colProp.dt = 'double';
+ colProp.validate = {
+ func: ['isCurrency'],
+ args: [''],
+ msg: ['Validation failed : isCurrency'],
+ };
+ break;
+ case 'Percent':
+ colProp.dt = 'double';
+ break;
+ case 'Duration':
+ colProp.dt = 'decimal';
+ break;
+ case 'Rating':
+ colProp.dt = 'int';
+ colProp.cdf = '0';
+ break;
+ case 'Formula':
+ colProp.dt = 'string';
+ break;
+ case 'Rollup':
+ colProp.dt = 'string';
+ break;
+ case 'Count':
+ colProp.dt = 'int';
+ break;
+ case 'Lookup':
+ colProp.dt = 'string';
+ break;
+ case 'DateTime':
+ colProp.dt = 'datetime';
+ break;
+ case 'CreatedTime':
+ colProp.dt = 'datetime';
+ break;
+ case 'LastModifiedTime':
+ colProp.dt = 'datetime';
+ break;
+ case 'AutoNumber':
+ colProp.dt = 'int';
+ break;
+ case 'Barcode':
+ colProp.dt = 'string';
+ break;
+ case 'Button':
+ colProp.dt = 'string';
+ break;
+ case 'JSON':
+ colProp.dt = 'string';
+ break;
+ default:
+ colProp.dt = 'string';
+ break;
+ }
+ return colProp;
+ }
+
+ static getDataTypeListForUiType(col: { uidt: UITypes; }, idType?: IDType) {
+ switch (col.uidt) {
+ case 'ID':
+ if (idType === 'AG') {
+ return ['character', 'text', 'varchar'];
+ } else if (idType === 'AI') {
+ return [
+ 'int',
+ 'integer',
+ 'tinyint',
+ 'smallint',
+ 'mediumint',
+ 'bigint',
+ 'int2',
+ 'int8',
+ ];
+ } else {
+ return dbTypes;
+ }
+ case 'ForeignKey':
+ return dbTypes;
+ case 'SingleLineText':
+ case 'LongText':
+ case 'Attachment':
+ case 'Collaborator':
+ case 'GeoData':
+ return ['string'];
+
+ case 'Checkbox':
+ return [
+ 'boolean',
+ ];
+
+ case 'MultiSelect':
+ return ['string'];
+
+ case 'SingleSelect':
+ return ['string'];
+
+ case 'Year':
+ return [
+ 'int',
+ ];
+
+ case 'Time':
+ return [
+ 'string',
+ ];
+
+ case 'PhoneNumber':
+ case 'Email':
+ return ['string'];
+
+ case 'URL':
+ return ['string'];
+
+ case 'Number':
+ return [
+ 'int',
+ ];
+
+ case 'Decimal':
+ return ['decimal', 'float', 'double'];
+
+ case 'Currency':
+ return [
+ 'decimal',
+ ];
+
+ case 'Percent':
+ return [
+ 'decimal',
+ ];
+
+ case 'Duration':
+ return [
+ 'decimal',
+ ];
+
+ case 'Rating':
+ return [
+ 'int',
+ ];
+
+ case 'Formula':
+ return ['string'];
+
+ case 'Rollup':
+ return ['string'];
+
+ case 'Count':
+ return [
+ 'int',
+ ];
+
+ case 'Lookup':
+ return ['string'];
+
+ case 'Date':
+ return ['date'];
+
+ case 'DateTime':
+ case 'CreatedTime':
+ case 'LastModifiedTime':
+ return ['datetime'];
+
+ case 'AutoNumber':
+ return [
+ 'int',
+ ];
+
+ case 'Barcode':
+ return ['string'];
+
+ case 'Geometry':
+ return ['string'];
+ case 'JSON':
+ return ['string'];
+
+ case 'Button':
+ default:
+ return dbTypes;
+ }
+ }
+
+ static getUnsupportedFnList() {
+ return [
+ 'LOG',
+ 'EXP',
+ 'POWER',
+ 'SQRT',
+ 'XOR',
+ 'REGEX_MATCH',
+ 'REGEX_EXTRACT',
+ 'REGEX_REPLACE',
+ 'VALUE',
+ 'COUNTA',
+ 'COUNT',
+ 'ROUNDDOWN',
+ 'ROUNDUP',
+ 'DATESTR',
+ 'DAY',
+ 'MONTH',
+ 'HOUR',
+ ];
+ }
+}
diff --git a/packages/nocodb-sdk/src/lib/sqlUi/SqlUiFactory.ts b/packages/nocodb-sdk/src/lib/sqlUi/SqlUiFactory.ts
index f12fd83ac4..82b2ed4f65 100644
--- a/packages/nocodb-sdk/src/lib/sqlUi/SqlUiFactory.ts
+++ b/packages/nocodb-sdk/src/lib/sqlUi/SqlUiFactory.ts
@@ -7,6 +7,7 @@ import { OracleUi } from './OracleUi';
import { PgUi } from './PgUi';
import { SqliteUi } from './SqliteUi';
import { SnowflakeUi } from './SnowflakeUi';
+import { DatabricksUi } from './DatabricksUi';
// import {YugabyteUi} from "./YugabyteUi";
// import {TidbUi} from "./TidbUi";
@@ -48,6 +49,10 @@ export class SqlUiFactory {
return SnowflakeUi;
}
+ if (connectionConfig.client === 'databricks') {
+ return DatabricksUi;
+ }
+
throw new Error('Database not supported');
}
}
diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts
index 41fd24021f..0c1f84f00f 100644
--- a/packages/nocodb/src/db/BaseModelSqlv2.ts
+++ b/packages/nocodb/src/db/BaseModelSqlv2.ts
@@ -2770,16 +2770,16 @@ class BaseModelSqlv2 {
{ raw: true, first: true },
)
)?.__nc_ai_id;
- } else if (this.isSnowflake) {
+ } else if (this.isSnowflake || this.isDatabricks) {
id = (
await this.execAndParse(
this.dbDriver(this.tnPath).max(ai.column_name, {
- as: 'id',
+ as: '__nc_ai_id',
}),
null,
{ raw: true, first: true },
)
- ).id;
+ ).__nc_ai_id;
}
response = await this.readByPk(
this.extractCompositePK({ rowId: id, insertObj, ag }),
@@ -3054,6 +3054,10 @@ class BaseModelSqlv2 {
return this.clientType === 'snowflake';
}
+ get isDatabricks() {
+ return this.clientType === 'databricks';
+ }
+
get clientType() {
return this.dbDriver.clientType();
}
@@ -3161,16 +3165,16 @@ class BaseModelSqlv2 {
},
)
)?.__nc_ai_id;
- } else if (this.isSnowflake) {
+ } else if (this.isSnowflake || this.isDatabricks) {
rowId = (
await this.execAndParse(
this.dbDriver(this.tnPath).max(ai.column_name, {
- as: 'id',
+ as: '__nc_ai_id',
}),
null,
{ raw: true, first: true },
)
- )?.id;
+ )?.__nc_ai_id;
}
// response = await this.readByPk(
// id,
@@ -4477,7 +4481,7 @@ class BaseModelSqlv2 {
const vTn = this.getTnPath(vTable);
- if (this.isSnowflake) {
+ if (this.isSnowflake || this.isDatabricks) {
const parentPK = this.dbDriver(parentTn)
.select(parentColumn.column_name)
.where(_wherePk(parentTable.primaryKeys, childId))
diff --git a/packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts b/packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts
index 6398fd185c..a6e57e777d 100644
--- a/packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts
+++ b/packages/nocodb/src/db/formulav2/formulaQueryBuilderv2.ts
@@ -833,6 +833,19 @@ async function _formulaQueryBuilder(
} else {
return fn(pt.arguments[0], a, prevBinaryOp);
}
+ } else if (knex.clientType() === 'databricks') {
+ const res = await mapFunctionName({
+ pt,
+ knex,
+ alias,
+ a,
+ aliasToCol: aliasToColumn,
+ fn,
+ colAlias,
+ prevBinaryOp,
+ model,
+ });
+ if (res) return res;
}
break;
case 'URL':
@@ -921,6 +934,19 @@ async function _formulaQueryBuilder(
if (typeof builder === 'function') {
return { builder: knex.raw(`??${colAlias}`, builder(pt.fnName)) };
}
+
+ if (
+ knex.clientType() === 'databricks' &&
+ builder.toQuery().endsWith(')')
+ ) {
+ // limit 1 for subquery
+ return {
+ builder: knex.raw(
+ `${builder.toQuery().replace(/\)$/, '')} LIMIT 1)${colAlias}`,
+ ),
+ };
+ }
+
return { builder: knex.raw(`??${colAlias}`, [builder || pt.name]) };
} else if (pt.type === 'BinaryExpression') {
// treat `&` as shortcut for concat
diff --git a/packages/nocodb/src/db/functionMappings/databricks.ts b/packages/nocodb/src/db/functionMappings/databricks.ts
new file mode 100644
index 0000000000..5b9291de0c
--- /dev/null
+++ b/packages/nocodb/src/db/functionMappings/databricks.ts
@@ -0,0 +1,44 @@
+import commonFns from './commonFns';
+import type { MapFnArgs } from '../mapFunctionName';
+
+const databricks = {
+ ...commonFns,
+ AND: async (args: MapFnArgs) => {
+ return {
+ builder: args.knex.raw(
+ `CASE WHEN ${args.knex
+ .raw(
+ `${(
+ await Promise.all(
+ args.pt.arguments.map(async (ar) =>
+ (await args.fn(ar, '', 'AND')).builder.toQuery(),
+ ),
+ )
+ ).join(' AND ')}`,
+ )
+ .wrap('(', ')')
+ .toQuery()} THEN TRUE ELSE FALSE END ${args.colAlias}`,
+ ),
+ };
+ },
+ OR: async (args: MapFnArgs) => {
+ return {
+ builder: args.knex.raw(
+ `CASE WHEN ${args.knex
+ .raw(
+ `${(
+ await Promise.all(
+ args.pt.arguments.map(async (ar) =>
+ (await args.fn(ar, '', 'OR')).builder.toQuery(),
+ ),
+ )
+ ).join(' OR ')}`,
+ )
+ .wrap('(', ')')
+ .toQuery()} THEN TRUE ELSE FALSE END ${args.colAlias}`,
+ ),
+ };
+ },
+};
+
+export default databricks;
diff --git a/packages/nocodb/src/db/mapFunctionName.ts b/packages/nocodb/src/db/mapFunctionName.ts
index 605f077f1d..70b19e4d73 100644
--- a/packages/nocodb/src/db/mapFunctionName.ts
+++ b/packages/nocodb/src/db/mapFunctionName.ts
@@ -5,6 +5,7 @@ import mssql from '~/db/functionMappings/mssql';
import mysql from '~/db/functionMappings/mysql';
import pg from '~/db/functionMappings/pg';
import sqlite from '~/db/functionMappings/sqlite';
+import databricks from '~/db/functionMappings/databricks';
export interface MapFnArgs {
pt: any;
@@ -42,6 +43,9 @@ const mapFunctionName = async (args: MapFnArgs): Promise => {
case 'sqlite3':
val = sqlite[name] || name;
break;
+ case 'databricks':
+ val = databricks[name] || name;
+ break;
}
if (typeof val === 'function') {
diff --git a/packages/nocodb/src/db/sql-client/lib/SqlClientFactory.ts b/packages/nocodb/src/db/sql-client/lib/SqlClientFactory.ts
index f63310236e..efcfc39297 100644
--- a/packages/nocodb/src/db/sql-client/lib/SqlClientFactory.ts
+++ b/packages/nocodb/src/db/sql-client/lib/SqlClientFactory.ts
@@ -1,5 +1,6 @@
import fs from 'fs';
import { promisify } from 'util';
+import { DatabricksClient } from 'nc-help';
import MySqlClient from '~/db/sql-client/lib/mysql/MysqlClient';
import MssqlClient from '~/db/sql-client/lib/mssql/MssqlClient';
import OracleClient from '~/db/sql-client/lib/oracle/OracleClient';
@@ -8,6 +9,7 @@ import PgClient from '~/db/sql-client/lib/pg/PgClient';
import YugabyteClient from '~/db/sql-client/lib/pg/YugabyteClient';
import TidbClient from '~/db/sql-client/lib/mysql/TidbClient';
import VitessClient from '~/db/sql-client/lib/mysql/VitessClient';
+import DbrClient from '~/db/sql-client/lib/databricks/DatabricksClient';
export class SqlClientFactory {
static create(connectionConfig) {
@@ -33,6 +35,10 @@ export class SqlClientFactory {
if (connectionConfig.meta.dbtype === 'yugabyte')
return new YugabyteClient(connectionConfig);
return new PgClient(connectionConfig);
+ } else if (connectionConfig.client === 'databricks') {
+ connectionConfig.client = DatabricksClient;
+ connectionConfig.pool = { min: 0, max: 1 };
+ return new DbrClient(connectionConfig);
}
throw new Error('Database not supported');
diff --git a/packages/nocodb/src/db/sql-client/lib/databricks/DatabricksClient.ts b/packages/nocodb/src/db/sql-client/lib/databricks/DatabricksClient.ts
new file mode 100644
index 0000000000..d6576de926
--- /dev/null
+++ b/packages/nocodb/src/db/sql-client/lib/databricks/DatabricksClient.ts
@@ -0,0 +1,2621 @@
+import { nanoid } from 'nanoid';
+import knex from 'knex';
+import isEmpty from 'lodash/isEmpty';
+import find from 'lodash/find';
+import KnexClient from '~/db/sql-client/lib/KnexClient';
+import Debug from '~/db/util/Debug';
+import Result from '~/db/util/Result';
+import queries from '~/db/sql-client/lib/pg/pg.queries';
+
+const log = new Debug('DatabricksClient');
+
+class DatabricksClient extends KnexClient {
+ protected queries: any;
+ protected _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 ${this.genIdentifier(
+ args.sequence_name,
+ )}`;
+ await this.sqlClient.raw(query);
+ result.data.object = {
+ upStatement: [{ sql: query }],
+ downStatement: [
+ {
+ sql: `${this.querySeparator()}CREATE SEQUENCE ${this.genIdentifier(
+ 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 {
+ result.data.list = [];
+ } 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 ${this.genIdentifier(args.sequence_name)}`;
+ await this.sqlClient.raw(query);
+ result.data.object = {
+ upStatement: [{ sql: query }],
+ downStatement: [
+ {
+ sql:
+ this.querySeparator() +
+ `DROP SEQUENCE ${this.genIdentifier(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 ${this.genIdentifier(
+ args.original_sequence_name,
+ )} RENAME TO ${this.genIdentifier(args.sequence_name)};`;
+ const downQuery =
+ this.querySeparator() +
+ `ALTER SEQUENCE ${this.genIdentifier(
+ args.sequence_name,
+ )} RENAME TO ${this.genIdentifier(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 (e) {
+ log.ppe(e);
+ result.code = -1;
+ // send back original error message
+ result.message = e.message;
+ } finally {
+ log.api(`${_func}:result:`, result);
+ }
+
+ return result;
+ }
+
+ getKnexDataTypes() {
+ const result = new Result();
+
+ result.data.list = [
+ 'BIGINT',
+ 'BINARY',
+ 'BOOLEAN',
+ 'DATE',
+ 'DECIMAL',
+ 'DOUBLE',
+ 'FLOAT',
+ 'INT',
+ 'INTERVAL',
+ 'VOID',
+ 'SMALLINT',
+ 'STRING',
+ 'TIMESTAMP',
+ 'TIMESTAMP_NTZ',
+ 'TINYINT',
+ ];
+
+ 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;
+
+ return;
+
+ try {
+ const connectionParamsWithoutDb = JSON.parse(
+ JSON.stringify(this.connectionConfig),
+ );
+ let rows = [];
+ try {
+ connectionParamsWithoutDb.connection.database = 'postgres';
+ tempSqlClient = knex({
+ ...connectionParamsWithoutDb,
+ pool: { min: 0, max: 1 },
+ });
+
+ 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 ?? ENCODING 'UTF8'`, [
+ args.database,
+ ]);
+ }
+
+ const schemaName = this.connectionConfig.schema || 'default';
+
+ // Check schemaExists because `CREATE SCHEMA IF NOT EXISTS` requires permissions of `CREATE ON DATABASE`
+ const schemaExists = !!(
+ await this.sqlClient.raw(
+ `SELECT schema_name FROM information_schema.schemata WHERE schema_name = ?`,
+ [schemaName],
+ )
+ ).rows?.[0];
+
+ if (!schemaExists) {
+ await this.sqlClient.raw(
+ `CREATE SCHEMA IF NOT EXISTS ?? AUTHORIZATION ?? `,
+ [schemaName, 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,
+ pool: { min: 0, max: 1 },
+ });
+ await this.sqlClient.destroy();
+ this.sqlClient = tempSqlClient;
+
+ await tempSqlClient.raw(
+ `ALTER 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, args.database],
+ );
+
+ log.debug('dropping database:', args);
+ await tempSqlClient.raw(`DROP DATABASE ??;`, [args.database]);
+ await tempSqlClient.destroy();
+ } 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 = ? and table_catalog = ?`,
+ [this.schema, args.tn, this.connectionConfig.connection.database],
+ );
+
+ if (exists.rows.length === 0) {
+ const data = await this.sqlClient.raw(
+ 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();
+ })
+ .toQuery(),
+ );
+ 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 = ?'`,
+ [this.schema, 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.sqlClient.raw(
+ `SELECT schema_name FROM schemata WHERE schema_name = ?`,
+ [args.database],
+ );
+ 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 schema_name
+ FROM schemata;`,
+ );
+ 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;
+ } 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 schema_name FROM schemata WHERE 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 {
+ let response = await this.sqlClient.raw(
+ `SELECT
+ c.table_name as tn,
+ c.column_name as cn,
+ c.full_data_type as dt,
+ c.numeric_precision as np,
+ c.numeric_scale as ns,
+ c.character_maximum_length as clen,
+ c.datetime_precision as dp,
+ c.column_default as cdf,
+ c.is_nullable as nrqd,
+ c.is_identity as ii,
+ c.ordinal_position as cop,
+ c.generation_expression,
+ c.character_octet_length,
+ pk.constraint_type as ck,
+ pk.constraint_name as pk_constraint_name,
+ c.comment as cc
+ 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')
+ order by table_name,ordinal_position ) pk
+ on
+ pk.table_name = c.table_name and pk.column_name=c.column_name
+ WHERE c.table_name = ?`,
+ [args.tn],
+ );
+
+ const columns = [];
+
+ // TODO: fix in driver
+ response = {
+ rows: response,
+ };
+
+ 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;
+ column.dtx = response.rows[i].dt;
+
+ column.ai = false;
+
+ column.pk = response.rows[i].ck === 'PRIMARY KEY';
+
+ if (column.pk && column.cn === 'id') {
+ column.meta = {
+ ag: 'nc',
+ }
+ } else if (column.pk && column.cdf === null && column.generation_expression === null) {
+ column.meta = {
+ ag: 'nc',
+ }
+ }
+
+ 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.cdf = response.rows[i].cdf
+ ? response.rows[i].cdf
+ .replace(/::[\w (),]+$/, '')
+ .replace(/^'|'$/g, '')
+ : response.rows[i].cdf;
+
+ column.cc = response.rows[i].cc;
+
+ column.dtxp =
+ response.rows[i].clen || response.rows[i].np || response.rows[i].dp;
+ column.dtxs = response.rows[i].ns;
+
+ if (response.rows[i].ii === 'YES') {
+ column.ai = true;
+ }
+
+ 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 {
+ result.data.list = [];
+ } catch (e) {
+ log.ppe(e, _func);
+ throw e;
+ }
+
+ log.api(`${_func}: result`, result);
+
+ 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;
+
+ args.childTableWithSchema = args.childTable;
+
+ args.parentTableWithSchema = args.parentTable;
+
+ try {
+ // const self = this;
+
+ await this.sqlClient.raw(
+ this.sqlClient.schema
+ .table(args.childTableWithSchema, function (table) {
+ table.dropForeign(args.childColumn, foreignKeyName);
+ })
+ .toQuery(),
+ );
+
+ const upStatement =
+ this.querySeparator() +
+ this.sqlClient.schema
+ .table(args.childTableWithSchema, function (table) {
+ table.dropForeign(args.childColumn, foreignKeyName);
+ })
+ .toQuery();
+
+ const downStatement =
+ this.querySeparator() +
+ this.sqlClient.schema
+ .table(args.childTableWithSchema, function (table) {
+ table
+ .foreign(args.childColumn, foreignKeyName)
+ .references(args.parentColumn)
+ .on(args.parentTableWithSchema);
+ })
+ .toQuery();
+
+ result.data.object = {
+ upStatement: [{ sql: upStatement }],
+ downStatement: [{ sql: downStatement }],
+ };
+ } 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(
+ `
+ 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=?
+ GROUP BY constraint_name, col.attnum, constraint_type, "schema", "table", definition
+ ORDER BY "schema", "table"; `,
+ [args.tn],
+ );
+
+ 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
+ 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
+ WHERE
+ tc.constraint_type = 'FOREIGN KEY'
+ AND tc.table_schema=:schema
+ AND tc.table_name=:table;`,
+ { schema: this.schema.toLowerCase(), table: (args.tn as string).toLowerCase() },
+ );
+
+ 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
+ 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
+ WHERE
+ tc.constraint_type = 'FOREIGN KEY'
+ AND tc.table_schema=?;`,
+ [this.schema.toLowerCase()],
+ );
+
+ 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 {
+ 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 {
+ result.data.list = [];
+ } 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);
+
+ try {
+ result.data.list = [];
+ } 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 = ?;`,
+ [this.schema],
+ );
+
+ 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 {
+ result.data.list = [];
+ } 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 {
+ 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 {
+ 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 {
+ 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);
+
+ 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 ${this.genIdentifier(
+ 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 ${this.genIdentifier(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 ${this.genIdentifier(
+ 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 ${this.genIdentifier(
+ args.function_declaration,
+ )};`,
+ );
+ await this.sqlClient.raw(upQuery);
+
+ const functionCreated = await this.functionRead({
+ function_name: args.function_name,
+ });
+
+ downQuery =
+ `DROP FUNCTION IF EXISTS ${this.genIdentifier(
+ 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 ${this.genIdentifier(args.procedure_name)} \n${
+ args.timing
+ } ${args.event}\nON ${this.genIdentifier(args.tn)} FOR EACH ROW\n${
+ args.statement
+ }`;
+ await this.sqlClient.raw(upQuery);
+ const downQuery =
+ this.querySeparator() +
+ `DROP PROCEDURE IF EXISTS ${this.genIdentifier(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 ${this.genIdentifier(args.procedure_name)} \n${
+ args.timing
+ } ${args.event}\nON ${this.genIdentifier(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 ${this.genIdentifier(args.trigger_name)} \n${
+ args.timing
+ } ${args.event}\nON ${this.genIdentifier(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 ${this.genIdentifier(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 ${this.genIdentifier(
+ args.trigger_name,
+ )} ON ${this.genIdentifier(args.tn)}`,
+ );
+ await this.sqlClient.raw(
+ `CREATE TRIGGER ${this.genIdentifier(args.trigger_name)} \n${
+ args.timing
+ } ${args.event}\nON ${this.genIdentifier(args.tn)} FOR EACH ROW\n${
+ args.statement
+ }`,
+ );
+
+ result.data.object = {
+ upStatement:
+ this.querySeparator() +
+ `DROP TRIGGER ${this.genIdentifier(
+ args.trigger_name,
+ )} ON ${this.genIdentifier(
+ args.tn,
+ )};${this.querySeparator()}CREATE TRIGGER ${this.genIdentifier(
+ args.trigger_name,
+ )} \n${args.timing} ${args.event}\nON ${this.genIdentifier(
+ args.tn,
+ )} FOR EACH ROW\n${args.statement}`,
+ downStatement:
+ this.querySeparator() +
+ `CREATE TRIGGER ${this.genIdentifier(args.trigger_name)} \n${
+ args.timing
+ } ${args.event}\nON ${this.genIdentifier(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 ${this.genIdentifier(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 ${this.genIdentifier(
+ 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 ${this.genIdentifier(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 ${this.genIdentifier(args.view_name)}`;
+
+ await this.sqlClient.raw(query);
+
+ result.data.object = {
+ upStatement: [{ sql: this.querySeparator() + query }],
+ downStatement: [
+ {
+ sql:
+ this.querySeparator() +
+ `CREATE VIEW ${this.genIdentifier(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.executeRawQuery(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: [] };
+ return result;
+ }
+
+ async afterTableUpdate(_args) {
+ const result = { upStatement: [], downStatement: [] };
+ 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 = 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};`;
+ }
+
+ if (upQuery !== '') await this.executeRawQuery(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) {
+ const query = this.sqlClient.raw(
+ 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.onDelete(relation.dr);
+ }
+ })
+ .toQuery(),
+ );
+
+ downQuery += this.querySeparator() + query;
+
+ await query;
+ }
+
+ 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.raw(
+ this.sqlClient.schema.dropTable(args.tn).toQuery(),
+ );
+
+ /** ************** 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}) TBLPROPERTIES('delta.columnMapping.mode' = 'name', 'delta.minReaderVersion' = '2', 'delta.minWriterVersion' = '5');`, [args.tn]);
+
+ return query;
+ }
+
+ alterTableColumn(t, n, o, existingQuery, change = 2) {
+ let query = '';
+
+ const defaultValue = this.sanitiseDefaultValue(n.cdf);
+ const shouldSanitize = true;
+
+ if (change === 0) {
+ query = existingQuery ? ',' : '';
+ if (n.ai) {
+ query += this.genQuery(
+ ` ?? VARCHAR(255) PRIMARY KEY`,
+ [n.cn],
+ shouldSanitize,
+ );
+ } else {
+ query += this.genQuery(
+ ` ?? ${this.sanitiseDataType(n.dt)}`,
+ [n.cn],
+ shouldSanitize,
+ );
+ query += n.rqd ? ' NOT NULL' : '';
+ query += defaultValue ? ` DEFAULT ${defaultValue}` : '';
+ }
+ } else if (change === 1) {
+ query += this.genQuery(
+ ` ADD COLUMN ?? ${this.sanitiseDataType(n.dt)}`,
+ [n.cn],
+ shouldSanitize,
+ );
+ query += n.rqd ? ' NOT NULL' : '';
+ query = this.genQuery(`ALTER TABLE ?? ${query};`, [t], shouldSanitize);
+
+ if (defaultValue) {
+ query += this.genQuery(
+ `ALTER TABLE ?? ALTER COLUMN ?? SET DEFAULT ${defaultValue};`,
+ [t, n.cn],
+ 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 ${this.sanitiseDataType(
+ n.dt,
+ )};\n`,
+ [t, 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 ${this.sanitiseDefaultValue(n.cdf)};\n`
+ : ` DROP DEFAULT;\n`;
+ }
+ }
+ return query;
+ }
+
+ get schema(): string {
+ return (this.connectionConfig && this.connectionConfig.schema) || 'default';
+ }
+
+ /**
+ *
+ * @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;
+ }
+
+ // get default bytea output format
+ async getDefaultByteaOutputFormat() {
+ const func = this.getDefaultByteaOutputFormat.name;
+ const result = new Result<'hex' | 'escape'>();
+ log.api(`${func}:args:`, {});
+
+ try {
+ const data = await this.sqlClient.raw(`SHOW bytea_output;`);
+ result.data = data.rows?.[0]?.bytea_output;
+ } catch (e) {
+ result.data = 'escape';
+ } finally {
+ log.api(`${func} :result: ${result}`);
+ }
+ 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.raw(
+ this.sqlClient.schema
+ .renameTable(
+ this.sqlClient.raw('??.??', [this.schema, args.tn_old]),
+ args.tn,
+ )
+ .toQuery(),
+ );
+
+ /** ************** create up & down statements *************** */
+ const upStatement =
+ this.querySeparator() +
+ this.sqlClient.schema
+ .renameTable(
+ this.sqlClient.raw('??.??', [this.schema, args.tn]),
+ args.tn_old,
+ )
+ .toQuery();
+
+ this.emit(`Success : ${upStatement}`);
+
+ const downStatement =
+ this.querySeparator() +
+ this.sqlClient.schema
+ .renameTable(
+ this.sqlClient.raw('??.??', [this.schema, args.tn_old]),
+ args.tn,
+ )
+ .toQuery();
+
+ /** ************** return files *************** */
+ result.data.object = {
+ upStatement: [{ sql: upStatement }],
+ downStatement: [{ sql: downStatement }],
+ };
+ } catch (e) {
+ log.ppe(e, _func);
+ throw e;
+ }
+
+ return result;
+ }
+
+ async executeRawQuery(query) {
+ const queries = query.split(';');
+ const response = [];
+
+ for (const q of queries) {
+ if (q.trim()) {
+ response.push(await this.sqlClient.raw(q));
+ }
+ }
+
+ return response.length === 1 ? response[0] : response;
+ }
+}
+export default DatabricksClient;
diff --git a/packages/nocodb/src/db/sql-client/lib/databricks/databricks.queries.ts b/packages/nocodb/src/db/sql-client/lib/databricks/databricks.queries.ts
new file mode 100644
index 0000000000..e1ba762d4c
--- /dev/null
+++ b/packages/nocodb/src/db/sql-client/lib/databricks/databricks.queries.ts
@@ -0,0 +1,3 @@
+const queries = {};
+
+export default queries;
diff --git a/packages/nocodb/src/db/sql-mgr/code/models/xc/ModelXcMetaDatabricks.ts b/packages/nocodb/src/db/sql-mgr/code/models/xc/ModelXcMetaDatabricks.ts
new file mode 100644
index 0000000000..0523ecf2bd
--- /dev/null
+++ b/packages/nocodb/src/db/sql-mgr/code/models/xc/ModelXcMetaDatabricks.ts
@@ -0,0 +1,509 @@
+import BaseModelXcMeta from './BaseModelXcMeta';
+
+class ModelXcMetaDatabricks extends BaseModelXcMeta {
+ /**
+ * @param dir
+ * @param filename
+ * @param ctx
+ * @param ctx.tn
+ * @param ctx.columns
+ * @param ctx.relations
+ */
+ constructor({ dir, filename, ctx }) {
+ super({ dir, filename, ctx });
+ }
+
+ /**
+ * Prepare variables used in code template
+ */
+ prepare() {
+ const data: any = {};
+
+ /* run of simple variable */
+ data.tn = this.ctx.tn;
+ data.dbType = this.ctx.dbType;
+
+ /* for complex code provide a func and args - do derivation within the func cbk */
+ data.columns = {
+ func: this._renderXcColumns.bind(this),
+ args: {
+ tn: this.ctx.tn,
+ columns: this.ctx.columns,
+ relations: this.ctx.relations,
+ },
+ };
+
+ /* for complex code provide a func and args - do derivation within the func cbk */
+ data.hasMany = {
+ func: this.renderXcHasMany.bind(this),
+ args: {
+ tn: this.ctx.tn,
+ columns: this.ctx.columns,
+ hasMany: this.ctx.hasMany,
+ },
+ };
+
+ /* for complex code provide a func and args - do derivation within the func cbk */
+ data.belongsTo = {
+ func: this.renderXcBelongsTo.bind(this),
+ args: {
+ tn: this.ctx.tn,
+ columns: this.ctx.columns,
+ belongsTo: this.ctx.belongsTo,
+ },
+ };
+
+ return data;
+ }
+
+ /**
+ *
+ * @param args
+ * @param args.columns
+ * @param args.relations
+ * @returns {string}
+ * @private
+ */
+ _renderXcColumns(args) {
+ let str = '[\r\n';
+
+ for (let i = 0; i < args.columns.length; ++i) {
+ str += `{\r\n`;
+ str += `cn: '${args.columns[i].cn}',\r\n`;
+ str += `type: '${this._getAbstractType(args.columns[i])}',\r\n`;
+ str += `dt: '${args.columns[i].dt}',\r\n`;
+ if (args.columns[i].rqd) str += `rqd: ${args.columns[i].rqd},\r\n`;
+
+ if (args.columns[i].cdf) {
+ str += `default: "${args.columns[i].cdf}",\r\n`;
+ str += `columnDefault: "${args.columns[i].cdf}",\r\n`;
+ }
+
+ if (args.columns[i].un) str += `un: ${args.columns[i].un},\r\n`;
+
+ if (args.columns[i].pk) str += `pk: ${args.columns[i].pk},\r\n`;
+
+ if (args.columns[i].ai) str += `ai: ${args.columns[i].ai},\r\n`;
+
+ if (args.columns[i].dtxp) str += `dtxp: "${args.columns[i].dtxp}",\r\n`;
+
+ if (args.columns[i].dtxs) str += `dtxs: ${args.columns[i].dtxs},\r\n`;
+
+ str += `validate: {
+ func: [],
+ args: [],
+ msg: []
+ },`;
+ str += `},\r\n`;
+ }
+
+ str += ']\r\n';
+
+ return str;
+ }
+
+ _getAbstractType(column) {
+ return this.getAbstractType(column);
+ }
+
+ getUIDataType(col): any {
+ const dt = col.dt.toLowerCase();
+ switch (dt) {
+ case 'bigint':
+ case 'tinyint':
+ case 'int':
+ case 'smallint':
+ return 'Number';
+ case 'decimal':
+ case 'double':
+ case 'float':
+ return 'Decimal';
+ case 'boolean':
+ return 'Checkbox';
+ case 'timestamp':
+ case 'timestamp_ntz':
+ return 'DateTime';
+
+ case 'date':
+ return 'Date';
+
+ case 'string':
+ return 'LongText';
+
+ case 'interval':
+ case 'void':
+ case 'binary':
+ default:
+ return 'SpecificDBType';
+ }
+ }
+
+ getAbstractType(col): any {
+ const dt = col.dt.toLowerCase();
+ switch (dt) {
+ case 'bigint':
+ case 'tinyint':
+ case 'decimal':
+ case 'double':
+ case 'float':
+ case 'int':
+ case 'smallint':
+ return 'integer';
+ case 'binary':
+ return dt;
+ case 'boolean':
+ return 'boolean';
+ case 'interval':
+ case 'void':
+ return dt;
+
+ case 'timestamp':
+ case 'timestamp_ntz':
+ return 'datetime';
+
+ case 'date':
+ return 'date';
+
+ case 'string':
+ return 'string';
+ }
+ }
+
+ _sequelizeGetType(column) {
+ let str = '';
+ switch (column.dt) {
+ case 'int':
+ str += `DataTypes.INTEGER(${column.dtxp})`;
+ if (column.un) str += `.UNSIGNED`;
+ break;
+ case 'tinyint':
+ str += `DataTypes.INTEGER(${column.dtxp})`;
+ if (column.un) str += `.UNSIGNED`;
+
+ break;
+ case 'smallint':
+ str += `DataTypes.INTEGER(${column.dtxp})`;
+ if (column.un) str += `.UNSIGNED`;
+
+ break;
+ case 'mediumint':
+ str += `DataTypes.INTEGER(${column.dtxp})`;
+ if (column.un) str += `.UNSIGNED`;
+
+ break;
+ case 'bigint':
+ str += `DataTypes.BIGINT`;
+ if (column.un) str += `.UNSIGNED`;
+
+ break;
+ case 'float':
+ str += `DataTypes.FLOAT`;
+ break;
+ case 'decimal':
+ str += `DataTypes.DECIMAL`;
+ break;
+ case 'double':
+ str += `"DOUBLE(${column.dtxp},${column.ns})"`;
+ break;
+ case 'real':
+ str += `DataTypes.FLOAT`;
+ break;
+ case 'bit':
+ str += `DataTypes.BOOLEAN`;
+ break;
+ case 'boolean':
+ str += `DataTypes.STRING(45)`;
+ break;
+ case 'serial':
+ str += `DataTypes.BIGINT`;
+ break;
+ case 'date':
+ str += `DataTypes.DATEONLY`;
+ break;
+ case 'datetime':
+ str += `DataTypes.DATE`;
+ break;
+ case 'timestamp':
+ str += `DataTypes.DATE`;
+ break;
+ case 'time':
+ str += `DataTypes.TIME`;
+ break;
+ case 'year':
+ str += `"YEAR"`;
+ break;
+ case 'char':
+ str += `DataTypes.CHAR(${column.dtxp})`;
+ break;
+ case 'varchar':
+ str += `DataTypes.STRING(${column.dtxp})`;
+ break;
+ case 'nchar':
+ str += `DataTypes.CHAR(${column.dtxp})`;
+ break;
+ case 'text':
+ str += `DataTypes.TEXT`;
+ break;
+ case 'tinytext':
+ str += `DataTypes.TEXT`;
+ break;
+ case 'mediumtext':
+ str += `DataTypes.TEXT`;
+ break;
+ case 'longtext':
+ str += `DataTypes.TEXT`;
+ break;
+ case 'binary':
+ str += `"BINARY(${column.dtxp})"`;
+ break;
+ case 'varbinary':
+ str += `"VARBINARY(${column.dtxp})"`;
+ break;
+ case 'blob':
+ str += `"BLOB"`;
+ break;
+ case 'tinyblob':
+ str += `"TINYBLOB"`;
+ break;
+ case 'mediumblob':
+ str += `"MEDIUMBLOB"`;
+ break;
+ case 'longblob':
+ str += `"LONGBLOB"`;
+ break;
+ case 'enum':
+ str += `DataTypes.ENUM(${column.dtxp})`;
+ break;
+ case 'set':
+ str += `"SET(${column.dtxp})"`;
+ break;
+ case 'geometry':
+ str += `DataTypes.GEOMETRY`;
+ break;
+ case 'point':
+ str += `"POINT"`;
+ break;
+ case 'linestring':
+ str += `"LINESTRING"`;
+ break;
+ case 'polygon':
+ str += `"POLYGON"`;
+ break;
+ case 'multipoint':
+ str += `"MULTIPOINT"`;
+ break;
+ case 'multilinestring':
+ str += `"MULTILINESTRING"`;
+ break;
+ case 'multipolygon':
+ str += `"MULTIPOLYGON"`;
+ break;
+ case 'json':
+ str += `DataTypes.JSON`;
+ break;
+ default:
+ str += `"${column.dt}"`;
+ break;
+ }
+ return str;
+ }
+
+ _sequelizeGetDefault(column) {
+ let str = '';
+ switch (column.dt) {
+ case 'int':
+ str += `'${column.cdf}'`;
+ break;
+ case 'tinyint':
+ str += `'${column.cdf}'`;
+ break;
+ case 'smallint':
+ str += `'${column.cdf}'`;
+ break;
+ case 'mediumint':
+ str += `'${column.cdf}'`;
+ break;
+ case 'bigint':
+ str += `'${column.cdf}'`;
+ break;
+ case 'float':
+ str += `'${column.cdf}'`;
+ break;
+ case 'decimal':
+ str += `'${column.cdf}'`;
+ break;
+ case 'double':
+ str += `'${column.cdf}'`;
+ break;
+ case 'real':
+ str += `'${column.cdf}'`;
+ break;
+ case 'bit':
+ str += column.cdf ? column.cdf.split('b')[1] : column.cdf;
+ break;
+ case 'boolean':
+ str += column.cdf;
+ break;
+ case 'serial':
+ str += column.cdf;
+ break;
+ case 'date':
+ str += `sequelize.literal('${column.cdf_sequelize}')`;
+ break;
+ case 'datetime':
+ str += `sequelize.literal('${column.cdf_sequelize}')`;
+ break;
+ case 'timestamp':
+ str += `sequelize.literal('${column.cdf_sequelize}')`;
+ break;
+ case 'time':
+ str += `'${column.cdf}'`;
+ break;
+ case 'year':
+ str += `'${column.cdf}'`;
+ break;
+ case 'char':
+ str += `'${column.cdf}'`;
+ break;
+ case 'varchar':
+ str += `'${column.cdf}'`;
+ break;
+ case 'nchar':
+ str += `'${column.cdf}'`;
+ break;
+ case 'text':
+ str += column.cdf;
+ break;
+ case 'tinytext':
+ str += column.cdf;
+ break;
+ case 'mediumtext':
+ str += column.cdf;
+ break;
+ case 'longtext':
+ str += column.cdf;
+ break;
+ case 'binary':
+ str += column.cdf;
+ break;
+ case 'varbinary':
+ str += column.cdf;
+ break;
+ case 'blob':
+ str += column.cdf;
+ break;
+ case 'tinyblob':
+ str += column.cdf;
+ break;
+ case 'mediumblob':
+ str += column.cdf;
+ break;
+ case 'longblob':
+ str += column.cdf;
+ break;
+ case 'enum':
+ str += `'${column.cdf}'`;
+ break;
+ case 'set':
+ str += `'${column.cdf}'`;
+ break;
+ case 'geometry':
+ str += `'${column.cdf}'`;
+ break;
+ case 'point':
+ str += `'${column.cdf}'`;
+ break;
+ case 'linestring':
+ str += `'${column.cdf}'`;
+ break;
+ case 'polygon':
+ str += `'${column.cdf}'`;
+ break;
+ case 'multipoint':
+ str += `'${column.cdf}'`;
+ break;
+ case 'multilinestring':
+ str += `'${column.cdf}'`;
+ break;
+ case 'multipolygon':
+ str += `'${column.cdf}'`;
+ break;
+ case 'json':
+ str += column.cdf;
+ break;
+ }
+ return str;
+ }
+
+ /* getXcColumnsObject(args) {
+ const columnsArr = [];
+
+ for (const column of args.columns) {
+ const columnObj = {
+ validate: {
+ func: [],
+ args: [],
+ msg: []
+ },
+ cn: column.cn,
+ _cn: column._cn || column.cn,
+ type: this._getAbstractType(column),
+ dt: column.dt,
+ uidt: column.uidt || this._getUIDataType(column),
+ uip: column.uip,
+ uicn: column.uicn,
+ ...column
+ };
+
+ if (column.rqd) {
+ columnObj.rqd = column.rqd;
+ }
+
+ if (column.cdf) {
+ columnObj.default = column.cdf;
+ columnObj.columnDefault = column.cdf;
+ }
+
+ if (column.un) {
+ columnObj.un = column.un;
+ }
+
+ if (column.pk) {
+ columnObj.pk = column.pk;
+ }
+
+ if (column.ai) {
+ columnObj.ai = column.ai;
+ }
+
+ if (column.dtxp) {
+ columnObj.dtxp = column.dtxp;
+ }
+
+ if (column.dtxs) {
+ columnObj.dtxs = column.dtxs;
+ }
+
+ columnsArr.push(columnObj);
+ }
+
+ this.mapDefaultDisplayValue(columnsArr);
+ return columnsArr;
+ }*/
+
+ /* getObject() {
+ return {
+ tn: this.ctx.tn,
+ _tn: this.ctx._tn,
+ columns: this.getXcColumnsObject(this.ctx),
+ pks: [],
+ hasMany: this.ctx.hasMany,
+ belongsTo: this.ctx.belongsTo,
+ dbType: this.ctx.dbType,
+ type: this.ctx.type,
+ }
+
+ }*/
+}
+
+export default ModelXcMetaDatabricks;
diff --git a/packages/nocodb/src/db/sql-mgr/code/models/xc/ModelXcMetaFactory.ts b/packages/nocodb/src/db/sql-mgr/code/models/xc/ModelXcMetaFactory.ts
index c0669dbc2a..fb21910c80 100644
--- a/packages/nocodb/src/db/sql-mgr/code/models/xc/ModelXcMetaFactory.ts
+++ b/packages/nocodb/src/db/sql-mgr/code/models/xc/ModelXcMetaFactory.ts
@@ -4,6 +4,7 @@ import ModelXcMetaOracle from './ModelXcMetaOracle';
import ModelXcMetaPg from './ModelXcMetaPg';
import ModelXcMetaSqlite from './ModelXcMetaSqlite';
import ModelXcMetaSnowflake from './ModelXcMetaSnowflake';
+import ModelXcMetaDatabricks from './ModelXcMetaDatabricks';
import type BaseModelXcMeta from './BaseModelXcMeta';
class ModelXcMetaFactory {
@@ -23,6 +24,8 @@ class ModelXcMetaFactory {
return new ModelXcMetaOracle(args);
} else if (connectionConfig.client === 'snowflake') {
return new ModelXcMetaSnowflake(args);
+ } else if (connectionConfig.client === 'databricks') {
+ return new ModelXcMetaDatabricks(args);
}
throw new Error('Database not supported');
diff --git a/packages/nocodb/src/helpers/populateMeta.ts b/packages/nocodb/src/helpers/populateMeta.ts
index fd95bc2222..213fb5b2e2 100644
--- a/packages/nocodb/src/helpers/populateMeta.ts
+++ b/packages/nocodb/src/helpers/populateMeta.ts
@@ -334,6 +334,14 @@ export async function populateMeta(
let colOrder = 1;
for (const column of columns) {
+ if (source.type === 'databricks') {
+ if (column.pk && !column.cdf) {
+ column.meta = {
+ ag: 'nc',
+ };
+ }
+ }
+
await Column.insert({
uidt: column.uidt || getColumnUiType(source, column),
fk_model_id: models2[table.tn].id,
diff --git a/packages/nocodb/src/schema/swagger-v2.json b/packages/nocodb/src/schema/swagger-v2.json
index bdd32d3999..93f24a2b05 100644
--- a/packages/nocodb/src/schema/swagger-v2.json
+++ b/packages/nocodb/src/schema/swagger-v2.json
@@ -9737,7 +9737,8 @@
"oracledb",
"pg",
"snowflake",
- "sqlite3"
+ "sqlite3",
+ "databricks"
],
"example": "mysql2",
"type": "string"
@@ -11989,7 +11990,8 @@
"oracledb",
"pg",
"snowflake",
- "sqlite3"
+ "sqlite3",
+ "databricks"
],
"example": "mysql2",
"type": "string"
@@ -12153,7 +12155,8 @@
"oracledb",
"pg",
"snowflake",
- "sqlite3"
+ "sqlite3",
+ "databricks"
],
"type": "string"
}
diff --git a/packages/nocodb/src/schema/swagger.json b/packages/nocodb/src/schema/swagger.json
index e2f59247e1..d23f3c88ca 100644
--- a/packages/nocodb/src/schema/swagger.json
+++ b/packages/nocodb/src/schema/swagger.json
@@ -14832,7 +14832,8 @@
"oracledb",
"pg",
"snowflake",
- "sqlite3"
+ "sqlite3",
+ "databricks"
],
"example": "mysql2",
"type": "string"
@@ -14977,7 +14978,8 @@
"oracledb",
"pg",
"snowflake",
- "sqlite3"
+ "sqlite3",
+ "databricks"
],
"example": "mysql2",
"type": "string"
@@ -17900,7 +17902,8 @@
"oracledb",
"pg",
"snowflake",
- "sqlite3"
+ "sqlite3",
+ "databricks"
],
"example": "mysql2",
"type": "string"
@@ -18064,7 +18067,8 @@
"oracledb",
"pg",
"snowflake",
- "sqlite3"
+ "sqlite3",
+ "databricks"
],
"type": "string"
}
diff --git a/packages/nocodb/src/services/meta-diffs.service.ts b/packages/nocodb/src/services/meta-diffs.service.ts
index fa3c572b16..df4ff7bda6 100644
--- a/packages/nocodb/src/services/meta-diffs.service.ts
+++ b/packages/nocodb/src/services/meta-diffs.service.ts
@@ -141,6 +141,8 @@ export class MetaDiffsService {
const changes: Array = [];
const virtualRelationColumns: Column[] = [];
+ const isDatabricks = sqlClient.knex.clientType() === 'databricks';
+
// @ts-ignore
const tableList: Array<{ tn: string }> = (
await sqlClient.tableList({ schema: source.getConfig()?.schema })
@@ -178,7 +180,10 @@ export class MetaDiffsService {
if (table.tn === 'nc_evolutions') continue;
const oldMetaIdx = oldTableMetas.findIndex(
- (m) => m.table_name === table.tn,
+ (m) =>
+ m.table_name === table.tn ||
+ (isDatabricks &&
+ m.table_name.toLowerCase() === table.tn.toLowerCase()),
);
// new table
@@ -223,7 +228,10 @@ export class MetaDiffsService {
for (const column of colListRef[table.tn]) {
const oldColIdx = oldMeta.columns.findIndex(
- (c) => c.column_name === column.cn,
+ (c) =>
+ c.column_name === column.cn ||
+ (isDatabricks &&
+ c.column_name.toLowerCase() === column.cn.toLowerCase()),
);
// new table
diff --git a/packages/nocodb/src/services/tables.service.ts b/packages/nocodb/src/services/tables.service.ts
index 73ab346e15..3dc052db5e 100644
--- a/packages/nocodb/src/services/tables.service.ts
+++ b/packages/nocodb/src/services/tables.service.ts
@@ -82,6 +82,8 @@ export class TablesService {
}
}
+ param.table.table_name = param.table.table_name.replace(/ /g, '_');
+
param.table.table_name = DOMPurify.sanitize(param.table.table_name);
// validate table name
@@ -519,7 +521,7 @@ export class TablesService {
}
tableCreatePayLoad.table_name = DOMPurify.sanitize(
- tableCreatePayLoad.table_name,
+ tableCreatePayLoad.table_name.replace(/ /g, '_'),
);
// validate table name
diff --git a/packages/nocodb/src/utils/globals.ts b/packages/nocodb/src/utils/globals.ts
index ff286fa015..3fd4c58d31 100644
--- a/packages/nocodb/src/utils/globals.ts
+++ b/packages/nocodb/src/utils/globals.ts
@@ -256,4 +256,5 @@ export const DB_TYPES = [
'snowflake',
'oracledb',
'pg',
+ 'databricks',
];
diff --git a/packages/nocodb/src/utils/nc-config/constants.ts b/packages/nocodb/src/utils/nc-config/constants.ts
index e77a68748d..98bc4f4d1c 100644
--- a/packages/nocodb/src/utils/nc-config/constants.ts
+++ b/packages/nocodb/src/utils/nc-config/constants.ts
@@ -81,4 +81,5 @@ export enum DriverClient {
PG = 'pg',
SQLITE = 'sqlite3',
SNOWFLAKE = 'snowflake',
+ DATABRICKS = 'databricks',
}