admindb 2.1.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +368 -335
- package/dist/app.js +227 -1
- package/dist/auth/config.d.ts +1 -1
- package/dist/auth/config.js +109 -1
- package/dist/auth/crypto.js +98 -1
- package/dist/auth/index.js +21 -1
- package/dist/auth/middleware.js +78 -1
- package/dist/auth/routes.js +64 -1
- package/dist/auth/types.js +7 -1
- package/dist/cli/args.d.ts +1 -1
- package/dist/cli/args.js +309 -1
- package/dist/cli/config.js +118 -1
- package/dist/cli/index.js +20 -1
- package/dist/cli/runner.js +208 -1
- package/dist/cli.js +4 -1
- package/dist/data/chain.d.ts +1 -0
- package/dist/data/chain.js +130 -0
- package/dist/data/datasets.d.ts +1 -1
- package/dist/data/datasets.js +171 -1
- package/dist/data/detector.d.ts +1 -1
- package/dist/data/detector.js +535 -1
- package/dist/data/engine.d.ts +1 -1
- package/dist/data/engine.js +586 -1
- package/dist/data/index.d.ts +1 -1
- package/dist/data/index.js +30 -1
- package/dist/data/prng.d.ts +1 -0
- package/dist/data/prng.js +73 -0
- package/dist/data/registry.d.ts +1 -0
- package/dist/data/registry.js +805 -0
- package/dist/data/schema-graph.d.ts +1 -0
- package/dist/data/schema-graph.js +210 -0
- package/dist/data/strategies.d.ts +1 -1
- package/dist/data/strategies.js +100 -1
- package/dist/data/templates.d.ts +1 -0
- package/dist/data/templates.js +134 -0
- package/dist/data/types.d.ts +1 -1
- package/dist/data/types.js +5 -1
- package/dist/data/validator.d.ts +1 -0
- package/dist/data/validator.js +160 -0
- package/dist/db/database.d.ts +1 -1
- package/dist/db/database.js +472 -1
- package/dist/db/export.d.ts +1 -1
- package/dist/db/export.js +67 -1
- package/dist/db/filters.d.ts +1 -1
- package/dist/db/filters.js +119 -1
- package/dist/db/index.js +24 -1
- package/dist/db/introspection.js +164 -1
- package/dist/db/manager.js +327 -1
- package/dist/db/migrations.js +100 -1
- package/dist/db/postgres.d.ts +1 -1
- package/dist/db/postgres.js +687 -1
- package/dist/db/types.d.ts +1 -1
- package/dist/db/types.js +6 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +52 -1
- package/dist/public/css/app.css +1 -1
- package/dist/public/js/api.js +1 -1
- package/dist/public/js/browse.js +1 -1
- package/dist/public/js/common.js +1 -0
- package/dist/public/js/databases.js +1 -1
- package/dist/public/js/designer.js +1 -1
- package/dist/public/js/forms.js +1 -1
- package/dist/public/js/home.js +1 -0
- package/dist/public/js/inspector.js +1 -1
- package/dist/public/js/query.js +1 -1
- package/dist/public/js/schema.js +1 -1
- package/dist/public/js/seed.js +1 -1
- package/dist/routes/api/erd.d.ts +1 -0
- package/dist/routes/api/erd.js +45 -0
- package/dist/routes/api/helpers.d.ts +1 -1
- package/dist/routes/api/helpers.js +213 -1
- package/dist/routes/api/import-export.js +82 -1
- package/dist/routes/api/index.js +48 -1
- package/dist/routes/api/query.js +74 -1
- package/dist/routes/api/rows.js +358 -1
- package/dist/routes/api/seed.js +167 -1
- package/dist/routes/api/tables.js +216 -1
- package/dist/routes/databases.js +210 -1
- package/dist/routes/index.js +19 -1
- package/dist/routes/pages.js +446 -1
- package/dist/serverless.js +149 -1
- package/dist/sql/classifier.js +24 -1
- package/dist/sql/error-analyzer.d.ts +1 -0
- package/dist/sql/error-analyzer.js +550 -0
- package/dist/sql/generator.d.ts +1 -1
- package/dist/sql/generator.js +245 -1
- package/dist/sql/index.js +18 -1
- package/dist/types/api.d.ts +1 -1
- package/dist/types/api.js +2 -1
- package/dist/utils/colors.js +54 -1
- package/dist/utils/common.d.ts +1 -1
- package/dist/utils/common.js +185 -1
- package/dist/utils/csv.d.ts +1 -1
- package/dist/utils/csv.js +61 -1
- package/dist/utils/datatype.js +167 -1
- package/dist/utils/icons.js +81 -1
- package/dist/utils/index.js +20 -1
- package/dist/utils/logger.js +36 -1
- package/dist/views/layouts/main.hbs +1 -74
- package/dist/views/pages/databases.hbs +1 -233
- package/dist/views/pages/designer.hbs +1 -89
- package/dist/views/pages/erd.hbs +1 -0
- package/dist/views/pages/error.hbs +1 -10
- package/dist/views/pages/form.hbs +1 -59
- package/dist/views/pages/home.hbs +1 -124
- package/dist/views/pages/info.hbs +1 -0
- package/dist/views/pages/login.hbs +1 -98
- package/dist/views/pages/query.hbs +1 -87
- package/dist/views/pages/schema.hbs +1 -298
- package/dist/views/pages/seed-select.hbs +251 -0
- package/dist/views/pages/seed.hbs +273 -167
- package/dist/views/pages/table.hbs +1 -327
- package/dist/views/partials/navbar.hbs +1 -83
- package/dist/views/partials/sidebar.hbs +1 -117
- package/docs/API.md +149 -144
- package/docs/EXAMPLES.md +579 -576
- package/docs/SECURITY.md +265 -265
- package/package.json +79 -78
- package/dist/public/js/icons.js +0 -1
package/dist/db/postgres.js
CHANGED
|
@@ -1 +1,687 @@
|
|
|
1
|
-
Object.defineProperty(exports,"__esModule",{value:!0}),exports.PostgresDatabase=void 0;const pg_1=require("pg"),generator_1=require("../sql/generator"),common_1=require("../utils/common"),types_1=require("./types"),filters_1=require("./filters");exports.PostgresDatabase=class{pool;logger;path;isReadOnly;dialect="postgres";schema;constructor(connection,logger,options={}){this.logger=logger;const opts="string"==typeof connection?{connectionString:connection,...options}:{...connection,...options};this.isReadOnly=Boolean(opts.readonly),this.schema=opts.schema||"public";let sslConfig=opts.ssl;if(opts.connectionString)try{const u=new URL(opts.connectionString),sslMode=(u.searchParams.get("sslmode")||u.searchParams.get("ssl")||"").toLowerCase(),host=u.hostname.toLowerCase();"disable"===sslMode?sslConfig=!1:void 0===sslConfig&&("require"!==sslMode&&"prefer"!==sslMode&&"no-verify"!==sslMode&&("localhost"===host||"127.0.0.1"===host||"::1"===host||"0.0.0.0"===host)||(sslConfig={rejectUnauthorized:!1}))}catch{}else if(opts.host&&void 0===sslConfig){const host=opts.host.toLowerCase();"localhost"===host||"127.0.0.1"===host||"::1"===host||"0.0.0.0"===host||(sslConfig={rejectUnauthorized:!1})}const poolConfig={connectionString:opts.connectionString,host:opts.host,port:opts.port,database:opts.database,user:opts.user,password:opts.password,ssl:sslConfig,max:opts.max??10,connectionTimeoutMillis:1e4,idleTimeoutMillis:3e4,keepAlive:!0};this.path=opts.connectionString?this.sanitizeConnectionString(opts.connectionString):`${opts.user||"postgres"}@${opts.host||"localhost"}:${opts.port||5432}/${opts.database||"postgres"}`,this.pool=new pg_1.Pool(poolConfig),this.pool.on("error",err=>{this.logger.error(`Unexpected PostgreSQL client error: ${(0,common_1.errorMessage)(err)}`)}),this.isReadOnly||this.initSchema().catch(err=>{this.logger.warn(`Failed to initialize Postgres schema: ${(0,common_1.errorMessage)(err)}`)}),this.logger.info(`Initialized PostgreSQL pool for ${this.path}${this.isReadOnly?" (read-only)":""}`)}sanitizeConnectionString(conn){try{const url=new URL(conn);return url.password&&(url.password="****"),url.toString()}catch{return conn.replace(/:([^@]+)@/,":****@")}}readonlyBlocked(){return{success:!1,error:"Database is open in read-only mode — write operations are disabled."}}async close(){try{await this.pool.end()}catch(err){this.logger.warn(`Failed to close PostgreSQL pool: ${(0,common_1.errorMessage)(err)}`)}}async tryRun(fn){try{return{success:!0,data:await fn()}}catch(err){const msg=(0,common_1.errorMessage)(err);return this.logger.error(msg),{success:!1,error:msg}}}async transaction(fn){if(this.isReadOnly)return this.readonlyBlocked();const client=await this.pool.connect();try{await client.query("BEGIN");const res=await fn(client);return await client.query("COMMIT"),{success:!0,data:res}}catch(err){try{await client.query("ROLLBACK")}catch{}const msg=(0,common_1.errorMessage)(err);return this.logger.error(msg),{success:!1,error:msg}}finally{client.release()}}async initSchema(){const sql=`\n CREATE TABLE IF NOT EXISTS ${(0,generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} (\n id SERIAL PRIMARY KEY,\n name VARCHAR(255) NOT NULL UNIQUE,\n sql TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n );\n `;await this.pool.query(sql)}hasMultipleStatements(sql){const s=String(sql??"").trim();if(!s)return!1;let count=0,inString=!1;for(let i=0;i<s.length;i++){const ch=s[i];if("'"===ch){if(inString&&"'"===s[i+1]){i++;continue}inString=!inString}else if(";"===ch&&!inString){s.slice(i+1).trim().length>0&&count++}}return count>0}async all(sql,params=[]){return this.tryRun(async()=>{const pgSql=(0,filters_1.convertPlaceholdersToPostgres)(sql);return(await this.pool.query(pgSql,params)).rows})}async run(sql,params=[]){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>{const pgSql=(0,filters_1.convertPlaceholdersToPostgres)(sql);return{changes:(await this.pool.query(pgSql,params)).rowCount??0,lastInsertRowid:null}})}async execResult(sql){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>{const res=await this.pool.query(sql);return{changes:Array.isArray(res)?res.reduce((acc,r)=>acc+(r.rowCount??0),0):res.rowCount??void 0}})}async runWrite(sql){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>{const res=await this.pool.query(sql);return{changes:Array.isArray(res)?res.reduce((acc,r)=>acc+(r.rowCount??0),0):res.rowCount??void 0}})}async listTables(){return this.tryRun(async()=>(await this.pool.query("SELECT table_name AS name\n FROM information_schema.tables\n WHERE table_schema = $1 AND table_type = 'BASE TABLE'\n ORDER BY table_name;",[this.schema])).rows.map(r=>({name:r.name})))}async getTableInfo(table){return this.tryRun(async()=>{const[colRes,pkRes,fkRes,idxRes]=await Promise.all([this.pool.query("SELECT\n ordinal_position AS cid,\n column_name AS name,\n data_type AS type,\n udt_name,\n CASE WHEN is_nullable = 'NO' THEN 1 ELSE 0 END AS notnull,\n column_default AS dflt_value\n FROM information_schema.columns\n WHERE table_schema = $1 AND table_name = $2\n ORDER BY ordinal_position;",[this.schema,table]),this.pool.query("SELECT kcu.column_name\n FROM information_schema.table_constraints tc\n JOIN information_schema.key_column_usage kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n WHERE tc.constraint_type = 'PRIMARY KEY'\n AND tc.table_schema = $1\n AND tc.table_name = $2\n ORDER BY kcu.ordinal_position;",[this.schema,table]),this.pool.query('SELECT\n row_number() OVER () AS id,\n kcu.position_in_unique_constraint AS seq,\n ccu.table_name AS table,\n kcu.column_name AS "from",\n ccu.column_name AS "to",\n rc.update_rule AS on_update,\n rc.delete_rule AS on_delete\n FROM information_schema.table_constraints AS tc\n JOIN information_schema.key_column_usage AS kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n JOIN information_schema.constraint_column_usage AS ccu\n ON ccu.constraint_name = tc.constraint_name\n AND ccu.table_schema = tc.table_schema\n JOIN information_schema.referential_constraints AS rc\n ON rc.constraint_name = tc.constraint_name\n AND rc.constraint_schema = tc.table_schema\n WHERE tc.constraint_type = \'FOREIGN KEY\'\n AND tc.table_schema = $1\n AND tc.table_name = $2;',[this.schema,table]),this.pool.query("SELECT\n i.relname AS name,\n ix.indisunique AS unique,\n pg_get_indexdef(ix.indexrelid) AS sql,\n ARRAY(\n SELECT pg_get_indexdef(ix.indexrelid, k + 1, true)\n FROM generate_subscripts(ix.indkey, 1) as k\n ORDER BY k\n ) AS columns\n FROM pg_index ix\n JOIN pg_class t ON t.oid = ix.indrelid\n JOIN pg_class i ON i.oid = ix.indexrelid\n JOIN pg_namespace n ON n.oid = t.relnamespace\n WHERE n.nspname = $1 AND t.relname = $2;",[this.schema,table])]),primaryKey=pkRes.rows.map(r=>r.column_name),columns=colRes.rows.map(c=>{const isPk=primaryKey.includes(c.name),displayType="USER-DEFINED"===c.type?c.udt_name:c.type;return{cid:c.cid,name:c.name,type:displayType.toUpperCase(),notnull:c.notnull,dflt_value:c.dflt_value,pk:isPk?primaryKey.indexOf(c.name)+1:0}}),indexes=idxRes.rows.map(ix=>({name:ix.name,unique:Boolean(ix.unique),partial:0,origin:"c",columns:Array.isArray(ix.columns)?ix.columns:[],sql:ix.sql})),colDefs=columns.map(c=>{let def=` ${(0,generator_1.quoteIdentifier)(c.name)} ${c.type}`;return c.notnull&&(def+=" NOT NULL"),c.dflt_value&&(def+=` DEFAULT ${c.dflt_value}`),def});primaryKey.length&&colDefs.push(` PRIMARY KEY (${primaryKey.map(generator_1.quoteIdentifier).join(", ")})`);const synthesizedSql=`CREATE TABLE ${(0,generator_1.quoteIdentifier)(table)} (\n${colDefs.join(",\n")}\n);`;return{table:table,columns:columns,foreignKeys:fkRes.rows.map(f=>({id:Number(f.id),seq:Number(f.seq??1),table:f.table,from:f.from,to:f.to,on_update:f.on_update,on_delete:f.on_delete})),primaryKey:primaryKey,indexes:indexes,sql:synthesizedSql}})}async getRowCount(table,filters){return this.tryRun(async()=>{let pgWhere="",params=[];if(filters&&Object.keys(filters).length>0){const{where:where,params:p}=(0,filters_1.buildFilterClause)(filters,Object.keys(filters));pgWhere=(0,filters_1.convertPlaceholdersToPostgres)(where),params=p}const sql=`SELECT COUNT(*) AS c FROM ${(0,generator_1.quoteIdentifier)(table)}${pgWhere}`,res=await this.pool.query(sql,params);return Number(res.rows[0]?.c??0)})}async getRows(table,opts={}){return this.tryRun(async()=>{const orderCol=opts.orderBy,orderDir="desc"===opts.orderDir?"DESC":"ASC",limit=opts.limit&&opts.limit>0?opts.limit:200,offset=((opts.page&&opts.page>0?opts.page:1)-1)*limit;let pgWhere="",filterParams=[];if(opts.filters&&Object.keys(opts.filters).length>0){const{where:where,params:params}=(0,filters_1.buildFilterClause)(opts.filters,Object.keys(opts.filters));pgWhere=(0,filters_1.convertPlaceholdersToPostgres)(where),filterParams=params}const nextParamIdx=filterParams.length+1;let sql=`SELECT * FROM ${(0,generator_1.quoteIdentifier)(table)}${pgWhere}`;orderCol&&(sql+=` ORDER BY ${(0,generator_1.quoteIdentifier)(orderCol)} ${orderDir}`),sql+=` LIMIT $${nextParamIdx} OFFSET $${nextParamIdx+1}`;const allParams=[...filterParams,limit,offset];return(await this.pool.query(sql,allParams)).rows})}async getAllRows(table){return this.tryRun(async()=>(await this.pool.query(`SELECT * FROM ${(0,generator_1.quoteIdentifier)(table)}`)).rows)}async getRow(table,where){return this.tryRun(async()=>{const conds=where.map((w,idx)=>`${(0,generator_1.quoteIdentifier)(w.column)} = $${idx+1}`),sql=`SELECT * FROM ${(0,generator_1.quoteIdentifier)(table)} WHERE ${conds.join(" AND ")} LIMIT 1`;return(await this.pool.query(sql,where.map(w=>w.value))).rows[0]??null})}async insertRow(table,fields){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>{const cols=fields.map(f=>(0,generator_1.quoteIdentifier)(f.column)),placeholders=fields.map((_,idx)=>`$${idx+1}`),sql=`INSERT INTO ${(0,generator_1.quoteIdentifier)(table)} (${cols.join(", ")}) VALUES (${placeholders.join(", ")}) RETURNING *`;return{changes:(await this.pool.query(sql,fields.map(f=>f.value))).rowCount??1,lastInsertRowid:null}})}async insertRows(table,rows){return this.transaction(async client=>{let inserted=0;for(const fields of rows){if(!fields.length)continue;const cols=fields.map(f=>(0,generator_1.quoteIdentifier)(f.column)),placeholders=fields.map((_,idx)=>`$${idx+1}`),sql=`INSERT INTO ${(0,generator_1.quoteIdentifier)(table)} (${cols.join(", ")}) VALUES (${placeholders.join(", ")})`;await client.query(sql,fields.map(f=>f.value)),inserted++}return{inserted:inserted,skipped:rows.length-inserted}})}async updateRow(table,fields,where){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>{const sets=fields.map((f,idx)=>`${(0,generator_1.quoteIdentifier)(f.column)} = $${idx+1}`),conds=where.map((w,idx)=>`${(0,generator_1.quoteIdentifier)(w.column)} = $${fields.length+idx+1}`),sql=`UPDATE ${(0,generator_1.quoteIdentifier)(table)} SET ${sets.join(", ")} WHERE ${conds.join(" AND ")}`;return{changes:(await this.pool.query(sql,[...fields.map(f=>f.value),...where.map(w=>w.value)])).rowCount??0,lastInsertRowid:null}})}async updateRows(table,rows){return this.transaction(async client=>{let updated=0;for(const{fields:fields,where:where}of rows){if(!fields.length||!where.length)continue;const sets=fields.map((f,idx)=>`${(0,generator_1.quoteIdentifier)(f.column)} = $${idx+1}`),conds=where.map((w,idx)=>`${(0,generator_1.quoteIdentifier)(w.column)} = $${fields.length+idx+1}`),sql=`UPDATE ${(0,generator_1.quoteIdentifier)(table)} SET ${sets.join(", ")} WHERE ${conds.join(" AND ")}`;updated+=(await client.query(sql,[...fields.map(f=>f.value),...where.map(w=>w.value)])).rowCount??0}return{updated:updated}})}async deleteRow(table,where){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>{const conds=where.map((w,idx)=>`${(0,generator_1.quoteIdentifier)(w.column)} = $${idx+1}`),sql=`DELETE FROM ${(0,generator_1.quoteIdentifier)(table)} WHERE ${conds.join(" AND ")}`;return{changes:(await this.pool.query(sql,where.map(w=>w.value))).rowCount??0,lastInsertRowid:null}})}async deleteRows(table,rows){return this.transaction(async client=>{let deleted=0;for(const where of rows){if(!where.length)continue;const conds=where.map((w,idx)=>`${(0,generator_1.quoteIdentifier)(w.column)} = $${idx+1}`),sql=`DELETE FROM ${(0,generator_1.quoteIdentifier)(table)} WHERE ${conds.join(" AND ")}`;deleted+=(await client.query(sql,where.map(w=>w.value))).rowCount??0}return{deleted:deleted}})}async getRowsByPks(table,pks){return this.tryRun(async()=>{const groups=[],params=[];let paramIdx=1;for(const where of pks){if(!where.length)continue;const conds=where.map(w=>(params.push(w.value),`${(0,generator_1.quoteIdentifier)(w.column)} = $${paramIdx++}`));groups.push(conds.join(" AND "))}if(!groups.length)return[];const sql=`SELECT * FROM ${(0,generator_1.quoteIdentifier)(table)} WHERE ${groups.map(g=>`(${g})`).join(" OR ")}`;return(await this.pool.query(sql,params)).rows})}async getRowsByFk(table,column,value,limit=50){return this.tryRun(async()=>{const n=Math.max(1,Math.min(500,Number(limit)||50)),countRes=await this.pool.query(`SELECT COUNT(*) AS c FROM ${(0,generator_1.quoteIdentifier)(table)} WHERE ${(0,generator_1.quoteIdentifier)(column)} = $1`,[value]);return{rows:(await this.pool.query(`SELECT * FROM ${(0,generator_1.quoteIdentifier)(table)} WHERE ${(0,generator_1.quoteIdentifier)(column)} = $1 LIMIT $2`,[value,n])).rows,total:Number(countRes.rows[0]?.c??0)}})}async getCreateStatement(table){const info=await this.getTableInfo(table);return info.success&&info.data?{success:!0,data:info.data.sql}:{success:!1,error:info.error}}async listSavedQueries(){return this.tryRun(async()=>(await this.pool.query(`SELECT id, name, sql, created_at FROM ${(0,generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} ORDER BY name`)).rows)}async saveQuery(name,sql){return this.tryRun(async()=>{const res=await this.pool.query(`INSERT INTO ${(0,generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} (name, sql) VALUES ($1, $2) RETURNING id`,[name,sql]);return{changes:res.rowCount??1,lastInsertRowid:Number(res.rows[0]?.id??null)}})}async deleteSavedQuery(id){return this.tryRun(async()=>({changes:(await this.pool.query(`DELETE FROM ${(0,generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} WHERE id = $1`,[Number(id)])).rowCount??0,lastInsertRowid:null}))}async getSchema(table){return this.tryRun(async()=>{const infoR=await this.getTableInfo(table);if(!infoR.success||!infoR.data)throw new Error(infoR.error||`Table ${table} not found.`);const{columns:columns,foreignKeys:foreignKeys,indexes:indexes,primaryKey:primaryKey}=infoR.data,references=(await this.pool.query('SELECT\n tc.table_name AS table,\n kcu.column_name AS "from",\n ccu.column_name AS "to"\n FROM information_schema.table_constraints AS tc\n JOIN information_schema.key_column_usage AS kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n JOIN information_schema.constraint_column_usage AS ccu\n ON ccu.constraint_name = tc.constraint_name\n AND ccu.table_schema = tc.table_schema\n WHERE tc.constraint_type = \'FOREIGN KEY\'\n AND tc.table_schema = $1\n AND ccu.table_name = $2;',[this.schema,table])).rows,columnDetails=columns.map(col=>{const isPk=primaryKey.includes(col.name),fk=foreignKeys.find(f=>f.from===col.name)??null,matchingIndexes=indexes.filter(i=>i.columns.includes(col.name)),unique=matchingIndexes.some(i=>i.unique&&1===i.columns.length&&i.columns[0]===col.name),indexed=matchingIndexes.length>0,referencedBy=references.filter(r=>r.to===col.name).map(r=>({table:r.table,from:r.from})),dropBlockers=[];return isPk&&1===primaryKey.length&&dropBlockers.push("Primary key"),referencedBy.length>0&&dropBlockers.push(`Referenced by ${referencedBy.map(r=>`${r.table}.${r.from}`).join(", ")}`),{...col,unique:unique,indexed:indexed,referencedBy:referencedBy,fk:fk,canDrop:0===dropBlockers.length,dropBlockers:dropBlockers}});return{table:table,columns:columnDetails,foreignKeys:foreignKeys,indexes:indexes,references:references}})}async getReferencingTables(table){return this.tryRun(async()=>{const refRes=await this.pool.query('SELECT\n tc.table_name AS table,\n kcu.column_name AS "from",\n ccu.column_name AS "to"\n FROM information_schema.table_constraints AS tc\n JOIN information_schema.key_column_usage AS kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n JOIN information_schema.constraint_column_usage AS ccu\n ON ccu.constraint_name = tc.constraint_name\n AND ccu.table_schema = tc.table_schema\n WHERE tc.constraint_type = \'FOREIGN KEY\'\n AND tc.table_schema = $1\n AND ccu.table_name = $2;',[this.schema,table]),byTable=new Map;for(const r of refRes.rows)byTable.has(r.table)||byTable.set(r.table,[]),byTable.get(r.table).push({from:r.from,to:r.to});const results=[];for(const[tbl,refs]of byTable.entries())try{const countRes=await this.pool.query(`SELECT COUNT(*) AS c FROM ${(0,generator_1.quoteIdentifier)(tbl)} WHERE ${refs.map(r=>`${(0,generator_1.quoteIdentifier)(r.from)} IS NOT NULL`).join(" OR ")}`);results.push({table:tbl,refs:refs,refCount:Number(countRes.rows[0]?.c??0)})}catch{results.push({table:tbl,refs:refs,refCount:0})}return results})}async renameTable(oldName,newName){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>(await this.pool.query(`ALTER TABLE ${(0,generator_1.quoteIdentifier)(oldName)} RENAME TO ${(0,generator_1.quoteIdentifier)(newName)};`),{changes:1}))}async addColumn(table,col){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>{let sql=`ALTER TABLE ${(0,generator_1.quoteIdentifier)(table)} ADD COLUMN ${(0,generator_1.quoteIdentifier)(col.name)} ${col.type}`;if(col.notNull&&(sql+=" NOT NULL"),void 0!==col.defaultValue&&null!==col.defaultValue&&""!==col.defaultValue&&(sql+=` DEFAULT ${col.defaultValue}`),col.foreignKey&&(sql+=` REFERENCES ${(0,generator_1.quoteIdentifier)(col.foreignKey.table)} (${(0,generator_1.quoteIdentifier)(col.foreignKey.column)})`),await this.pool.query(sql),col.unique){const idxName=`idx_${table}_${col.name}_unique`;await this.pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS ${(0,generator_1.quoteIdentifier)(idxName)} ON ${(0,generator_1.quoteIdentifier)(table)} (${(0,generator_1.quoteIdentifier)(col.name)});`)}return{changes:1}})}async modifyColumn(table,oldColName,newColDef){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>{const newName=String(newColDef.name||oldColName).trim();oldColName!==newName&&await this.pool.query(`ALTER TABLE ${(0,generator_1.quoteIdentifier)(table)} RENAME COLUMN ${(0,generator_1.quoteIdentifier)(oldColName)} TO ${(0,generator_1.quoteIdentifier)(newName)};`);const colToModify=newName;if(newColDef.type&&await this.pool.query(`ALTER TABLE ${(0,generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0,generator_1.quoteIdentifier)(colToModify)} TYPE ${newColDef.type} USING ${(0,generator_1.quoteIdentifier)(colToModify)}::${newColDef.type};`),void 0!==newColDef.notNull){const action=newColDef.notNull?"SET NOT NULL":"DROP NOT NULL";await this.pool.query(`ALTER TABLE ${(0,generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0,generator_1.quoteIdentifier)(colToModify)} ${action};`)}if(void 0!==newColDef.defaultValue&&(null===newColDef.defaultValue||""===newColDef.defaultValue?await this.pool.query(`ALTER TABLE ${(0,generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0,generator_1.quoteIdentifier)(colToModify)} DROP DEFAULT;`):await this.pool.query(`ALTER TABLE ${(0,generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0,generator_1.quoteIdentifier)(colToModify)} SET DEFAULT ${newColDef.defaultValue};`)),void 0!==newColDef.unique){const idxName=`idx_${table}_${colToModify}_unique`;newColDef.unique?await this.pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS ${(0,generator_1.quoteIdentifier)(idxName)} ON ${(0,generator_1.quoteIdentifier)(table)} (${(0,generator_1.quoteIdentifier)(colToModify)});`):await this.pool.query(`DROP INDEX IF EXISTS ${(0,generator_1.quoteIdentifier)(idxName)};`)}return{changes:1}})}async renameColumn(table,oldName,newName){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>(await this.pool.query(`ALTER TABLE ${(0,generator_1.quoteIdentifier)(table)} RENAME COLUMN ${(0,generator_1.quoteIdentifier)(oldName)} TO ${(0,generator_1.quoteIdentifier)(newName)};`),{changes:1}))}async dropColumn(table,column){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>(await this.pool.query(`ALTER TABLE ${(0,generator_1.quoteIdentifier)(table)} DROP COLUMN ${(0,generator_1.quoteIdentifier)(column)} CASCADE;`),{changes:1}))}async dropTable(table){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>(await this.pool.query(`DROP TABLE ${(0,generator_1.quoteIdentifier)(table)} CASCADE;`),{changes:1}))}async createIndex(table,index){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>{const unique=index.unique?"UNIQUE ":"",cols=index.columns.map(generator_1.quoteIdentifier).join(", "),name=index.name||`idx_${table}_${index.columns.join("_")}`;return await this.pool.query(`CREATE ${unique}INDEX IF NOT EXISTS ${(0,generator_1.quoteIdentifier)(name)} ON ${(0,generator_1.quoteIdentifier)(table)} (${cols});`),{changes:1}})}async dropIndex(indexName){return this.isReadOnly?this.readonlyBlocked():this.tryRun(async()=>(await this.pool.query(`DROP INDEX IF EXISTS ${(0,generator_1.quoteIdentifier)(indexName)} CASCADE;`),{changes:1}))}};
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PostgresDatabase = void 0;
|
|
4
|
+
const pg_1 = require("pg");
|
|
5
|
+
const generator_1 = require("../sql/generator");
|
|
6
|
+
const common_1 = require("../utils/common");
|
|
7
|
+
const types_1 = require("./types");
|
|
8
|
+
const filters_1 = require("./filters");
|
|
9
|
+
class PostgresDatabase {
|
|
10
|
+
pool;
|
|
11
|
+
logger;
|
|
12
|
+
path;
|
|
13
|
+
isReadOnly;
|
|
14
|
+
dialect = 'postgres';
|
|
15
|
+
schema;
|
|
16
|
+
constructor(connection, logger, options = {}) {
|
|
17
|
+
this.logger = logger;
|
|
18
|
+
const opts = typeof connection === 'string'
|
|
19
|
+
? { connectionString: connection, ...options }
|
|
20
|
+
: { ...connection, ...options };
|
|
21
|
+
this.isReadOnly = Boolean(opts.readonly);
|
|
22
|
+
this.schema = opts.schema || 'public';
|
|
23
|
+
let sslConfig = opts.ssl;
|
|
24
|
+
if (opts.connectionString) {
|
|
25
|
+
try {
|
|
26
|
+
const u = new URL(opts.connectionString);
|
|
27
|
+
const sslMode = (u.searchParams.get('sslmode') || u.searchParams.get('ssl') || '').toLowerCase();
|
|
28
|
+
const host = u.hostname.toLowerCase();
|
|
29
|
+
const isLocal = host === 'localhost' || host === '127.0.0.1' || host === '::1' || host === '0.0.0.0';
|
|
30
|
+
if (sslMode === 'disable') {
|
|
31
|
+
sslConfig = false;
|
|
32
|
+
}
|
|
33
|
+
else if (sslConfig === undefined && (sslMode === 'require' || sslMode === 'prefer' || sslMode === 'no-verify' || !isLocal)) {
|
|
34
|
+
sslConfig = { rejectUnauthorized: false };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch { }
|
|
38
|
+
}
|
|
39
|
+
else if (opts.host && sslConfig === undefined) {
|
|
40
|
+
const host = opts.host.toLowerCase();
|
|
41
|
+
const isLocal = host === 'localhost' || host === '127.0.0.1' || host === '::1' || host === '0.0.0.0';
|
|
42
|
+
if (!isLocal)
|
|
43
|
+
sslConfig = { rejectUnauthorized: false };
|
|
44
|
+
}
|
|
45
|
+
const poolConfig = {
|
|
46
|
+
connectionString: opts.connectionString,
|
|
47
|
+
host: opts.host,
|
|
48
|
+
port: opts.port,
|
|
49
|
+
database: opts.database,
|
|
50
|
+
user: opts.user,
|
|
51
|
+
password: opts.password,
|
|
52
|
+
ssl: sslConfig,
|
|
53
|
+
max: opts.max ?? 10,
|
|
54
|
+
connectionTimeoutMillis: 10000,
|
|
55
|
+
idleTimeoutMillis: 30000,
|
|
56
|
+
keepAlive: true,
|
|
57
|
+
};
|
|
58
|
+
this.path = opts.connectionString
|
|
59
|
+
? (0, common_1.sanitizeConnectionString)(opts.connectionString)
|
|
60
|
+
: `${opts.user || 'postgres'}@${opts.host || 'localhost'}:${opts.port || 5432}/${opts.database || 'postgres'}`;
|
|
61
|
+
this.pool = new pg_1.Pool(poolConfig);
|
|
62
|
+
this.pool.on('error', (err) => {
|
|
63
|
+
this.logger.error(`Unexpected PostgreSQL client error: ${(0, common_1.errorMessage)(err)}`);
|
|
64
|
+
});
|
|
65
|
+
if (!this.isReadOnly) {
|
|
66
|
+
this.initSchema().catch((err) => {
|
|
67
|
+
this.logger.warn(`Failed to initialize Postgres schema: ${(0, common_1.errorMessage)(err)}`);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
this.logger.info(`Initialized PostgreSQL pool for ${this.path}${this.isReadOnly ? ' (read-only)' : ''}`);
|
|
71
|
+
}
|
|
72
|
+
readonlyBlocked() {
|
|
73
|
+
return { success: false, error: 'Database is open in read-only mode — write operations are disabled.' };
|
|
74
|
+
}
|
|
75
|
+
async close() {
|
|
76
|
+
try {
|
|
77
|
+
await this.pool.end();
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
this.logger.warn(`Failed to close PostgreSQL pool: ${(0, common_1.errorMessage)(err)}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async tryRun(fn) {
|
|
84
|
+
try {
|
|
85
|
+
return { success: true, data: await fn() };
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
const msg = (0, common_1.errorMessage)(err);
|
|
89
|
+
this.logger.error(msg);
|
|
90
|
+
return { success: false, error: msg };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async transaction(fn) {
|
|
94
|
+
if (this.isReadOnly)
|
|
95
|
+
return this.readonlyBlocked();
|
|
96
|
+
const client = await this.pool.connect();
|
|
97
|
+
try {
|
|
98
|
+
await client.query('BEGIN');
|
|
99
|
+
const res = await fn(client);
|
|
100
|
+
await client.query('COMMIT');
|
|
101
|
+
return { success: true, data: res };
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
try {
|
|
105
|
+
await client.query('ROLLBACK');
|
|
106
|
+
}
|
|
107
|
+
catch { }
|
|
108
|
+
const msg = (0, common_1.errorMessage)(err);
|
|
109
|
+
this.logger.error(msg);
|
|
110
|
+
return { success: false, error: msg };
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
client.release();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async initSchema() {
|
|
117
|
+
await this.pool.query(`
|
|
118
|
+
CREATE TABLE IF NOT EXISTS ${(0, generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} (
|
|
119
|
+
id SERIAL PRIMARY KEY,
|
|
120
|
+
name VARCHAR(255) NOT NULL UNIQUE,
|
|
121
|
+
sql TEXT NOT NULL,
|
|
122
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
123
|
+
);
|
|
124
|
+
`);
|
|
125
|
+
}
|
|
126
|
+
hasMultipleStatements(sql) {
|
|
127
|
+
const s = String(sql ?? '').trim();
|
|
128
|
+
if (!s)
|
|
129
|
+
return false;
|
|
130
|
+
let count = 0;
|
|
131
|
+
let inString = false;
|
|
132
|
+
for (let i = 0; i < s.length; i++) {
|
|
133
|
+
const ch = s[i];
|
|
134
|
+
if (ch === "'") {
|
|
135
|
+
if (inString && s[i + 1] === "'") {
|
|
136
|
+
i++;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
inString = !inString;
|
|
140
|
+
}
|
|
141
|
+
else if (ch === ';' && !inString && s.slice(i + 1).trim().length > 0) {
|
|
142
|
+
count++;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return count > 0;
|
|
146
|
+
}
|
|
147
|
+
async all(sql, params = []) {
|
|
148
|
+
return this.tryRun(async () => {
|
|
149
|
+
const res = await this.pool.query((0, filters_1.convertPlaceholdersToPostgres)(sql), params);
|
|
150
|
+
return res.rows;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
async run(sql, params = []) {
|
|
154
|
+
if (this.isReadOnly)
|
|
155
|
+
return this.readonlyBlocked();
|
|
156
|
+
return this.tryRun(async () => {
|
|
157
|
+
const res = await this.pool.query((0, filters_1.convertPlaceholdersToPostgres)(sql), params);
|
|
158
|
+
return { changes: res.rowCount ?? 0, lastInsertRowid: null };
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
async execResult(sql) {
|
|
162
|
+
if (this.isReadOnly)
|
|
163
|
+
return this.readonlyBlocked();
|
|
164
|
+
return this.tryRun(async () => {
|
|
165
|
+
const res = await this.pool.query(sql);
|
|
166
|
+
const changes = Array.isArray(res)
|
|
167
|
+
? res.reduce((acc, r) => acc + (r.rowCount ?? 0), 0)
|
|
168
|
+
: res.rowCount ?? undefined;
|
|
169
|
+
return { changes };
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
async runWrite(sql) {
|
|
173
|
+
return this.execResult(sql);
|
|
174
|
+
}
|
|
175
|
+
async listTables() {
|
|
176
|
+
return this.tryRun(async () => {
|
|
177
|
+
const res = await this.pool.query(`SELECT table_name AS name
|
|
178
|
+
FROM information_schema.tables
|
|
179
|
+
WHERE table_schema = $1 AND table_type = 'BASE TABLE'
|
|
180
|
+
ORDER BY table_name;`, [this.schema]);
|
|
181
|
+
return res.rows.map((r) => ({ name: r.name }));
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
async getTableInfo(table) {
|
|
185
|
+
return this.tryRun(async () => {
|
|
186
|
+
const [colRes, pkRes, fkRes, idxRes] = await Promise.all([
|
|
187
|
+
this.pool.query(`SELECT
|
|
188
|
+
ordinal_position AS cid,
|
|
189
|
+
column_name AS name,
|
|
190
|
+
data_type AS type,
|
|
191
|
+
udt_name,
|
|
192
|
+
CASE WHEN is_nullable = 'NO' THEN 1 ELSE 0 END AS notnull,
|
|
193
|
+
column_default AS dflt_value
|
|
194
|
+
FROM information_schema.columns
|
|
195
|
+
WHERE table_schema = $1 AND table_name = $2
|
|
196
|
+
ORDER BY ordinal_position;`, [this.schema, table]),
|
|
197
|
+
this.pool.query(`SELECT kcu.column_name
|
|
198
|
+
FROM information_schema.table_constraints tc
|
|
199
|
+
JOIN information_schema.key_column_usage kcu
|
|
200
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
201
|
+
AND tc.table_schema = kcu.table_schema
|
|
202
|
+
WHERE tc.constraint_type = 'PRIMARY KEY'
|
|
203
|
+
AND tc.table_schema = $1
|
|
204
|
+
AND tc.table_name = $2
|
|
205
|
+
ORDER BY kcu.ordinal_position;`, [this.schema, table]),
|
|
206
|
+
this.pool.query(`SELECT
|
|
207
|
+
row_number() OVER () AS id,
|
|
208
|
+
kcu.position_in_unique_constraint AS seq,
|
|
209
|
+
ccu.table_name AS table,
|
|
210
|
+
kcu.column_name AS "from",
|
|
211
|
+
ccu.column_name AS "to",
|
|
212
|
+
rc.update_rule AS on_update,
|
|
213
|
+
rc.delete_rule AS on_delete
|
|
214
|
+
FROM information_schema.table_constraints AS tc
|
|
215
|
+
JOIN information_schema.key_column_usage AS kcu
|
|
216
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
217
|
+
AND tc.table_schema = kcu.table_schema
|
|
218
|
+
JOIN information_schema.constraint_column_usage AS ccu
|
|
219
|
+
ON ccu.constraint_name = tc.constraint_name
|
|
220
|
+
AND ccu.table_schema = tc.table_schema
|
|
221
|
+
JOIN information_schema.referential_constraints AS rc
|
|
222
|
+
ON rc.constraint_name = tc.constraint_name
|
|
223
|
+
AND rc.constraint_schema = tc.table_schema
|
|
224
|
+
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
225
|
+
AND tc.table_schema = $1
|
|
226
|
+
AND tc.table_name = $2;`, [this.schema, table]),
|
|
227
|
+
this.pool.query(`SELECT
|
|
228
|
+
i.relname AS name,
|
|
229
|
+
ix.indisunique AS unique,
|
|
230
|
+
pg_get_indexdef(ix.indexrelid) AS sql,
|
|
231
|
+
ARRAY(
|
|
232
|
+
SELECT pg_get_indexdef(ix.indexrelid, k + 1, true)
|
|
233
|
+
FROM generate_subscripts(ix.indkey, 1) as k
|
|
234
|
+
ORDER BY k
|
|
235
|
+
) AS columns
|
|
236
|
+
FROM pg_index ix
|
|
237
|
+
JOIN pg_class t ON t.oid = ix.indrelid
|
|
238
|
+
JOIN pg_class i ON i.oid = ix.indexrelid
|
|
239
|
+
JOIN pg_namespace n ON n.oid = t.relnamespace
|
|
240
|
+
WHERE n.nspname = $1 AND t.relname = $2;`, [this.schema, table]),
|
|
241
|
+
]);
|
|
242
|
+
const primaryKey = pkRes.rows.map((r) => r.column_name);
|
|
243
|
+
const columns = colRes.rows.map((c) => {
|
|
244
|
+
const isPk = primaryKey.includes(c.name);
|
|
245
|
+
const displayType = c.type === 'USER-DEFINED' ? c.udt_name : c.type;
|
|
246
|
+
return {
|
|
247
|
+
cid: c.cid,
|
|
248
|
+
name: c.name,
|
|
249
|
+
type: displayType.toUpperCase(),
|
|
250
|
+
notnull: c.notnull,
|
|
251
|
+
dflt_value: c.dflt_value,
|
|
252
|
+
pk: isPk ? primaryKey.indexOf(c.name) + 1 : 0,
|
|
253
|
+
};
|
|
254
|
+
});
|
|
255
|
+
const indexes = idxRes.rows.map((ix) => ({
|
|
256
|
+
name: ix.name,
|
|
257
|
+
unique: Boolean(ix.unique),
|
|
258
|
+
partial: 0,
|
|
259
|
+
origin: 'c',
|
|
260
|
+
columns: Array.isArray(ix.columns) ? ix.columns : [],
|
|
261
|
+
sql: ix.sql,
|
|
262
|
+
}));
|
|
263
|
+
const colDefs = columns.map((c) => {
|
|
264
|
+
let def = ` ${(0, generator_1.quoteIdentifier)(c.name)} ${c.type}`;
|
|
265
|
+
if (c.notnull)
|
|
266
|
+
def += ' NOT NULL';
|
|
267
|
+
if (c.dflt_value)
|
|
268
|
+
def += ` DEFAULT ${c.dflt_value}`;
|
|
269
|
+
return def;
|
|
270
|
+
});
|
|
271
|
+
if (primaryKey.length) {
|
|
272
|
+
colDefs.push(` PRIMARY KEY (${primaryKey.map(generator_1.quoteIdentifier).join(', ')})`);
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
table,
|
|
276
|
+
columns,
|
|
277
|
+
foreignKeys: fkRes.rows.map((f) => ({
|
|
278
|
+
id: Number(f.id),
|
|
279
|
+
seq: Number(f.seq ?? 1),
|
|
280
|
+
table: f.table,
|
|
281
|
+
from: f.from,
|
|
282
|
+
to: f.to,
|
|
283
|
+
on_update: f.on_update,
|
|
284
|
+
on_delete: f.on_delete,
|
|
285
|
+
})),
|
|
286
|
+
primaryKey,
|
|
287
|
+
indexes,
|
|
288
|
+
sql: `CREATE TABLE ${(0, generator_1.quoteIdentifier)(table)} (\n${colDefs.join(',\n')}\n);`,
|
|
289
|
+
};
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
async getRowCount(table, filters) {
|
|
293
|
+
return this.tryRun(async () => {
|
|
294
|
+
let pgWhere = '';
|
|
295
|
+
let params = [];
|
|
296
|
+
if (filters && Object.keys(filters).length > 0) {
|
|
297
|
+
const { where, params: p } = (0, filters_1.buildFilterClause)(filters, Object.keys(filters));
|
|
298
|
+
pgWhere = (0, filters_1.convertPlaceholdersToPostgres)(where);
|
|
299
|
+
params = p;
|
|
300
|
+
}
|
|
301
|
+
const res = await this.pool.query(`SELECT COUNT(*) AS c FROM ${(0, generator_1.quoteIdentifier)(table)}${pgWhere}`, params);
|
|
302
|
+
return Number(res.rows[0]?.c ?? 0);
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
async getRows(table, opts = {}) {
|
|
306
|
+
return this.tryRun(async () => {
|
|
307
|
+
const orderCol = opts.orderBy;
|
|
308
|
+
const orderDir = opts.orderDir === 'desc' ? 'DESC' : 'ASC';
|
|
309
|
+
const limit = opts.limit && opts.limit > 0 ? opts.limit : 200;
|
|
310
|
+
const page = opts.page && opts.page > 0 ? opts.page : 1;
|
|
311
|
+
const offset = (page - 1) * limit;
|
|
312
|
+
let pgWhere = '';
|
|
313
|
+
let filterParams = [];
|
|
314
|
+
if (opts.filters && Object.keys(opts.filters).length > 0) {
|
|
315
|
+
const { where, params } = (0, filters_1.buildFilterClause)(opts.filters, Object.keys(opts.filters));
|
|
316
|
+
pgWhere = (0, filters_1.convertPlaceholdersToPostgres)(where);
|
|
317
|
+
filterParams = params;
|
|
318
|
+
}
|
|
319
|
+
const nextParamIdx = filterParams.length + 1;
|
|
320
|
+
let sql = `SELECT * FROM ${(0, generator_1.quoteIdentifier)(table)}${pgWhere}`;
|
|
321
|
+
if (orderCol)
|
|
322
|
+
sql += ` ORDER BY ${(0, generator_1.quoteIdentifier)(orderCol)} ${orderDir}`;
|
|
323
|
+
sql += ` LIMIT $${nextParamIdx} OFFSET $${nextParamIdx + 1}`;
|
|
324
|
+
const res = await this.pool.query(sql, [...filterParams, limit, offset]);
|
|
325
|
+
return res.rows;
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
async getAllRows(table) {
|
|
329
|
+
return this.tryRun(async () => {
|
|
330
|
+
const res = await this.pool.query(`SELECT * FROM ${(0, generator_1.quoteIdentifier)(table)}`);
|
|
331
|
+
return res.rows;
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
async getRow(table, where) {
|
|
335
|
+
return this.tryRun(async () => {
|
|
336
|
+
const conds = where.map((w, idx) => `${(0, generator_1.quoteIdentifier)(w.column)} = $${idx + 1}`);
|
|
337
|
+
const sql = `SELECT * FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${conds.join(' AND ')} LIMIT 1`;
|
|
338
|
+
const res = await this.pool.query(sql, where.map((w) => w.value));
|
|
339
|
+
return res.rows[0] ?? null;
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
async insertRow(table, fields) {
|
|
343
|
+
if (this.isReadOnly)
|
|
344
|
+
return this.readonlyBlocked();
|
|
345
|
+
return this.tryRun(async () => {
|
|
346
|
+
const cols = fields.map((f) => (0, generator_1.quoteIdentifier)(f.column));
|
|
347
|
+
const placeholders = fields.map((_, idx) => `$${idx + 1}`);
|
|
348
|
+
const sql = `INSERT INTO ${(0, generator_1.quoteIdentifier)(table)} (${cols.join(', ')}) VALUES (${placeholders.join(', ')}) RETURNING *`;
|
|
349
|
+
const res = await this.pool.query(sql, fields.map((f) => f.value));
|
|
350
|
+
return { changes: res.rowCount ?? 1, lastInsertRowid: null };
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
async insertRows(table, rows) {
|
|
354
|
+
return this.transaction(async (client) => {
|
|
355
|
+
let inserted = 0;
|
|
356
|
+
for (const fields of rows) {
|
|
357
|
+
if (!fields.length)
|
|
358
|
+
continue;
|
|
359
|
+
const cols = fields.map((f) => (0, generator_1.quoteIdentifier)(f.column));
|
|
360
|
+
const placeholders = fields.map((_, idx) => `$${idx + 1}`);
|
|
361
|
+
const sql = `INSERT INTO ${(0, generator_1.quoteIdentifier)(table)} (${cols.join(', ')}) VALUES (${placeholders.join(', ')})`;
|
|
362
|
+
await client.query(sql, fields.map((f) => f.value));
|
|
363
|
+
inserted++;
|
|
364
|
+
}
|
|
365
|
+
return { inserted, skipped: rows.length - inserted };
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
async updateRow(table, fields, where) {
|
|
369
|
+
if (this.isReadOnly)
|
|
370
|
+
return this.readonlyBlocked();
|
|
371
|
+
return this.tryRun(async () => {
|
|
372
|
+
const sets = fields.map((f, idx) => `${(0, generator_1.quoteIdentifier)(f.column)} = $${idx + 1}`);
|
|
373
|
+
const conds = where.map((w, idx) => `${(0, generator_1.quoteIdentifier)(w.column)} = $${fields.length + idx + 1}`);
|
|
374
|
+
const sql = `UPDATE ${(0, generator_1.quoteIdentifier)(table)} SET ${sets.join(', ')} WHERE ${conds.join(' AND ')}`;
|
|
375
|
+
const res = await this.pool.query(sql, [...fields.map((f) => f.value), ...where.map((w) => w.value)]);
|
|
376
|
+
return { changes: res.rowCount ?? 0, lastInsertRowid: null };
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
async updateRows(table, rows) {
|
|
380
|
+
return this.transaction(async (client) => {
|
|
381
|
+
let updated = 0;
|
|
382
|
+
for (const { fields, where } of rows) {
|
|
383
|
+
if (!fields.length || !where.length)
|
|
384
|
+
continue;
|
|
385
|
+
const sets = fields.map((f, idx) => `${(0, generator_1.quoteIdentifier)(f.column)} = $${idx + 1}`);
|
|
386
|
+
const conds = where.map((w, idx) => `${(0, generator_1.quoteIdentifier)(w.column)} = $${fields.length + idx + 1}`);
|
|
387
|
+
const sql = `UPDATE ${(0, generator_1.quoteIdentifier)(table)} SET ${sets.join(', ')} WHERE ${conds.join(' AND ')}`;
|
|
388
|
+
const res = await client.query(sql, [...fields.map((f) => f.value), ...where.map((w) => w.value)]);
|
|
389
|
+
updated += res.rowCount ?? 0;
|
|
390
|
+
}
|
|
391
|
+
return { updated };
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
async deleteRow(table, where) {
|
|
395
|
+
if (this.isReadOnly)
|
|
396
|
+
return this.readonlyBlocked();
|
|
397
|
+
return this.tryRun(async () => {
|
|
398
|
+
const conds = where.map((w, idx) => `${(0, generator_1.quoteIdentifier)(w.column)} = $${idx + 1}`);
|
|
399
|
+
const sql = `DELETE FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${conds.join(' AND ')}`;
|
|
400
|
+
const res = await this.pool.query(sql, where.map((w) => w.value));
|
|
401
|
+
return { changes: res.rowCount ?? 0, lastInsertRowid: null };
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
async deleteRows(table, rows) {
|
|
405
|
+
return this.transaction(async (client) => {
|
|
406
|
+
let deleted = 0;
|
|
407
|
+
for (const where of rows) {
|
|
408
|
+
if (!where.length)
|
|
409
|
+
continue;
|
|
410
|
+
const conds = where.map((w, idx) => `${(0, generator_1.quoteIdentifier)(w.column)} = $${idx + 1}`);
|
|
411
|
+
const sql = `DELETE FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${conds.join(' AND ')}`;
|
|
412
|
+
const res = await client.query(sql, where.map((w) => w.value));
|
|
413
|
+
deleted += res.rowCount ?? 0;
|
|
414
|
+
}
|
|
415
|
+
return { deleted };
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
async getRowsByPks(table, pks) {
|
|
419
|
+
return this.tryRun(async () => {
|
|
420
|
+
const groups = [];
|
|
421
|
+
const params = [];
|
|
422
|
+
let paramIdx = 1;
|
|
423
|
+
for (const where of pks) {
|
|
424
|
+
if (!where.length)
|
|
425
|
+
continue;
|
|
426
|
+
const conds = where.map((w) => {
|
|
427
|
+
params.push(w.value);
|
|
428
|
+
return `${(0, generator_1.quoteIdentifier)(w.column)} = $${paramIdx++}`;
|
|
429
|
+
});
|
|
430
|
+
groups.push(conds.join(' AND '));
|
|
431
|
+
}
|
|
432
|
+
if (!groups.length)
|
|
433
|
+
return [];
|
|
434
|
+
const sql = `SELECT * FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${groups.map((g) => `(${g})`).join(' OR ')}`;
|
|
435
|
+
const res = await this.pool.query(sql, params);
|
|
436
|
+
return res.rows;
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
async getRowsByFk(table, column, value, limit = 50) {
|
|
440
|
+
return this.tryRun(async () => {
|
|
441
|
+
const n = Math.max(1, Math.min(500, Number(limit) || 50));
|
|
442
|
+
const countRes = await this.pool.query(`SELECT COUNT(*) AS c FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${(0, generator_1.quoteIdentifier)(column)} = $1`, [value]);
|
|
443
|
+
const rowsRes = await this.pool.query(`SELECT * FROM ${(0, generator_1.quoteIdentifier)(table)} WHERE ${(0, generator_1.quoteIdentifier)(column)} = $1 LIMIT $2`, [value, n]);
|
|
444
|
+
return { rows: rowsRes.rows, total: Number(countRes.rows[0]?.c ?? 0) };
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
async getCreateStatement(table) {
|
|
448
|
+
const info = await this.getTableInfo(table);
|
|
449
|
+
if (!info.success || !info.data)
|
|
450
|
+
return { success: false, error: info.error };
|
|
451
|
+
return { success: true, data: info.data.sql };
|
|
452
|
+
}
|
|
453
|
+
async listSavedQueries() {
|
|
454
|
+
return this.tryRun(async () => {
|
|
455
|
+
const res = await this.pool.query(`SELECT id, name, sql, created_at FROM ${(0, generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} ORDER BY name`);
|
|
456
|
+
return res.rows;
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
async saveQuery(name, sql) {
|
|
460
|
+
return this.tryRun(async () => {
|
|
461
|
+
const res = await this.pool.query(`INSERT INTO ${(0, generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} (name, sql) VALUES ($1, $2) RETURNING id`, [name, sql]);
|
|
462
|
+
return { changes: res.rowCount ?? 1, lastInsertRowid: Number(res.rows[0]?.id ?? null) };
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
async deleteSavedQuery(id) {
|
|
466
|
+
return this.tryRun(async () => {
|
|
467
|
+
const res = await this.pool.query(`DELETE FROM ${(0, generator_1.quoteIdentifier)(types_1.INTERNAL_TABLES.savedQueries)} WHERE id = $1`, [Number(id)]);
|
|
468
|
+
return { changes: res.rowCount ?? 0, lastInsertRowid: null };
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
async getSettings() {
|
|
472
|
+
return this.tryRun(async () => {
|
|
473
|
+
const keys = ['server_version', 'max_connections', 'port', 'timezone', 'shared_buffers', 'work_mem'];
|
|
474
|
+
const settings = {};
|
|
475
|
+
try {
|
|
476
|
+
const res = await this.pool.query(`SELECT name, setting FROM pg_settings WHERE name = ANY($1::text[])`, [keys]);
|
|
477
|
+
for (const row of res.rows)
|
|
478
|
+
settings[row.name] = String(row.setting);
|
|
479
|
+
}
|
|
480
|
+
catch {
|
|
481
|
+
try {
|
|
482
|
+
const ver = await this.pool.query('SHOW server_version');
|
|
483
|
+
settings['server_version'] = String(ver.rows[0]?.server_version);
|
|
484
|
+
}
|
|
485
|
+
catch { }
|
|
486
|
+
}
|
|
487
|
+
return settings;
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
async getSchema(table) {
|
|
491
|
+
return this.tryRun(async () => {
|
|
492
|
+
const infoR = await this.getTableInfo(table);
|
|
493
|
+
if (!infoR.success || !infoR.data)
|
|
494
|
+
throw new Error(infoR.error || `Table ${table} not found.`);
|
|
495
|
+
const { columns, foreignKeys, indexes, primaryKey } = infoR.data;
|
|
496
|
+
const refRes = await this.pool.query(`SELECT
|
|
497
|
+
tc.table_name AS table,
|
|
498
|
+
kcu.column_name AS "from",
|
|
499
|
+
ccu.column_name AS "to"
|
|
500
|
+
FROM information_schema.table_constraints AS tc
|
|
501
|
+
JOIN information_schema.key_column_usage AS kcu
|
|
502
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
503
|
+
AND tc.table_schema = kcu.table_schema
|
|
504
|
+
JOIN information_schema.constraint_column_usage AS ccu
|
|
505
|
+
ON ccu.constraint_name = tc.constraint_name
|
|
506
|
+
AND ccu.table_schema = tc.table_schema
|
|
507
|
+
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
508
|
+
AND tc.table_schema = $1
|
|
509
|
+
AND ccu.table_name = $2;`, [this.schema, table]);
|
|
510
|
+
const references = refRes.rows;
|
|
511
|
+
const columnDetails = columns.map((col) => {
|
|
512
|
+
const isPk = primaryKey.includes(col.name);
|
|
513
|
+
const fk = foreignKeys.find((f) => f.from === col.name) ?? null;
|
|
514
|
+
const matchingIndexes = indexes.filter((i) => i.columns.includes(col.name));
|
|
515
|
+
const unique = matchingIndexes.some((i) => i.unique && i.columns.length === 1 && i.columns[0] === col.name);
|
|
516
|
+
const indexed = matchingIndexes.length > 0;
|
|
517
|
+
const referencedBy = references
|
|
518
|
+
.filter((r) => r.to === col.name)
|
|
519
|
+
.map((r) => ({ table: r.table, from: r.from }));
|
|
520
|
+
const dropBlockers = [];
|
|
521
|
+
if (isPk && primaryKey.length === 1)
|
|
522
|
+
dropBlockers.push('Primary key');
|
|
523
|
+
if (referencedBy.length > 0) {
|
|
524
|
+
dropBlockers.push(`Referenced by ${referencedBy.map((r) => `${r.table}.${r.from}`).join(', ')}`);
|
|
525
|
+
}
|
|
526
|
+
return {
|
|
527
|
+
...col,
|
|
528
|
+
unique,
|
|
529
|
+
indexed,
|
|
530
|
+
referencedBy,
|
|
531
|
+
fk,
|
|
532
|
+
canDrop: dropBlockers.length === 0,
|
|
533
|
+
dropBlockers,
|
|
534
|
+
};
|
|
535
|
+
});
|
|
536
|
+
return {
|
|
537
|
+
table,
|
|
538
|
+
columns: columnDetails,
|
|
539
|
+
foreignKeys,
|
|
540
|
+
indexes,
|
|
541
|
+
references,
|
|
542
|
+
};
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
async getReferencingTables(table) {
|
|
546
|
+
return this.tryRun(async () => {
|
|
547
|
+
const refRes = await this.pool.query(`SELECT
|
|
548
|
+
tc.table_name AS table,
|
|
549
|
+
kcu.column_name AS "from",
|
|
550
|
+
ccu.column_name AS "to"
|
|
551
|
+
FROM information_schema.table_constraints AS tc
|
|
552
|
+
JOIN information_schema.key_column_usage AS kcu
|
|
553
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
554
|
+
AND tc.table_schema = kcu.table_schema
|
|
555
|
+
JOIN information_schema.constraint_column_usage AS ccu
|
|
556
|
+
ON ccu.constraint_name = tc.constraint_name
|
|
557
|
+
AND ccu.table_schema = tc.table_schema
|
|
558
|
+
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
559
|
+
AND tc.table_schema = $1
|
|
560
|
+
AND ccu.table_name = $2;`, [this.schema, table]);
|
|
561
|
+
const byTable = new Map();
|
|
562
|
+
for (const r of refRes.rows) {
|
|
563
|
+
if (!byTable.has(r.table))
|
|
564
|
+
byTable.set(r.table, []);
|
|
565
|
+
byTable.get(r.table).push({ from: r.from, to: r.to });
|
|
566
|
+
}
|
|
567
|
+
const results = [];
|
|
568
|
+
for (const [tbl, refs] of byTable.entries()) {
|
|
569
|
+
try {
|
|
570
|
+
const countRes = await this.pool.query(`SELECT COUNT(*) AS c FROM ${(0, generator_1.quoteIdentifier)(tbl)} WHERE ${refs.map((r) => `${(0, generator_1.quoteIdentifier)(r.from)} IS NOT NULL`).join(' OR ')}`);
|
|
571
|
+
results.push({ table: tbl, refs, refCount: Number(countRes.rows[0]?.c ?? 0) });
|
|
572
|
+
}
|
|
573
|
+
catch {
|
|
574
|
+
results.push({ table: tbl, refs, refCount: 0 });
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return results;
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
async renameTable(oldName, newName) {
|
|
581
|
+
if (this.isReadOnly)
|
|
582
|
+
return this.readonlyBlocked();
|
|
583
|
+
return this.tryRun(async () => {
|
|
584
|
+
await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(oldName)} RENAME TO ${(0, generator_1.quoteIdentifier)(newName)};`);
|
|
585
|
+
return { changes: 1 };
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
async addColumn(table, col) {
|
|
589
|
+
if (this.isReadOnly)
|
|
590
|
+
return this.readonlyBlocked();
|
|
591
|
+
return this.tryRun(async () => {
|
|
592
|
+
let sql = `ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} ADD COLUMN ${(0, generator_1.quoteIdentifier)(col.name)} ${col.type}`;
|
|
593
|
+
if (col.notNull)
|
|
594
|
+
sql += ' NOT NULL';
|
|
595
|
+
if (col.defaultValue !== undefined && col.defaultValue !== null && col.defaultValue !== '') {
|
|
596
|
+
sql += ` DEFAULT ${col.defaultValue}`;
|
|
597
|
+
}
|
|
598
|
+
if (col.foreignKey) {
|
|
599
|
+
sql += ` REFERENCES ${(0, generator_1.quoteIdentifier)(col.foreignKey.table)} (${(0, generator_1.quoteIdentifier)(col.foreignKey.column)})`;
|
|
600
|
+
}
|
|
601
|
+
await this.pool.query(sql);
|
|
602
|
+
if (col.unique) {
|
|
603
|
+
const idxName = `idx_${table}_${col.name}_unique`;
|
|
604
|
+
await this.pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS ${(0, generator_1.quoteIdentifier)(idxName)} ON ${(0, generator_1.quoteIdentifier)(table)} (${(0, generator_1.quoteIdentifier)(col.name)});`);
|
|
605
|
+
}
|
|
606
|
+
return { changes: 1 };
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
async modifyColumn(table, oldColName, newColDef) {
|
|
610
|
+
if (this.isReadOnly)
|
|
611
|
+
return this.readonlyBlocked();
|
|
612
|
+
return this.tryRun(async () => {
|
|
613
|
+
const newName = String(newColDef.name || oldColName).trim();
|
|
614
|
+
if (oldColName !== newName) {
|
|
615
|
+
await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} RENAME COLUMN ${(0, generator_1.quoteIdentifier)(oldColName)} TO ${(0, generator_1.quoteIdentifier)(newName)};`);
|
|
616
|
+
}
|
|
617
|
+
const colToModify = newName;
|
|
618
|
+
if (newColDef.type) {
|
|
619
|
+
await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0, generator_1.quoteIdentifier)(colToModify)} TYPE ${newColDef.type} USING ${(0, generator_1.quoteIdentifier)(colToModify)}::${newColDef.type};`);
|
|
620
|
+
}
|
|
621
|
+
if (newColDef.notNull !== undefined) {
|
|
622
|
+
const action = newColDef.notNull ? 'SET NOT NULL' : 'DROP NOT NULL';
|
|
623
|
+
await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0, generator_1.quoteIdentifier)(colToModify)} ${action};`);
|
|
624
|
+
}
|
|
625
|
+
if (newColDef.defaultValue !== undefined) {
|
|
626
|
+
const action = newColDef.defaultValue === null || newColDef.defaultValue === ''
|
|
627
|
+
? 'DROP DEFAULT'
|
|
628
|
+
: `SET DEFAULT ${newColDef.defaultValue}`;
|
|
629
|
+
await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} ALTER COLUMN ${(0, generator_1.quoteIdentifier)(colToModify)} ${action};`);
|
|
630
|
+
}
|
|
631
|
+
if (newColDef.unique !== undefined) {
|
|
632
|
+
const idxName = `idx_${table}_${colToModify}_unique`;
|
|
633
|
+
if (newColDef.unique) {
|
|
634
|
+
await this.pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS ${(0, generator_1.quoteIdentifier)(idxName)} ON ${(0, generator_1.quoteIdentifier)(table)} (${(0, generator_1.quoteIdentifier)(colToModify)});`);
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
await this.pool.query(`DROP INDEX IF EXISTS ${(0, generator_1.quoteIdentifier)(idxName)};`);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return { changes: 1 };
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
async renameColumn(table, oldName, newName) {
|
|
644
|
+
if (this.isReadOnly)
|
|
645
|
+
return this.readonlyBlocked();
|
|
646
|
+
return this.tryRun(async () => {
|
|
647
|
+
await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} RENAME COLUMN ${(0, generator_1.quoteIdentifier)(oldName)} TO ${(0, generator_1.quoteIdentifier)(newName)};`);
|
|
648
|
+
return { changes: 1 };
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
async dropColumn(table, column) {
|
|
652
|
+
if (this.isReadOnly)
|
|
653
|
+
return this.readonlyBlocked();
|
|
654
|
+
return this.tryRun(async () => {
|
|
655
|
+
await this.pool.query(`ALTER TABLE ${(0, generator_1.quoteIdentifier)(table)} DROP COLUMN ${(0, generator_1.quoteIdentifier)(column)} CASCADE;`);
|
|
656
|
+
return { changes: 1 };
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
async dropTable(table) {
|
|
660
|
+
if (this.isReadOnly)
|
|
661
|
+
return this.readonlyBlocked();
|
|
662
|
+
return this.tryRun(async () => {
|
|
663
|
+
await this.pool.query(`DROP TABLE ${(0, generator_1.quoteIdentifier)(table)} CASCADE;`);
|
|
664
|
+
return { changes: 1 };
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
async createIndex(table, index) {
|
|
668
|
+
if (this.isReadOnly)
|
|
669
|
+
return this.readonlyBlocked();
|
|
670
|
+
return this.tryRun(async () => {
|
|
671
|
+
const unique = index.unique ? 'UNIQUE ' : '';
|
|
672
|
+
const cols = index.columns.map(generator_1.quoteIdentifier).join(', ');
|
|
673
|
+
const name = index.name || `idx_${table}_${index.columns.join('_')}`;
|
|
674
|
+
await this.pool.query(`CREATE ${unique}INDEX IF NOT EXISTS ${(0, generator_1.quoteIdentifier)(name)} ON ${(0, generator_1.quoteIdentifier)(table)} (${cols});`);
|
|
675
|
+
return { changes: 1 };
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
async dropIndex(indexName) {
|
|
679
|
+
if (this.isReadOnly)
|
|
680
|
+
return this.readonlyBlocked();
|
|
681
|
+
return this.tryRun(async () => {
|
|
682
|
+
await this.pool.query(`DROP INDEX IF EXISTS ${(0, generator_1.quoteIdentifier)(indexName)} CASCADE;`);
|
|
683
|
+
return { changes: 1 };
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
exports.PostgresDatabase = PostgresDatabase;
|