@@ -30,7 +30,7 @@ export class DatabaseInspector {
3030
3131 // Get table information
3232 const tablesQuery = `
33- SELECT
33+ SELECT
3434 schemaname,
3535 tablename,
3636 ${ includeSizes ? "pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as total_size," : '' }
@@ -49,35 +49,23 @@ export class DatabaseInspector {
4949
5050 if ( tablesError ) throw tablesError ;
5151
52- // Process each table
53- for ( const table of tables || [ ] ) {
54- const tableInfo : any = {
55- name : table . tablename ,
56- schema : table . schemaname ,
57- } ;
52+ // Process all tables in parallel — count/columns/indexes per table
53+ // are independent of each other so fire them all at once.
54+ shape . tables = await Promise . all (
55+ ( tables || [ ] ) . map ( async ( table : Record < string , any > ) => {
56+ const tableInfo : any = {
57+ name : table . tablename ,
58+ schema : table . schemaname ,
59+ } ;
5860
59- if ( includeSizes ) {
60- tableInfo . total_size = table . total_size ;
61- tableInfo . table_size = table . table_size ;
62- tableInfo . indexes_size = table . indexes_size ;
63- }
64-
65- // Get row count
66- if ( includeCounts ) {
67- const { data : countData , error : countError } = await this . supabase . rpc ( 'exec_sql' , {
68- q : `SELECT COUNT(*) as count FROM ${ table . tablename } ` ,
69- params : { }
70- } ) ;
71-
72- if ( ! countError && countData && countData . length > 0 ) {
73- tableInfo . row_count = parseInt ( countData [ 0 ] . count ) ;
61+ if ( includeSizes ) {
62+ tableInfo . total_size = table . total_size ;
63+ tableInfo . table_size = table . table_size ;
64+ tableInfo . indexes_size = table . indexes_size ;
7465 }
75- }
7666
77- // Get column information
78- if ( includeColumns ) {
7967 const columnsQuery = `
80- SELECT
68+ SELECT
8169 column_name,
8270 data_type,
8371 is_nullable,
@@ -88,20 +76,8 @@ export class DatabaseInspector {
8876 ORDER BY ordinal_position
8977 ` ;
9078
91- const { data : columns , error : columnsError } = await this . supabase . rpc ( 'exec_sql' , {
92- q : columnsQuery ,
93- params : { }
94- } ) ;
95-
96- if ( ! columnsError ) {
97- tableInfo . columns = columns ;
98- }
99- }
100-
101- // Get indexes
102- if ( includeIndexes ) {
10379 const indexesQuery = `
104- SELECT
80+ SELECT
10581 i.indexname,
10682 i.indexdef,
10783 ${ includeSizes ? "pg_size_pretty(pg_relation_size(i.schemaname||'.'||i.indexname)) as index_size," : '' }
@@ -113,18 +89,32 @@ export class DatabaseInspector {
11389 ORDER BY i.indexname
11490 ` ;
11591
116- const { data : indexes , error : indexesError } = await this . supabase . rpc ( 'exec_sql' , {
117- q : indexesQuery ,
118- params : { }
119- } ) ;
120-
121- if ( ! indexesError ) {
122- tableInfo . indexes = indexes ;
92+ // Fire count, columns, indexes concurrently for this table
93+ const [ countResult , columnsResult , indexesResult ] = await Promise . all ( [
94+ includeCounts
95+ ? this . supabase . rpc ( 'exec_sql' , { q : `SELECT COUNT(*) as count FROM ${ table . tablename } ` , params : { } } )
96+ : Promise . resolve ( { data : null , error : null } ) ,
97+ includeColumns
98+ ? this . supabase . rpc ( 'exec_sql' , { q : columnsQuery , params : { } } )
99+ : Promise . resolve ( { data : null , error : null } ) ,
100+ includeIndexes
101+ ? this . supabase . rpc ( 'exec_sql' , { q : indexesQuery , params : { } } )
102+ : Promise . resolve ( { data : null , error : null } ) ,
103+ ] ) ;
104+
105+ if ( ! countResult . error && countResult . data ?. length > 0 ) {
106+ tableInfo . row_count = parseInt ( countResult . data [ 0 ] . count ) ;
107+ }
108+ if ( ! columnsResult . error && columnsResult . data ) {
109+ tableInfo . columns = columnsResult . data ;
110+ }
111+ if ( ! indexesResult . error && indexesResult . data ) {
112+ tableInfo . indexes = indexesResult . data ;
123113 }
124- }
125114
126- shape . tables . push ( tableInfo ) ;
127- }
115+ return tableInfo ;
116+ } )
117+ ) ;
128118
129119 // Get relationships (foreign keys)
130120 const relationshipsQuery = `
@@ -178,127 +168,103 @@ export class DatabaseInspector {
178168 timestamp : new Date ( ) . toISOString ( ) ,
179169 } ;
180170
181- // Cache hit ratio
182- if ( includeCacheStats ) {
183- const cacheQuery = `
184- SELECT
185- sum(heap_blks_read) as heap_read,
186- sum(heap_blks_hit) as heap_hit,
187- sum(heap_blks_hit) / NULLIF((sum(heap_blks_hit) + sum(heap_blks_read)), 0) as cache_hit_ratio
188- FROM pg_statio_user_tables
189- ` ;
190-
191- const { data : cacheData , error : cacheError } = await this . supabase . rpc ( 'exec_sql' , {
192- q : cacheQuery ,
193- params : { }
194- } ) ;
195-
196- if ( ! cacheError && cacheData && cacheData . length > 0 ) {
197- stats . cache_stats = {
198- hit_ratio : parseFloat ( cacheData [ 0 ] . cache_hit_ratio || 0 ) . toFixed ( 4 ) ,
199- heap_blocks_hit : parseInt ( cacheData [ 0 ] . heap_hit || 0 ) ,
200- heap_blocks_read : parseInt ( cacheData [ 0 ] . heap_read || 0 ) ,
201- } ;
202- }
171+ // Build all four queries upfront (empty string = skipped)
172+ const cacheQuery = `
173+ SELECT
174+ sum(heap_blks_read) as heap_read,
175+ sum(heap_blks_hit) as heap_hit,
176+ sum(heap_blks_hit) / NULLIF((sum(heap_blks_hit) + sum(heap_blks_read)), 0) as cache_hit_ratio
177+ FROM pg_statio_user_tables
178+ ` ;
179+
180+ const tableStatsQuery = `
181+ SELECT
182+ schemaname,
183+ relname as table_name,
184+ seq_scan, seq_tup_read, idx_scan, idx_tup_fetch,
185+ n_tup_ins as inserts, n_tup_upd as updates, n_tup_del as deletes,
186+ n_live_tup as live_rows, n_dead_tup as dead_rows
187+ FROM pg_stat_user_tables
188+ WHERE schemaname = 'public'
189+ ${ tableName ? `AND relname = '${ tableName } '` : '' }
190+ ORDER BY seq_scan + idx_scan DESC
191+ ` ;
192+
193+ const indexUsageQuery = `
194+ SELECT
195+ schemaname, tablename as table_name, indexname as index_name,
196+ idx_scan as scans, idx_tup_read as rows_read, idx_tup_fetch as rows_fetched
197+ FROM pg_stat_user_indexes
198+ WHERE schemaname = 'public'
199+ ${ tableName ? `AND tablename = '${ tableName } '` : '' }
200+ ORDER BY idx_scan DESC
201+ ` ;
202+
203+ const unusedIndexesQuery = `
204+ SELECT
205+ schemaname, tablename as table_name, indexname as index_name,
206+ pg_size_pretty(pg_relation_size(schemaname||'.'||indexname)) as index_size
207+ FROM pg_stat_user_indexes
208+ WHERE schemaname = 'public' AND idx_scan = 0 AND indexname NOT LIKE '%_pkey'
209+ ORDER BY pg_relation_size(schemaname||'.'||indexname) DESC
210+ ` ;
211+
212+ // Fire all independent stat queries in parallel
213+ const [
214+ { data : cacheData , error : cacheError } ,
215+ { data : tableStats , error : tableStatsError } ,
216+ { data : indexUsage , error : indexUsageError } ,
217+ { data : unusedIndexes , error : unusedError } ,
218+ ] = await Promise . all ( [
219+ includeCacheStats
220+ ? this . supabase . rpc ( 'exec_sql' , { q : cacheQuery , params : { } } )
221+ : Promise . resolve ( { data : null , error : null } ) ,
222+ includeQueryStats
223+ ? this . supabase . rpc ( 'exec_sql' , { q : tableStatsQuery , params : { } } )
224+ : Promise . resolve ( { data : null , error : null } ) ,
225+ includeIndexUsage
226+ ? this . supabase . rpc ( 'exec_sql' , { q : indexUsageQuery , params : { } } )
227+ : Promise . resolve ( { data : null , error : null } ) ,
228+ includeIndexUsage && ! tableName
229+ ? this . supabase . rpc ( 'exec_sql' , { q : unusedIndexesQuery , params : { } } )
230+ : Promise . resolve ( { data : null , error : null } ) ,
231+ ] ) ;
232+
233+ if ( ! cacheError && cacheData ?. length > 0 ) {
234+ stats . cache_stats = {
235+ hit_ratio : parseFloat ( cacheData [ 0 ] . cache_hit_ratio || 0 ) . toFixed ( 4 ) ,
236+ heap_blocks_hit : parseInt ( cacheData [ 0 ] . heap_hit || 0 ) ,
237+ heap_blocks_read : parseInt ( cacheData [ 0 ] . heap_read || 0 ) ,
238+ } ;
203239 }
204240
205- // Table access statistics
206- if ( includeQueryStats ) {
207- const tableStatsQuery = `
208- SELECT
209- schemaname,
210- relname as table_name,
211- seq_scan,
212- seq_tup_read,
213- idx_scan,
214- idx_tup_fetch,
215- n_tup_ins as inserts,
216- n_tup_upd as updates,
217- n_tup_del as deletes,
218- n_live_tup as live_rows,
219- n_dead_tup as dead_rows
220- FROM pg_stat_user_tables
221- WHERE schemaname = 'public'
222- ${ tableName ? `AND relname = '${ tableName } '` : '' }
223- ORDER BY seq_scan + idx_scan DESC
224- ` ;
225-
226- const { data : tableStats , error : tableStatsError } = await this . supabase . rpc ( 'exec_sql' , {
227- q : tableStatsQuery ,
228- params : { }
229- } ) ;
230-
231- if ( ! tableStatsError ) {
232- stats . table_stats = tableStats ?. map ( ( t : any ) => ( {
233- table_name : t . table_name ,
234- sequential_scans : parseInt ( t . seq_scan || 0 ) ,
235- sequential_rows_read : parseInt ( t . seq_tup_read || 0 ) ,
236- index_scans : parseInt ( t . idx_scan || 0 ) ,
237- index_rows_fetched : parseInt ( t . idx_tup_fetch || 0 ) ,
238- inserts : parseInt ( t . inserts || 0 ) ,
239- updates : parseInt ( t . updates || 0 ) ,
240- deletes : parseInt ( t . deletes || 0 ) ,
241- live_rows : parseInt ( t . live_rows || 0 ) ,
242- dead_rows : parseInt ( t . dead_rows || 0 ) ,
243- } ) ) ;
244- }
241+ if ( ! tableStatsError && tableStats ) {
242+ stats . table_stats = tableStats . map ( ( t : any ) => ( {
243+ table_name : t . table_name ,
244+ sequential_scans : parseInt ( t . seq_scan || 0 ) ,
245+ sequential_rows_read : parseInt ( t . seq_tup_read || 0 ) ,
246+ index_scans : parseInt ( t . idx_scan || 0 ) ,
247+ index_rows_fetched : parseInt ( t . idx_tup_fetch || 0 ) ,
248+ inserts : parseInt ( t . inserts || 0 ) ,
249+ updates : parseInt ( t . updates || 0 ) ,
250+ deletes : parseInt ( t . deletes || 0 ) ,
251+ live_rows : parseInt ( t . live_rows || 0 ) ,
252+ dead_rows : parseInt ( t . dead_rows || 0 ) ,
253+ } ) ) ;
245254 }
246255
247- // Index usage statistics
248- if ( includeIndexUsage ) {
249- const indexUsageQuery = `
250- SELECT
251- schemaname,
252- tablename as table_name,
253- indexname as index_name,
254- idx_scan as scans,
255- idx_tup_read as rows_read,
256- idx_tup_fetch as rows_fetched
257- FROM pg_stat_user_indexes
258- WHERE schemaname = 'public'
259- ${ tableName ? `AND tablename = '${ tableName } '` : '' }
260- ORDER BY idx_scan DESC
261- ` ;
262-
263- const { data : indexUsage , error : indexUsageError } = await this . supabase . rpc ( 'exec_sql' , {
264- q : indexUsageQuery ,
265- params : { }
266- } ) ;
267-
268- if ( ! indexUsageError ) {
269- stats . index_usage = indexUsage ?. map ( ( i : any ) => ( {
270- table_name : i . table_name ,
271- index_name : i . index_name ,
272- scans : parseInt ( i . scans || 0 ) ,
273- rows_read : parseInt ( i . rows_read || 0 ) ,
274- rows_fetched : parseInt ( i . rows_fetched || 0 ) ,
275- } ) ) ;
276- }
256+ if ( ! indexUsageError && indexUsage ) {
257+ stats . index_usage = indexUsage . map ( ( i : any ) => ( {
258+ table_name : i . table_name ,
259+ index_name : i . index_name ,
260+ scans : parseInt ( i . scans || 0 ) ,
261+ rows_read : parseInt ( i . rows_read || 0 ) ,
262+ rows_fetched : parseInt ( i . rows_fetched || 0 ) ,
263+ } ) ) ;
277264 }
278265
279- // Find unused indexes
280- if ( includeIndexUsage && ! tableName ) {
281- const unusedIndexesQuery = `
282- SELECT
283- schemaname,
284- tablename as table_name,
285- indexname as index_name,
286- pg_size_pretty(pg_relation_size(schemaname||'.'||indexname)) as index_size
287- FROM pg_stat_user_indexes
288- WHERE schemaname = 'public'
289- AND idx_scan = 0
290- AND indexname NOT LIKE '%_pkey'
291- ORDER BY pg_relation_size(schemaname||'.'||indexname) DESC
292- ` ;
293-
294- const { data : unusedIndexes , error : unusedError } = await this . supabase . rpc ( 'exec_sql' , {
295- q : unusedIndexesQuery ,
296- params : { }
297- } ) ;
298-
299- if ( ! unusedError ) {
300- stats . unused_indexes = unusedIndexes ;
301- }
266+ if ( ! unusedError && unusedIndexes ) {
267+ stats . unused_indexes = unusedIndexes ;
302268 }
303269
304270 return stats ;
@@ -347,7 +313,7 @@ export class DatabaseInspector {
347313 */
348314 async getConnectionInfo ( ) {
349315 const query = `
350- SELECT
316+ SELECT
351317 current_database() as database_name,
352318 current_schema() as current_schema,
353319 version() as postgres_version,
@@ -364,4 +330,3 @@ export class DatabaseInspector {
364330 return data && data . length > 0 ? data [ 0 ] : null ;
365331 }
366332}
367-
0 commit comments