2import type { Knex } from 'knex' 14import * as _ from 'lodash-es' 16type DbCtx = {db: Knex} 18const staleStates = ['idle', 'idle in transaction', 'idle in transaction (aborted)', 'disabled'] 20const getSuConnInfo = async (qsName: string) => { 24export const killstaleconns = async ({qsName}: {qsName: string}) => { 25 const connInfo = await getSuConnInfo(qsName) 28 await dbConnWrapNoMap({connInfo, reuseConn: false, pgAdType: 'Knex'}, async (_db) => { 29 const db = _db as Knex 30 const {rows: conns} = await db.raw(` 31 SELECT pid, state, application_name, left(query, 80) as query 33 WHERE usename = current_user AND pid <> pg_backend_pid() 34 ORDER BY query_start desc 37 console.log(`Found ${conns.length} connections from current user`) 38 const staleConns = conns.filter((c: {state: string}) => staleStates.includes(c.state)) 39 const activeConns = conns.filter((c: {state: string}) => !staleStates.includes(c.state)) 42 if (conns.length === 0) { console.log('No connections to kill'); return } 43 console.log(`FORCE: terminating ALL ${conns.length} connections (stale + active)`) 44 for (const c of conns) { await db.raw(`SELECT pg_terminate_backend(${c.pid})`) } 45 console.log(`Terminated ${conns.length} connections`) 49 if (activeConns.length > 0) { 50 console.log(`Skipping ${activeConns.length} active connections:`, activeConns.map((c: {pid: number, state: string, query: string}) => ({pid: c.pid, state: c.state, query: c.query}))) 53 if (staleConns.length === 0) { console.log('No stale connections to kill'); return } 55 console.log(`Terminating ${staleConns.length} stale connections:`, staleConns.map((c: {pid: number, state: string}) => ({pid: c.pid, state: c.state}))) 56 for (const c of staleConns) { await db.raw(`SELECT pg_terminate_backend(${c.pid})`) } 57 console.log(`Terminated ${staleConns.length} stale connections`) 60killstaleconns.cliDescript = 'terminate idle/stuck connections from this user (--force: kill all including active; connects as superuser)' 61killstaleconns.cliSchema = { force: { flag: true, desc: 'also terminate active connections' } } 62killstaleconns.noWrapDbConn = true 64export const connsall = async ({qsName}: {qsName: string}) => { 65 const connInfo = await getSuConnInfo(qsName) 66 await dbConnWrapNoMap({connInfo, reuseConn: false, pgAdType: 'Knex'}, async (_db) => { 67 const db = _db as Knex 68 const {rows} = await db.raw(` 69 SELECT pid, state, usename, application_name, left(query, 60) as query 71 WHERE pid <> pg_backend_pid() 72 ORDER BY query_start desc 77connsall.cliDescript = 'show all connections (superuser)' 78connsall.noWrapDbConn = true 80export const migratestatus = async ({db}: DbCtx) => { 81 const {migrationOpts} = deployDbCtx.getStore() ?? {} 82 const [lastAppliedMigrations, unappliedMigrations] = await db.migrate.list(migrationOpts) 83 const last5AppliedMigrations = _.takeRight(lastAppliedMigrations, 5) 84 console.log({last5AppliedMigrations, totalApplied: lastAppliedMigrations.length, unappliedMigrations}) 86migratestatus.cliDescript = 'show pending/applied migrations' 88export const rollback = async ({db}: DbCtx) => { 89 const {migrationOpts} = deployDbCtx.getStore() ?? {} 90 const ret = await db.migrate.down(migrationOpts) 91 const [idk, migrationNames] = ret 92 console.log('rolled back', migrationNames) 95export const migrate = async ({db}: DbCtx) => { 96 const {migrationOpts, qsName, kyselyOutFile} = deployDbCtx.getStore() ?? {} 98 const migDirs = _.castArray((migrationOpts as any).directory).map((d: string) => d.replace(ptDir + '/', '')) 100 .filter(p => migDirs.some(d => p === d || p.startsWith(d + '/'))) 103 `uncommitted migration files - commit before migrating:\n ${dirtyMigs.join('\n ')}\n(override: --confirmedByUser=<secret>)`, 110 disableTransactions: false 112 const [batchNo, log] = await db.migrate.latest(cfg) 114 console.info('Database is already up to date'); 116 console.info('Ran migrations: ' + log.join(', ')); 119 if (kyselyOutFile && qsName) { 123 {commitMsg: `pt kysely-regen: ${qsName}`, relevantFilesA: [kyselyOutFile], skipViolationCheck: true} 126 console.warn(`kysely regen failed (migrations already applied): ${err}`) 130migrate.cliDescript = 'run pending migrations' 131migrate.cliSchema = { confirmedByUser: { type: 'string', hidden: true, desc: 'override dirty-migration guard' } }