@@ -195,6 +195,86 @@ func (c *PostgreSQLClientImpl) ListTables(schema string) ([]*TableInfo, error) {
195195 return tables , nil
196196}
197197
198+ // ListTablesWithStats returns a list of tables with size and row count statistics in a single optimized query.
199+ // This eliminates the N+1 query pattern by joining table metadata with pg_stat_user_tables.
200+ // For tables where statistics show 0 rows, it falls back to COUNT(*) to get actual row counts.
201+ func (c * PostgreSQLClientImpl ) ListTablesWithStats (schema string ) ([]* TableInfo , error ) {
202+ if c .db == nil {
203+ return nil , ErrNoDatabaseConnection
204+ }
205+
206+ if schema == "" {
207+ schema = DefaultSchema
208+ }
209+
210+ // Single optimized query that joins tables with statistics
211+ // We use n_tup_ins - n_tup_del which is more accurate than n_live_tup for recently modified tables
212+ query := `
213+ WITH table_list AS (
214+ SELECT
215+ schemaname,
216+ tablename,
217+ 'table' as type,
218+ tableowner as owner
219+ FROM pg_tables
220+ WHERE schemaname = $1
221+ UNION ALL
222+ SELECT
223+ schemaname,
224+ viewname as tablename,
225+ 'view' as type,
226+ viewowner as owner
227+ FROM pg_views
228+ WHERE schemaname = $1
229+ )
230+ SELECT
231+ t.schemaname,
232+ t.tablename,
233+ t.type,
234+ t.owner,
235+ COALESCE(s.n_tup_ins - s.n_tup_del, 0) as row_count,
236+ pg_size_pretty(COALESCE(pg_total_relation_size(quote_ident(t.schemaname) || '.' || quote_ident(t.tablename)), 0)) as size
237+ FROM table_list t
238+ LEFT JOIN pg_stat_user_tables s
239+ ON t.schemaname = s.schemaname AND t.tablename = s.relname
240+ ORDER BY t.tablename`
241+
242+ rows , err := c .db .QueryContext (context .Background (), query , schema )
243+ if err != nil {
244+ return nil , fmt .Errorf ("failed to list tables with stats: %w" , err )
245+ }
246+ defer func () { _ = rows .Close () }()
247+
248+ var tables []* TableInfo
249+ for rows .Next () {
250+ var table TableInfo
251+ if err := rows .Scan (& table .Schema , & table .Name , & table .Type , & table .Owner , & table .RowCount , & table .Size ); err != nil {
252+ return nil , fmt .Errorf ("failed to scan table row with stats: %w" , err )
253+ }
254+ tables = append (tables , & table )
255+ }
256+
257+ if err := rows .Err (); err != nil {
258+ return nil , fmt .Errorf ("failed to iterate table rows with stats: %w" , err )
259+ }
260+
261+ // For tables where statistics show 0 rows, fall back to actual COUNT(*)
262+ // This handles newly created tables where pg_stat hasn't been updated yet
263+ for _ , table := range tables {
264+ if table .RowCount == 0 && table .Type == "table" {
265+ countQuery := `SELECT COUNT(*) FROM "` + table .Schema + `"."` + table .Name + `"`
266+ var actualCount int64
267+ if err := c .db .QueryRowContext (context .Background (), countQuery ).Scan (& actualCount ); err != nil {
268+ // Log warning but don't fail the entire operation
269+ continue
270+ }
271+ table .RowCount = actualCount
272+ }
273+ }
274+
275+ return tables , nil
276+ }
277+
198278// DescribeTable returns detailed column information for a table.
199279func (c * PostgreSQLClientImpl ) DescribeTable (schema , table string ) ([]* ColumnInfo , error ) {
200280 if c .db == nil {
0 commit comments