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 e9771ea..8914d41 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,19 +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( - getFullName, - isActivated && !allDeactivated, - )({ + : 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/general.js b/forward_engineering/helpers/general.js index e573ac3..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 ? `\n OPTIONS(\n${tab(options.join(',\n'))}\n)` : ''; + return options.length ? `\nOPTIONS (\n${tab(options.join(',\n'))}\n)` : ''; }; const cleanObject = obj => diff --git a/forward_engineering/helpers/utils.js b/forward_engineering/helpers/utils.js index e0464e5..fd0412a 100644 --- a/forward_engineering/helpers/utils.js +++ b/forward_engineering/helpers/utils.js @@ -329,38 +329,69 @@ 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) => - ({ columns, projectId, datasetName }) => { - const keys = columns.reduce((tables, key) => { - let column = wrapByBackticks(key.name); + isActivated => + ({ columns, projectId, datasetName, tab }) => { + const allColumnNames = new Set(); + 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.has(columnName)) { + allColumnNames.add(columnName); } - if (isActivated && !key.isActivated) { - tables[key.tableName].deactivated.push(column); - } else { - tables[key.tableName].activated.push(column); - } + columnsByTable[column.tableName ?? NO_TABLE_KEY] ??= {}; + columnsByTable[column.tableName ?? NO_TABLE_KEY][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(', ')}*/` : ''); + const tableColumns = columnsByTable[tableName]; + const activated = []; + const deactivated = []; + + for (const columnName of allColumnNames) { + let columnExpression; + const column = tableColumns[columnName]; + + 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 ${columns || '*'} FROM ${getFullName(projectId, datasetName, tableName)}`; + return `SELECT\n${tab(finalColumns || '*')}\n${getFromStatement({ projectId, datasetName, tableName })}`; }) .join('\nUNION ALL\n'); };