const SQL_QUERIES = { TABLE_LIST: /* sql */ ` SELECT * FROM information_schema.tables WHERE table_schema = ? AND table_type = 'BASE TABLE' AND table_name != 'geometry_columns' AND table_name != 'spatial_ref_sys'; `, BULK_COLUMNS: /* sql */ ` SELECT table_name, data_type, column_name, character_maximum_length, column_default, is_nullable FROM information_schema.columns WHERE table_schema = ? AND table_name = ANY(?); `, BULK_INDEXES: /* sql */ ` SELECT t.relname as table_name, ix.indexrelid, i.relname as index_name, a.attname as column_name, ix.indisunique as is_unique, ix.indisprimary as is_primary FROM pg_class t, pg_namespace s, pg_class i, pg_index ix, pg_attribute a WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND a.attnum = ANY(ix.indkey) AND t.relkind = 'r' AND t.relnamespace = s.oid AND s.nspname = ? AND t.relname = ANY(?); `, BULK_FOREIGN_KEYS: /* sql */ ` SELECT cl.relname AS table_name, c.conname AS constraint_name, att.attname AS column_name, fcl.relname AS foreign_table, fatt.attname AS fk_column_name, CASE c.confupdtype WHEN 'a' THEN 'NO ACTION' WHEN 'r' THEN 'RESTRICT' WHEN 'c' THEN 'CASCADE' WHEN 'n' THEN 'SET NULL' WHEN 'd' THEN 'SET DEFAULT' END AS on_update, CASE c.confdeltype WHEN 'a' THEN 'NO ACTION' WHEN 'r' THEN 'RESTRICT' WHEN 'c' THEN 'CASCADE' WHEN 'n' THEN 'SET NULL' WHEN 'd' THEN 'SET DEFAULT' END AS on_delete FROM pg_constraint c JOIN pg_class cl ON cl.oid = c.conrelid JOIN pg_namespace n ON n.oid = cl.relnamespace JOIN pg_class fcl ON fcl.oid = c.confrelid JOIN LATERAL unnest(c.conkey, c.confkey) AS cols(conkey, confkey) ON true JOIN pg_attribute att ON att.attrelid = c.conrelid AND att.attnum = cols.conkey JOIN pg_attribute fatt ON fatt.attrelid = c.confrelid AND fatt.attnum = cols.confkey WHERE c.contype = 'f' AND n.nspname = ? AND cl.relname = ANY(?) AND fcl.relnamespace = cl.relnamespace ORDER BY cl.relname, c.conname, cols.conkey; ` }; const toStrapiType = (column)=>{ const rootType = column.data_type.toLowerCase().match(/[^(), ]+/)?.[0]; switch(rootType){ case 'integer': { // find a way to figure out the increments return { type: 'integer' }; } case 'text': { return { type: 'text', args: [ 'longtext' ] }; } case 'boolean': { return { type: 'boolean' }; } case 'character': { return { type: 'string', args: [ column.character_maximum_length ] }; } case 'timestamp': { return { type: 'datetime', args: [ { useTz: false, precision: 6 } ] }; } case 'date': { return { type: 'date' }; } case 'time': { return { type: 'time', args: [ { precision: 3 } ] }; } case 'numeric': { return { type: 'decimal', args: [ 10, 2 ] }; } case 'real': case 'double': { return { type: 'double' }; } case 'bigint': { return { type: 'bigInteger' }; } case 'jsonb': { return { type: 'jsonb' }; } default: { return { type: 'specificType', args: [ column.data_type ] }; } } }; const getIndexType = (index)=>{ if (index.is_primary) { return 'primary'; } if (index.is_unique) { return 'unique'; } }; class PostgresqlSchemaInspector { async getSchema() { const schema = { tables: [] }; const dbSchema = this.getDatabaseSchema(); const tables = await this.getTables(); if (tables.length === 0) { return schema; } // Run 3 bulk queries in parallel instead of N+1 per-table queries const [allColumns, allIndexes, allForeignKeys] = await Promise.all([ this.getBulkColumns(dbSchema, tables), this.getBulkIndexes(dbSchema, tables), this.getBulkForeignKeys(dbSchema, tables) ]); schema.tables = tables.map((tableName)=>({ name: tableName, columns: allColumns.get(tableName) ?? [], indexes: allIndexes.get(tableName) ?? [], foreignKeys: allForeignKeys.get(tableName) ?? [] })); return schema; } getDatabaseSchema() { return this.db.getSchemaName() || 'public'; } async getTables() { const { rows } = await this.db.connection.raw(SQL_QUERIES.TABLE_LIST, [ this.getDatabaseSchema() ]); return rows.map((row)=>row.table_name); } async getBulkColumns(dbSchema, tableNames) { if (tableNames.length === 0) { return new Map(); } const { rows } = await this.db.connection.raw(SQL_QUERIES.BULK_COLUMNS, [ dbSchema, tableNames ]); const result = new Map(); for (const row of rows){ const { type, args = [], ...rest } = toStrapiType(row); const defaultTo = row.column_default && row.column_default.includes('nextval(') ? null : row.column_default; const column = { type, args, defaultTo, name: row.column_name, notNullable: row.is_nullable === 'NO', unsigned: false, ...rest }; const existing = result.get(row.table_name); if (existing) { existing.push(column); } else { result.set(row.table_name, [ column ]); } } return result; } async getBulkIndexes(dbSchema, tableNames) { if (tableNames.length === 0) { return new Map(); } const { rows } = await this.db.connection.raw(SQL_QUERIES.BULK_INDEXES, [ dbSchema, tableNames ]); // Group by table, then by indexrelid const byTable = new Map(); for (const index of rows){ if (index.column_name === 'id') { continue; } let tableIndexes = byTable.get(index.table_name); if (!tableIndexes) { tableIndexes = new Map(); byTable.set(index.table_name, tableIndexes); } const existing = tableIndexes.get(index.indexrelid); if (existing) { existing.columns.push(index.column_name); } else { tableIndexes.set(index.indexrelid, { columns: [ index.column_name ], name: index.index_name, type: getIndexType(index) }); } } const result = new Map(); for (const [tableName, tableIndexes] of byTable){ result.set(tableName, Array.from(tableIndexes.values())); } return result; } async getIndexes(tableName) { const dbSchema = this.getDatabaseSchema(); const result = await this.getBulkIndexes(dbSchema, [ tableName ]); return result.get(tableName) ?? []; } async getForeignKeys(tableName) { const dbSchema = this.getDatabaseSchema(); const result = await this.getBulkForeignKeys(dbSchema, [ tableName ]); return result.get(tableName) ?? []; } async getBulkForeignKeys(dbSchema, tableNames) { if (tableNames.length === 0) { return new Map(); } const { rows } = await this.db.connection.raw(SQL_QUERIES.BULK_FOREIGN_KEYS, [ dbSchema, tableNames ]); // Group by table_name, then by constraint_name const byTable = new Map(); for (const row of rows){ let tableFKs = byTable.get(row.table_name); if (!tableFKs) { tableFKs = new Map(); byTable.set(row.table_name, tableFKs); } let fk = tableFKs.get(row.constraint_name); if (!fk) { fk = { name: row.constraint_name, columns: [ row.column_name ], referencedColumns: [ row.fk_column_name ], referencedTable: row.foreign_table, onUpdate: row.on_update?.toUpperCase() ?? null, onDelete: row.on_delete?.toUpperCase() ?? null }; tableFKs.set(row.constraint_name, fk); } else { if (!fk.columns.includes(row.column_name)) { fk.columns.push(row.column_name); } if (!fk.referencedColumns.includes(row.fk_column_name)) { fk.referencedColumns.push(row.fk_column_name); } } } const result = new Map(); for (const [tableName, tableFKs] of byTable){ result.set(tableName, Array.from(tableFKs.values())); } return result; } constructor(db){ this.db = db; } } export { PostgresqlSchemaInspector as default }; //# sourceMappingURL=schema-inspector.mjs.map