/* eslint-disable no-console */ import * as fs from 'fs'; import * as path from 'path'; import { format } from 'sql-formatter'; import { PgColumn, PgTable } from '../abstractions'; import { ContentEntityModel } from '../content-entity-model'; export function buildFullTableName(tableName: string, schema?: string): string { return schema ? `${schema}.${tableName}` : tableName; } /** * Builds a DROP TABLE IF EXISTS statement. * @param table */ export function buildDropTableIfExists(table: PgTable): string { return `DROP TABLE IF EXISTS ${table.buildFullName()} CASCADE`; } /** * Builds a CREATE TABLE statement. * @param table - PgTable instance to build the CREATE TABLE statement for. */ export function buildCreateTable(table: PgTable): string { const columns = [ table.pk, ...table.fks, ...table.virtualFks, ...table.columns, ]; return `CREATE TABLE ${table.buildFullName()}( ${columns.map((c) => c.buildExpression()).join(',\n')} )`; } /** * Builds a GRANT statement for INSERTs and UPDATEs. * @param table - PgTable instance to build the GRANT statement for. */ export function buildInsertUpdateGrants(table: PgTable): string { const columns = [...table.fks, ...table.virtualFks, ...table.columns]; return `GRANT INSERT, UPDATE (${columns.map((c) => c.name).join(',')}) ON ${table.buildFullName()} TO ":DATABASE_GQL_ROLE";`; } /** * Builds a GRANT statement for SELECTs and DELETEs. * @param table - PgTable instance to build the GRANT statement for. */ export function buildSelectDeleteGrants(table: PgTable): string { return `GRANT SELECT, DELETE ON ${table.buildFullName()} TO ":DATABASE_GQL_ROLE";`; } /** * Builds additional statements relevant for table creation * that should be executed after CRATE TABLE e.g. adding indexes or comments. * @param table - PgTable instance to build extra statements for. */ export function buildAdditionalTableStatements(table: PgTable): string[] { const statements: string[] = []; const columns = [ table.pk, ...table.fks, ...table.virtualFks, ...table.columns, ]; const additionalStatements = ([] as (string | undefined)[]) .concat(...columns.map((p) => p.buildAdditionalStatements())) .filter((s: string | undefined): s is string => s !== undefined); statements.push(...additionalStatements); return statements; } /** * Formats an SQL string using pg-formatter. * @param sql - Input SQL string. */ export function formatSql(sql: string): string { return format(sql, { language: 'sql' }); } /** * Generates a migration for graphile-migrate. * @param statements - Array of SQL statements. * @param outPath - Path where the dump is created. */ export async function generateMigration( statements: string[], outPath: string, ): Promise { const dirPath = path.dirname(outPath); if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } console.log(`Writing migration to ${outPath}.`); const preamble = ` --! Message: added content types -- Statements below are generated from publish format definitions.`; statements.unshift(preamble); fs.writeFile(outPath, formatSql(statements.join(';\n')), (err) => { if (err) { throw err; } }); } /** * Combines `nameParts` into a single snake case string. * @param nameParts - Name parts to combine. */ export function buildName(...nameParts: string[]): string { return nameParts.join('_'); } export function renameDbItem( item: PgTable | PgColumn, newName: string, displayName?: string, ): void { item.name = newName; if (displayName) { item.displayName = displayName; } } export interface NameOverride { [name: string]: [string, string?]; } export function applyNameOverrides( model: ContentEntityModel, overrides: NameOverride, ): void { const mainModelTables = [model.contentEntity, ...model.relatedObjects]; for (const table of mainModelTables) { let rename = overrides[table.name]; if (rename) { renameDbItem(table, ...rename); } for (const column of table.columns) { rename = overrides[column.name]; if (rename) { renameDbItem(column, ...rename); } } } } export function isReservedPgWord(s: string): boolean { return PgReservedWords.includes(s.toUpperCase()); } const PgReservedWords = [ 'ALL', 'ANALYSE', 'ANALYZE', 'AND', 'ANY', 'ARRAY', 'AS', 'ASC', 'ASYMMETRIC', 'BOTH', 'CASE', 'CAST', 'CHECK', 'COLLATE', 'COLUMN', 'CONSTRAINT', 'CREATE', 'CURRENT_CATALOG', 'CURRENT_DATE', 'CURRENT_ROLE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'DEFAULT', 'DEFERRABLE', 'DESC', 'DISTINCT', 'DO', 'ELSE', 'END', 'EXCEPT', 'FALSE', 'FETCH', 'FOR', 'FOREIGN', 'FROM', 'GRANT', 'GROUP', 'HAVING', 'IN', 'INITIALLY', 'INTERSECT', 'INTO', 'LATERAL', 'LEADING', 'LIMIT', 'LOCALTIME', 'LOCALTIMESTAMP', 'NOT', 'NULL', 'OFFSET', 'ON', 'ONLY', 'OR', 'ORDER', 'PLACING', 'PRIMARY', 'REFERENCES', 'RETURNING', 'SELECT', 'SESSION_USER', 'SOME', 'SYMMETRIC', 'TABLE', 'THEN', 'TO', 'TRAILING', 'TRUE', 'UNION', 'UNIQUE', 'USER', 'USING', 'VARIADIC', 'WHEN', 'WHERE', 'WINDOW', 'WITH', ];