UNPKG

@cheetah.js/orm

Version:
313 lines 14 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PgDriver = void 0; const pg_1 = require("pg"); class PgDriver { constructor(options) { if (options.connectionString) { this.connectionString = options.connectionString; } else { const { host, port, username, password, database } = options; this.connectionString = `postgres://${username}:${password}@${host}:${port}/${database}`; } this.client = new pg_1.Client({ connectionString: this.connectionString, }); } getCreateTableInstruction(schema, tableName, creates) { let beforeSql = ``; const st = `CREATE TABLE "${schema}"."${tableName}" (${creates.map(colDiff => { const isAutoIncrement = colDiff.colChanges?.autoIncrement; let sql = ``; if (colDiff.colChanges?.enumItems) { beforeSql += `CREATE TYPE "${schema}_${tableName}_${colDiff.colName}_enum" AS ENUM (${colDiff.colChanges.enumItems.map(item => `'${item}'`).join(', ')});`; sql += `"${colDiff.colName}" "${schema}_${tableName}_${colDiff.colName}_enum"`; } else { sql += `"${colDiff.colName}" ${isAutoIncrement ? 'SERIAL' : colDiff.colType + (colDiff.colLength ? `(${colDiff.colLength})` : '')}`; } if (!colDiff.colChanges?.nullable) { sql += ' NOT NULL'; } if (colDiff.colChanges?.primary) { sql += ' PRIMARY KEY'; } if (colDiff.colChanges?.unique) { sql += ' UNIQUE'; } if (colDiff.colChanges?.default) { sql += ` DEFAULT ${colDiff.colChanges.default}`; } return sql; })});`; return beforeSql + st; } getAlterTableFkInstruction(schema, tableName, colDiff, fk) { return `ALTER TABLE "${schema}"."${tableName}" ADD CONSTRAINT "${tableName}_${colDiff.colName}_fk" FOREIGN KEY ("${colDiff.colName}") REFERENCES "${fk.referencedTableName}" ("${fk.referencedColumnName}");`; } getCreateIndex(index, schema, tableName) { return `CREATE INDEX "${index.name}" ON "${schema}"."${tableName}" (${index.properties.map(prop => `"${prop}"`).join(', ')});`; } getAddColumn(schema, tableName, colName, colDiff, colDiffInstructions) { let beforeSql = ``; let sql = ``; if (colDiff.colChanges?.enumItems) { beforeSql += `CREATE TYPE "${schema}_${tableName}_${colDiff.colName}_enum" AS ENUM (${colDiff.colChanges.enumItems.map(item => `'${item}'`).join(', ')});`; colDiffInstructions.push(beforeSql); sql += `ALTER TABLE "${schema}"."${tableName}" ADD COLUMN "${colDiff.colName}" "${schema}_${tableName}_${colDiff.colName}_enum"`; } else { sql += `ALTER TABLE "${schema}"."${tableName}" ADD COLUMN "${colName}" ${colDiff.colType}${(colDiff.colLength ? `(${colDiff.colLength})` : '')}`; } if (!colDiff.colChanges?.nullable) { sql += ' NOT NULL'; } if (colDiff.colChanges?.primary) { sql += ' PRIMARY KEY'; } if (colDiff.colChanges?.unique) { sql += ' UNIQUE'; } if (colDiff.colChanges?.default) { sql += ` DEFAULT ${colDiff.colChanges.default}`; } colDiffInstructions.push(sql.concat(';')); if (colDiff.colChanges?.foreignKeys) { colDiff.colChanges.foreignKeys.forEach(fk => { colDiffInstructions.push(`ALTER TABLE "${schema}"."${tableName}" ADD CONSTRAINT "${tableName}_${colName}_fk" FOREIGN KEY ("${colName}") REFERENCES "${fk.referencedTableName}" ("${fk.referencedColumnName}");`); }); } } getDropColumn(colDiffInstructions, schema, tableName, colName) { colDiffInstructions.push(`ALTER TABLE "${schema}"."${tableName}" DROP COLUMN IF EXISTS "${colName}";`); } getDropIndex(index, schema, tableName) { return `${this.getDropConstraint(index, schema, tableName)}`; } getAlterTableType(schema, tableName, colName, colDiff) { return `ALTER TABLE "${schema}"."${tableName}" ALTER COLUMN "${colName}" TYPE ${colDiff.colType}${(colDiff.colLength ? `(${colDiff.colLength})` : '')};`; } getAlterTableDefaultInstruction(schema, tableName, colName, colDiff) { return `ALTER TABLE "${schema}"."${tableName}" ALTER COLUMN "${colName}" SET DEFAULT ${colDiff.colChanges.default};`; } getAlterTablePrimaryKeyInstruction(schema, tableName, colName, colDiff) { return `ALTER TABLE "${schema}"."${tableName}" ADD PRIMARY KEY ("${colName}");`; } getDropConstraint(param, schema, tableName) { return `ALTER TABLE "${schema}"."${tableName}" DROP CONSTRAINT "${param.name}";`; } getAddUniqueConstraint(schema, tableName, colName) { return `ALTER TABLE "${schema}"."${tableName}" ADD UNIQUE ("${colName}");`; } getAlterTableDropNullInstruction(schema, tableName, colName, colDiff) { return `ALTER TABLE "${schema}"."${tableName}" ALTER COLUMN "${colName}" DROP NOT NULL;`; } getAlterTableDropNotNullInstruction(schema, tableName, colName, colDiff) { return `ALTER TABLE "${schema}"."${tableName}" ALTER COLUMN "${colName}" SET NOT NULL;`; } getAlterTableEnumInstruction(schema, tableName, colName, colDiff) { return `ALTER TABLE "${schema}"."${tableName}" ALTER COLUMN "${colName}" TYPE varchar(255);DROP TYPE IF EXISTS "${schema}_${tableName}_${colName}_enum";CREATE TYPE "${schema}_${tableName}_${colName}_enum" AS ENUM (${colDiff.colChanges.enumItems.map(item => `'${item}'`).join(', ')});ALTER TABLE "${schema}"."${tableName}" ALTER COLUMN "${colName}" TYPE "${schema}_${tableName}_${colName}_enum" USING "${colName}"::text::"${schema}_${tableName}_${colName}_enum"`; } getDropTypeEnumInstruction(param, schema, tableName) { return `DROP TYPE IF EXISTS "${param.name}";`; } async startTransaction() { await this.client.query('BEGIN;'); } async commitTransaction() { await this.client.query('COMMIT;'); } async rollbackTransaction() { await this.client.query('ROLLBACK;'); } async executeStatement(statement) { let { statement: statementType, table, columns, where, limit, alias } = statement; let sql = ''; switch (statementType) { case 'select': sql = `SELECT ${columns ? columns.join(', ') : '*'} FROM ${table} ${alias}`; break; case 'insert': // TODO: Tratar corretamente os valores string, number const fields = Object.keys(statement.values).map(v => `"${v}"`).join(', '); const values = Object.values(statement.values).map(value => this.toDatabaseValue(value)).join(', '); sql = `INSERT INTO ${table} (${fields}) VALUES (${values}) RETURNING ${statement.columns.join(', ').replaceAll(`${alias}.`, '')}`; break; case 'update': sql = `UPDATE ${table} as ${alias} SET ${Object.entries(statement.values).map(([key, value]) => `${key} = ${this.toDatabaseValue(value)}`).join(', ')}`; break; case 'delete': break; } if (statement.join) { statement.join.forEach(join => { sql += ` ${join.type} JOIN ${join.joinSchema}.${join.joinTable} ${join.joinAlias} ON ${join.on}`; }); } if (statementType !== 'insert') { if (where) { sql += ` WHERE ${where}`; } if (statement.orderBy) { sql += ` ORDER BY ${statement.orderBy.join(', ')}`; } if (statement.offset) { sql += ` OFFSET ${statement.offset}`; } if (limit) { sql += ` LIMIT ${statement.limit}`; } } const startTime = Date.now(); return { query: await this.client.query(sql), startTime, sql }; } connect() { return this.client.connect(); } disconnect() { return this.client.end(); } executeSql(s) { return this.client.query(s); } async snapshot(tableName, options) { const schema = (options && options.schema) || 'public'; const sql = `SELECT * FROM information_schema.columns WHERE table_name = '${tableName}' AND table_schema = '${schema}'`; const result = await this.client.query(sql); if (!result.rows || result.rows.length === 0) { return; } const indexes = await this.index(tableName, options) || []; const constraints = await this.constraints(tableName, options) || []; let enums = await this.getEnums(tableName, schema); // @ts-ignore enums = enums.reduce((acc, curr) => { if (!acc[curr.type]) { acc[curr.type] = []; } acc[curr.type].push(curr.label); return acc; }, {}); return { tableName, schema, indexes, columns: result.rows.map(row => { // console.log(this.getForeignKeys(constraints, row), row.column_name) return { default: row.column_default, length: row.character_maximum_length || row.numeric_precision || row.datetime_precision, name: row.column_name, nullable: row.is_nullable === 'YES', primary: constraints.some(c => c.type === 'PRIMARY KEY' && c.consDef.includes(row.column_name)), unique: constraints.some(c => (c.type === 'UNIQUE' || c.type === 'PRIMARY KEY') && c.consDef.includes(row.column_name)), type: row.data_type, foreignKeys: this.getForeignKeys(constraints, row), isEnum: row.data_type === 'USER-DEFINED', enumItems: row.data_type === 'USER-DEFINED' ? enums[`${schema}_${tableName}_${row.column_name}_enum`] : undefined, precision: row.numeric_precision, scale: row.numeric_scale, isDecimal: row.data_type === 'numeric', }; }) }; } getForeignKeys(constraints, row) { const name = row.column_name; return constraints.filter(c => c.type === 'FOREIGN KEY' && c.consDef.match(new RegExp(`FOREIGN KEY \\("${row.column_name}"\\)`))).map(c => { const filter = c.consDef.match(/REFERENCES\s+"([^"]+)"\s*\(([^)]+)\)/); if (!filter) throw new Error('Invalid constraint definition'); return { referencedColumnName: filter[2].split(',')[0].trim(), referencedTableName: filter[1], }; }); } async index(tableName, options) { const schema = (options && options.schema) || 'public'; let result = await this.client.query(`SELECT indexname AS index_name, indexdef AS column_name, tablename AS table_name FROM pg_indexes WHERE tablename = '${tableName}' AND schemaname = '${schema}'`); //@ts-ignore result.rows = result.rows.filter(row => row.index_name.includes('_pkey') || !row.column_name.includes('UNIQUE INDEX')); return result.rows.map(row => { return { table: tableName, indexName: row.index_name, columnName: row.column_name }; }); } async constraints(tableName, options) { const schema = (options && options.schema) || 'public'; const result = await this.client.query(`SELECT conname AS index_name, pg_get_constraintdef(pg_constraint.oid) as consdef, CASE contype WHEN 'c' THEN 'CHECK' WHEN 'f' THEN 'FOREIGN KEY' WHEN 'p' THEN 'PRIMARY KEY' WHEN 'u' THEN 'UNIQUE' WHEN 't' THEN 'TRIGGER' WHEN 'x' THEN 'EXCLUSION' ELSE 'UNKNOWN' END AS constraint_type FROM pg_constraint where conrelid = ( SELECT oid FROM pg_class WHERE relname = '${tableName}' AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = '${schema}') ) AND conkey @> ARRAY( SELECT attnum FROM pg_attribute WHERE attrelid = conrelid AND attname = '${schema}' )`); return result.rows.map(row => { return { indexName: row.index_name, type: row.constraint_type, consDef: row.consdef }; }); } toDatabaseValue(value) { if (value instanceof Date) { return `'${value.toISOString()}'`; } switch (typeof value) { case 'string': return `'${value}'`; case 'number': return value; case 'boolean': return value; case 'object': return `'${JSON.stringify(value)}'`; default: return `'${value}'`; } } async getEnums(tableName, schema) { const result = await this.client.query(`SELECT e.enumlabel as label, t.typname as type FROM pg_type t JOIN pg_enum e ON t.oid = e.enumtypid JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname = '${schema}'`); return result.rows.map(row => { return { label: row.label, type: row.type }; }); } } exports.PgDriver = PgDriver; //# sourceMappingURL=pg-driver.js.map