From 88a60928212017bd23be754e8f53b1ea6055fe2c Mon Sep 17 00:00:00 2001 From: yevhenii-moroziuk Date: Sat, 7 Feb 2026 09:49:23 +0200 Subject: [PATCH 1/6] HCK-14719: Fix generation for columns --- forward_engineering/helpers/utils.js | 63 ++++++++++++++++++---------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/forward_engineering/helpers/utils.js b/forward_engineering/helpers/utils.js index e0464e5..f99906f 100644 --- a/forward_engineering/helpers/utils.js +++ b/forward_engineering/helpers/utils.js @@ -332,35 +332,54 @@ const decorateType = ({ type, columnDefinition }) => { const generateViewSelectStatement = (getFullName, isActivated) => ({ columns, projectId, datasetName }) => { - const keys = columns.reduce((tables, key) => { - let column = wrapByBackticks(key.name); + const allColumnNames = []; + const deactivatedColumnNames = new Set(); + const columnsByTable = {}; - if (key.alias) { - column = `${column} as ${key.alias}`; - } + columns.forEach(column => { + const columnName = column.alias || column.name; - if (!tables[key.tableName]) { - tables[key.tableName] = { - activated: [], - deactivated: [], - }; + if (!allColumnNames.includes(columnName)) { + allColumnNames.push(columnName); } - if (isActivated && !key.isActivated) { - tables[key.tableName].deactivated.push(column); - } else { - tables[key.tableName].activated.push(column); - } + columnsByTable[column.tableName] ??= {}; + columnsByTable[column.tableName][columnName] = { + name: column.name, + alias: column.alias, + }; - return tables; - }, {}); + if (!column.isActivated) { + deactivatedColumnNames.add(columnName); + } + }); - return Object.keys(keys) + return Object.keys(columnsByTable) .map(tableName => { - const { deactivated, activated } = keys[tableName]; - const columns = activated.join(', ') + (deactivated.length ? `/*, ${deactivated.join(', ')}*/` : ''); - - return `SELECT ${columns || '*'} FROM ${getFullName(projectId, datasetName, tableName)}`; + const tableColumns = columnsByTable[tableName]; + const activated = []; + const deactivated = []; + + allColumnNames.forEach(columnName => { + let columnExpression; + const column = tableColumns[columnName]; + + if (column) { + columnExpression = column.alias + ? `${wrapByBackticks(column.name)} AS ${wrapByBackticks(column.alias)}` + : wrapByBackticks(column.name); + } else { + columnExpression = `NULL AS ${wrapByBackticks(columnName)}`; + } + + const arrayToPush = isActivated && deactivatedColumnNames.has(columnName) ? deactivated : activated; + arrayToPush.push(columnExpression); + }); + + const finalColumns = + activated.join(',\n ') + (deactivated.length ? `\n /*, ${deactivated.join(', ')}*/` : ''); + + return `SELECT\n ${finalColumns || '*'}\nFROM ${getFullName(projectId, datasetName, tableName)}`; }) .join('\nUNION ALL\n'); }; From 6a7db85904a4d51cf73e2f53fc79ce4eff5e3b5e Mon Sep 17 00:00:00 2001 From: yevhenii-moroziuk Date: Sun, 8 Feb 2026 13:43:54 +0200 Subject: [PATCH 2/6] HCK-14719: Support view columns without tables --- forward_engineering/ddlProvider.js | 5 +-- forward_engineering/helpers/utils.js | 55 +++++++++++++++++++--------- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/forward_engineering/ddlProvider.js b/forward_engineering/ddlProvider.js index 3334a0f..bbb01c4 100644 --- a/forward_engineering/ddlProvider.js +++ b/forward_engineering/ddlProvider.js @@ -241,10 +241,7 @@ module.exports = (baseProvider, options, app) => { selectStatement: `\n ${_.trim( viewData.selectStatement ? viewData.selectStatement - : generateViewSelectStatement( - getFullName, - isActivated && !allDeactivated, - )({ + : generateViewSelectStatement(isActivated && !allDeactivated)({ columns: viewData.keys, datasetName: dbData.databaseName, projectId: dbData.projectId, diff --git a/forward_engineering/helpers/utils.js b/forward_engineering/helpers/utils.js index f99906f..dac795d 100644 --- a/forward_engineering/helpers/utils.js +++ b/forward_engineering/helpers/utils.js @@ -329,25 +329,44 @@ const decorateType = ({ type, columnDefinition }) => { return isComplexType ? dataType.replace(/<[\s\S]+>$/, '<>') : dataType; }; +const NO_TABLE_KEY = '__NO_TABLE__'; + +const getFromStatement = ({ projectId, datasetName, tableName }) => { + if (tableName === NO_TABLE_KEY) { + return 'FROM (SELECT 1)'; + } + + return `FROM ${getFullName(projectId, datasetName, tableName)}`; +}; + const generateViewSelectStatement = - (getFullName, isActivated) => + isActivated => ({ columns, projectId, datasetName }) => { - const allColumnNames = []; + const allColumnNames = new Set(); const deactivatedColumnNames = new Set(); - const columnsByTable = {}; + const columnsByTable = { + [NO_TABLE_KEY]: {}, + }; columns.forEach(column => { const columnName = column.alias || column.name; - if (!allColumnNames.includes(columnName)) { - allColumnNames.push(columnName); + if (!allColumnNames.has(columnName)) { + allColumnNames.add(columnName); } - columnsByTable[column.tableName] ??= {}; - columnsByTable[column.tableName][columnName] = { - name: column.name, - alias: column.alias, - }; + if (column.tableName) { + columnsByTable[column.tableName] ??= {}; + columnsByTable[column.tableName][columnName] = { + name: column.name, + alias: column.alias, + }; + } else { + columnsByTable[NO_TABLE_KEY][columnName] = { + name: column.name, + alias: column.alias, + }; + } if (!column.isActivated) { deactivatedColumnNames.add(columnName); @@ -360,26 +379,28 @@ const generateViewSelectStatement = const activated = []; const deactivated = []; - allColumnNames.forEach(columnName => { + for (const columnName of allColumnNames) { let columnExpression; const column = tableColumns[columnName]; - if (column) { - columnExpression = column.alias - ? `${wrapByBackticks(column.name)} AS ${wrapByBackticks(column.alias)}` - : wrapByBackticks(column.name); + if (column && tableName !== NO_TABLE_KEY) { + columnExpression = wrapByBackticks(column.name); + + if (column.alias) { + columnExpression += ` AS ${wrapByBackticks(column.alias)}`; + } } else { columnExpression = `NULL AS ${wrapByBackticks(columnName)}`; } const arrayToPush = isActivated && deactivatedColumnNames.has(columnName) ? deactivated : activated; arrayToPush.push(columnExpression); - }); + } const finalColumns = activated.join(',\n ') + (deactivated.length ? `\n /*, ${deactivated.join(', ')}*/` : ''); - return `SELECT\n ${finalColumns || '*'}\nFROM ${getFullName(projectId, datasetName, tableName)}`; + return `SELECT\n ${finalColumns || '*'}\n${getFromStatement({ projectId, datasetName, tableName })}`; }) .join('\nUNION ALL\n'); }; From d0fca984c31169016bf135daded73059a7f5ef9a Mon Sep 17 00:00:00 2001 From: yevhenii-moroziuk Date: Sun, 8 Feb 2026 13:58:43 +0200 Subject: [PATCH 3/6] HCK-14719: Simplify the code --- forward_engineering/helpers/utils.js | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/forward_engineering/helpers/utils.js b/forward_engineering/helpers/utils.js index dac795d..1bfa791 100644 --- a/forward_engineering/helpers/utils.js +++ b/forward_engineering/helpers/utils.js @@ -344,9 +344,7 @@ const generateViewSelectStatement = ({ columns, projectId, datasetName }) => { const allColumnNames = new Set(); const deactivatedColumnNames = new Set(); - const columnsByTable = { - [NO_TABLE_KEY]: {}, - }; + const columnsByTable = {}; columns.forEach(column => { const columnName = column.alias || column.name; @@ -355,18 +353,11 @@ const generateViewSelectStatement = allColumnNames.add(columnName); } - if (column.tableName) { - columnsByTable[column.tableName] ??= {}; - columnsByTable[column.tableName][columnName] = { - name: column.name, - alias: column.alias, - }; - } else { - columnsByTable[NO_TABLE_KEY][columnName] = { - name: column.name, - alias: column.alias, - }; - } + columnsByTable[column.tableName ?? NO_TABLE_KEY] ??= {}; + columnsByTable[column.tableName ?? NO_TABLE_KEY][columnName] = { + name: column.name, + alias: column.alias, + }; if (!column.isActivated) { deactivatedColumnNames.add(columnName); @@ -400,9 +391,9 @@ const generateViewSelectStatement = const finalColumns = activated.join(',\n ') + (deactivated.length ? `\n /*, ${deactivated.join(', ')}*/` : ''); - return `SELECT\n ${finalColumns || '*'}\n${getFromStatement({ projectId, datasetName, tableName })}`; + return ` SELECT\n ${finalColumns || '*'}\n ${getFromStatement({ projectId, datasetName, tableName })}`; }) - .join('\nUNION ALL\n'); + .join('\n UNION ALL\n'); }; const clearEmptyStatements = statements => statements.filter(statementComponent => Boolean(statementComponent)); From 81a7e7434e210a3d116727c04f9ecff18f86ec4c Mon Sep 17 00:00:00 2001 From: Alik Rakhmonov Date: Mon, 9 Feb 2026 17:14:25 +0100 Subject: [PATCH 4/6] add tab indents --- forward_engineering/configs/templates.js | 2 +- forward_engineering/ddlProvider.js | 9 +++++---- forward_engineering/helpers/utils.js | 8 ++++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/forward_engineering/configs/templates.js b/forward_engineering/configs/templates.js index cf00225..91c115b 100644 --- a/forward_engineering/configs/templates.js +++ b/forward_engineering/configs/templates.js @@ -10,7 +10,7 @@ module.exports = { '${constraintName}FOREIGN KEY (${foreignKeys}) REFERENCES ${primaryTableName}(${primaryKeys}) NOT ENFORCED', createView: - 'CREATE ${orReplace}${materialized}VIEW ${ifNotExist}${name}${columns}${partitions}${clustering}${options} AS${selectStatement};\n', + 'CREATE ${orReplace}${materialized}VIEW ${ifNotExist}${name}${columns}${partitions}${clustering}${options}\nAS ${selectStatement};\n', dropDatabase: 'DROP SCHEMA IF EXISTS ${name};', diff --git a/forward_engineering/ddlProvider.js b/forward_engineering/ddlProvider.js index 65dd29e..b5c10f9 100644 --- a/forward_engineering/ddlProvider.js +++ b/forward_engineering/ddlProvider.js @@ -209,7 +209,7 @@ module.exports = (baseProvider, options, app) => { .filter(Boolean) .map(wrapByBackticks); - columns = activated.join(', ') + (deActivated.length ? `/* ${deActivated.join(', ')} */` : ''); + columns = activated.join(',\n') + (deActivated.length ? `/* ${deActivated.join(', ')} */` : ''); } else { columns = viewData.keys .map(key => wrapByBackticks(key.alias || key.name)) @@ -237,16 +237,17 @@ module.exports = (baseProvider, options, app) => { materialized: viewData.materialized ? 'MATERIALIZED ' : '', orReplace: viewData.orReplace && !viewData.materialized ? 'OR REPLACE ' : '', ifNotExist: viewData.ifNotExist ? 'IF NOT EXISTS ' : '', - columns: columns.length ? `\n (${columns})` : '', - selectStatement: `\n ${_.trim( + columns: columns.length ? `(\n${tab(columns)}\n)` : '', + selectStatement: _.trim( viewData.selectStatement ? viewData.selectStatement : generateViewSelectStatement(isActivated && !allDeactivated)({ columns: viewData.keys, datasetName: dbData.databaseName, projectId: dbData.projectId, + tab, }), - )}`, + ), options: getViewOptions(viewData), partitions: partitionsStatement ? '\n' + partitionsStatement : '', clustering, diff --git a/forward_engineering/helpers/utils.js b/forward_engineering/helpers/utils.js index 1bfa791..fd0412a 100644 --- a/forward_engineering/helpers/utils.js +++ b/forward_engineering/helpers/utils.js @@ -341,7 +341,7 @@ const getFromStatement = ({ projectId, datasetName, tableName }) => { const generateViewSelectStatement = isActivated => - ({ columns, projectId, datasetName }) => { + ({ columns, projectId, datasetName, tab }) => { const allColumnNames = new Set(); const deactivatedColumnNames = new Set(); const columnsByTable = {}; @@ -389,11 +389,11 @@ const generateViewSelectStatement = } const finalColumns = - activated.join(',\n ') + (deactivated.length ? `\n /*, ${deactivated.join(', ')}*/` : ''); + activated.join(',\n') + (deactivated.length ? `\n /*, ${deactivated.join(', ')}*/` : ''); - return ` SELECT\n ${finalColumns || '*'}\n ${getFromStatement({ projectId, datasetName, tableName })}`; + return `SELECT\n${tab(finalColumns || '*')}\n${getFromStatement({ projectId, datasetName, tableName })}`; }) - .join('\n UNION ALL\n'); + .join('\nUNION ALL\n'); }; const clearEmptyStatements = statements => statements.filter(statementComponent => Boolean(statementComponent)); From 9f56ff5170f6f6751f74725eb225b8bebf1bd06f Mon Sep 17 00:00:00 2001 From: Alik Rakhmonov Date: Mon, 9 Feb 2026 17:21:47 +0100 Subject: [PATCH 5/6] fix --- forward_engineering/helpers/general.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forward_engineering/helpers/general.js b/forward_engineering/helpers/general.js index e573ac3..c253169 100644 --- a/forward_engineering/helpers/general.js +++ b/forward_engineering/helpers/general.js @@ -85,7 +85,7 @@ module.exports = app => { options = [...options, ...getMaterializedViewOptions(viewData)]; - return options.length ? `\n OPTIONS(\n${tab(options.join(',\n'))}\n)` : ''; + return options.length ? `\nOPTIONS(\n${tab(options.join(',\n'))}\n)` : ''; }; const cleanObject = obj => From 57f48cc929910934362a49380df2282937661f2b Mon Sep 17 00:00:00 2001 From: Alik Rakhmonov Date: Mon, 9 Feb 2026 17:31:09 +0100 Subject: [PATCH 6/6] fixes --- forward_engineering/ddlProvider.js | 2 +- forward_engineering/helpers/general.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/forward_engineering/ddlProvider.js b/forward_engineering/ddlProvider.js index b5c10f9..8914d41 100644 --- a/forward_engineering/ddlProvider.js +++ b/forward_engineering/ddlProvider.js @@ -237,7 +237,7 @@ module.exports = (baseProvider, options, app) => { materialized: viewData.materialized ? 'MATERIALIZED ' : '', orReplace: viewData.orReplace && !viewData.materialized ? 'OR REPLACE ' : '', ifNotExist: viewData.ifNotExist ? 'IF NOT EXISTS ' : '', - columns: columns.length ? `(\n${tab(columns)}\n)` : '', + columns: columns.length ? ` (\n${tab(columns)}\n)` : '', selectStatement: _.trim( viewData.selectStatement ? viewData.selectStatement diff --git a/forward_engineering/helpers/general.js b/forward_engineering/helpers/general.js index c253169..2571a5d 100644 --- a/forward_engineering/helpers/general.js +++ b/forward_engineering/helpers/general.js @@ -85,7 +85,7 @@ module.exports = app => { options = [...options, ...getMaterializedViewOptions(viewData)]; - return options.length ? `\nOPTIONS(\n${tab(options.join(',\n'))}\n)` : ''; + return options.length ? `\nOPTIONS (\n${tab(options.join(',\n'))}\n)` : ''; }; const cleanObject = obj =>