19
19
#include "catalog/pg_class_d.h"
20
20
21
21
#include "common.h"
22
+ #include "fe_utils/connect.h"
22
23
#include "fe_utils/simple_list.h"
23
24
#include "fe_utils/string_utils.h"
24
25
@@ -61,10 +62,8 @@ static void vacuum_all_databases(vacuumingOptions *vacopts,
61
62
int concurrentCons ,
62
63
const char * progname , bool echo , bool quiet );
63
64
64
- static void prepare_vacuum_command (PQExpBuffer sql , PGconn * conn ,
65
- vacuumingOptions * vacopts , const char * table ,
66
- bool table_pre_qualified ,
67
- const char * progname , bool echo );
65
+ static void prepare_vacuum_command (PQExpBuffer sql , int serverVersion ,
66
+ vacuumingOptions * vacopts , const char * table );
68
67
69
68
static void run_vacuum_command (PGconn * conn , const char * sql , bool echo ,
70
69
const char * table , const char * progname , bool async );
@@ -359,13 +358,18 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts,
359
358
const char * progname , bool echo , bool quiet )
360
359
{
361
360
PQExpBufferData sql ;
361
+ PQExpBufferData buf ;
362
+ PQExpBufferData catalog_query ;
363
+ PGresult * res ;
362
364
PGconn * conn ;
363
365
SimpleStringListCell * cell ;
364
366
ParallelSlot * slots ;
365
367
SimpleStringList dbtables = {NULL , NULL };
366
368
int i ;
369
+ int ntups ;
367
370
bool failed = false;
368
371
bool parallel = concurrentCons > 1 ;
372
+ bool tables_listed = false;
369
373
const char * stage_commands [] = {
370
374
"SET default_statistics_target=1; SET vacuum_cost_delay=0;" ,
371
375
"SET default_statistics_target=10; RESET vacuum_cost_delay;" ,
@@ -410,53 +414,132 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts,
410
414
fflush (stdout );
411
415
}
412
416
413
- initPQExpBuffer (& sql );
414
-
415
417
/*
416
- * If a table list is not provided and we're using multiple connections,
417
- * prepare the list of tables by querying the catalogs.
418
+ * Prepare the list of tables to process by querying the catalogs.
419
+ *
420
+ * Since we execute the constructed query with the default search_path
421
+ * (which could be unsafe), everything in this query MUST be fully
422
+ * qualified.
423
+ *
424
+ * First, build a WITH clause for the catalog query if any tables were
425
+ * specified, with a set of values made of relation names and their
426
+ * optional set of columns. This is used to match any provided column
427
+ * lists with the generated qualified identifiers and to filter for the
428
+ * tables provided via --table. If a listed table does not exist, the
429
+ * catalog query will fail.
418
430
*/
419
- if (parallel && (!tables || !tables -> head ))
431
+ initPQExpBuffer (& catalog_query );
432
+ for (cell = tables ? tables -> head : NULL ; cell ; cell = cell -> next )
420
433
{
421
- PQExpBufferData buf ;
422
- PGresult * res ;
423
- int ntups ;
424
-
425
- initPQExpBuffer (& buf );
426
-
427
- res = executeQuery (conn ,
428
- "SELECT c.relname, ns.nspname"
429
- " FROM pg_class c, pg_namespace ns\n"
430
- " WHERE relkind IN ("
431
- CppAsString2 (RELKIND_RELATION ) ", "
432
- CppAsString2 (RELKIND_MATVIEW ) ")"
433
- " AND c.relnamespace = ns.oid\n"
434
- " ORDER BY c.relpages DESC;" ,
435
- progname , echo );
436
-
437
- ntups = PQntuples (res );
438
- for (i = 0 ; i < ntups ; i ++ )
439
- {
440
- appendPQExpBufferStr (& buf ,
441
- fmtQualifiedId (PQgetvalue (res , i , 1 ),
442
- PQgetvalue (res , i , 0 )));
434
+ char * just_table ;
435
+ const char * just_columns ;
443
436
444
- simple_string_list_append (& dbtables , buf .data );
445
- resetPQExpBuffer (& buf );
437
+ /*
438
+ * Split relation and column names given by the user, this is used to
439
+ * feed the CTE with values on which are performed pre-run validity
440
+ * checks as well. For now these happen only on the relation name.
441
+ */
442
+ splitTableColumnsSpec (cell -> val , PQclientEncoding (conn ),
443
+ & just_table , & just_columns );
444
+
445
+ if (!tables_listed )
446
+ {
447
+ appendPQExpBuffer (& catalog_query ,
448
+ "WITH listed_tables (table_oid, column_list) "
449
+ "AS (\n VALUES (" );
450
+ tables_listed = true;
446
451
}
452
+ else
453
+ appendPQExpBuffer (& catalog_query , ",\n (" );
447
454
448
- termPQExpBuffer ( & buf );
449
- tables = & dbtables ;
455
+ appendStringLiteralConn ( & catalog_query , just_table , conn );
456
+ appendPQExpBuffer ( & catalog_query , "::pg_catalog.regclass, " ) ;
450
457
451
- /*
452
- * If there are more connections than vacuumable relations, we don't
453
- * need to use them all.
454
- */
458
+ if (just_columns && just_columns [0 ] != '\0' )
459
+ appendStringLiteralConn (& catalog_query , just_columns , conn );
460
+ else
461
+ appendPQExpBufferStr (& catalog_query , "NULL" );
462
+
463
+ appendPQExpBufferStr (& catalog_query , "::pg_catalog.text)" );
464
+
465
+ pg_free (just_table );
466
+ }
467
+
468
+ /* Finish formatting the CTE */
469
+ if (tables_listed )
470
+ appendPQExpBuffer (& catalog_query , "\n)\n" );
471
+
472
+ appendPQExpBuffer (& catalog_query , "SELECT c.relname, ns.nspname" );
473
+
474
+ if (tables_listed )
475
+ appendPQExpBuffer (& catalog_query , ", listed_tables.column_list" );
476
+
477
+ appendPQExpBuffer (& catalog_query ,
478
+ " FROM pg_catalog.pg_class c\n"
479
+ " JOIN pg_catalog.pg_namespace ns"
480
+ " ON c.relnamespace OPERATOR(pg_catalog.=) ns.oid\n" );
481
+
482
+ /* Used to match the tables listed by the user */
483
+ if (tables_listed )
484
+ appendPQExpBuffer (& catalog_query , " JOIN listed_tables"
485
+ " ON listed_tables.table_oid OPERATOR(pg_catalog.=) c.oid\n" );
486
+
487
+ appendPQExpBuffer (& catalog_query , " WHERE c.relkind OPERATOR(pg_catalog.=) ANY (array["
488
+ CppAsString2 (RELKIND_RELATION ) ", "
489
+ CppAsString2 (RELKIND_MATVIEW ) "])\n" );
490
+
491
+ /*
492
+ * Execute the catalog query. We use the default search_path for this
493
+ * query for consistency with table lookups done elsewhere by the user.
494
+ */
495
+ appendPQExpBuffer (& catalog_query , " ORDER BY c.relpages DESC;" );
496
+ executeCommand (conn , "RESET search_path;" , progname , echo );
497
+ res = executeQuery (conn , catalog_query .data , progname , echo );
498
+ termPQExpBuffer (& catalog_query );
499
+ PQclear (executeQuery (conn , ALWAYS_SECURE_SEARCH_PATH_SQL ,
500
+ progname , echo ));
501
+
502
+ /*
503
+ * If no rows are returned, there are no matching tables, so we are done.
504
+ */
505
+ ntups = PQntuples (res );
506
+ if (ntups == 0 )
507
+ {
508
+ PQclear (res );
509
+ PQfinish (conn );
510
+ return ;
511
+ }
512
+
513
+ /*
514
+ * Build qualified identifiers for each table, including the column list
515
+ * if given.
516
+ */
517
+ initPQExpBuffer (& buf );
518
+ for (i = 0 ; i < ntups ; i ++ )
519
+ {
520
+ appendPQExpBufferStr (& buf ,
521
+ fmtQualifiedId (PQgetvalue (res , i , 1 ),
522
+ PQgetvalue (res , i , 0 )));
523
+
524
+ if (tables_listed && !PQgetisnull (res , i , 2 ))
525
+ appendPQExpBufferStr (& buf , PQgetvalue (res , i , 2 ));
526
+
527
+ simple_string_list_append (& dbtables , buf .data );
528
+ resetPQExpBuffer (& buf );
529
+ }
530
+ termPQExpBuffer (& buf );
531
+ PQclear (res );
532
+
533
+ /*
534
+ * If there are more connections than vacuumable relations, we don't need
535
+ * to use them all.
536
+ */
537
+ if (parallel )
538
+ {
455
539
if (concurrentCons > ntups )
456
540
concurrentCons = ntups ;
457
541
if (concurrentCons <= 1 )
458
542
parallel = false;
459
- PQclear (res );
460
543
}
461
544
462
545
/*
@@ -493,10 +576,12 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts,
493
576
stage_commands [stage ], progname , echo );
494
577
}
495
578
496
- cell = tables ? tables -> head : NULL ;
579
+ initPQExpBuffer (& sql );
580
+
581
+ cell = dbtables .head ;
497
582
do
498
583
{
499
- const char * tabname = cell ? cell -> val : NULL ;
584
+ const char * tabname = cell -> val ;
500
585
ParallelSlot * free_slot ;
501
586
502
587
if (CancelRequested )
@@ -529,12 +614,8 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts,
529
614
else
530
615
free_slot = slots ;
531
616
532
- /*
533
- * Prepare the vacuum command. Note that in some cases this requires
534
- * query execution, so be sure to use the free connection.
535
- */
536
- prepare_vacuum_command (& sql , free_slot -> connection , vacopts , tabname ,
537
- tables == & dbtables , progname , echo );
617
+ prepare_vacuum_command (& sql , PQserverVersion (free_slot -> connection ),
618
+ vacopts , tabname );
538
619
539
620
/*
540
621
* Execute the vacuum. If not in parallel mode, this terminates the
@@ -544,8 +625,7 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts,
544
625
run_vacuum_command (free_slot -> connection , sql .data ,
545
626
echo , tabname , progname , parallel );
546
627
547
- if (cell )
548
- cell = cell -> next ;
628
+ cell = cell -> next ;
549
629
} while (cell != NULL );
550
630
551
631
if (parallel )
@@ -653,14 +733,12 @@ vacuum_all_databases(vacuumingOptions *vacopts,
653
733
* Construct a vacuum/analyze command to run based on the given options, in the
654
734
* given string buffer, which may contain previous garbage.
655
735
*
656
- * An optional table name can be passed; this must be already be properly
657
- * quoted. The command is semicolon-terminated.
736
+ * The table name used must be already properly quoted. The command generated
737
+ * depends on the server version involved and it is semicolon-terminated.
658
738
*/
659
739
static void
660
- prepare_vacuum_command (PQExpBuffer sql , PGconn * conn ,
661
- vacuumingOptions * vacopts , const char * table ,
662
- bool table_pre_qualified ,
663
- const char * progname , bool echo )
740
+ prepare_vacuum_command (PQExpBuffer sql , int serverVersion ,
741
+ vacuumingOptions * vacopts , const char * table )
664
742
{
665
743
const char * paren = " (" ;
666
744
const char * comma = ", " ;
@@ -673,12 +751,12 @@ prepare_vacuum_command(PQExpBuffer sql, PGconn *conn,
673
751
appendPQExpBufferStr (sql , "ANALYZE" );
674
752
675
753
/* parenthesized grammar of ANALYZE is supported since v11 */
676
- if (PQserverVersion ( conn ) >= 110000 )
754
+ if (serverVersion >= 110000 )
677
755
{
678
756
if (vacopts -> skip_locked )
679
757
{
680
758
/* SKIP_LOCKED is supported since v12 */
681
- Assert (PQserverVersion ( conn ) >= 120000 );
759
+ Assert (serverVersion >= 120000 );
682
760
appendPQExpBuffer (sql , "%sSKIP_LOCKED" , sep );
683
761
sep = comma ;
684
762
}
@@ -701,19 +779,19 @@ prepare_vacuum_command(PQExpBuffer sql, PGconn *conn,
701
779
appendPQExpBufferStr (sql , "VACUUM" );
702
780
703
781
/* parenthesized grammar of VACUUM is supported since v9.0 */
704
- if (PQserverVersion ( conn ) >= 90000 )
782
+ if (serverVersion >= 90000 )
705
783
{
706
784
if (vacopts -> disable_page_skipping )
707
785
{
708
786
/* DISABLE_PAGE_SKIPPING is supported since v9.6 */
709
- Assert (PQserverVersion ( conn ) >= 90600 );
787
+ Assert (serverVersion >= 90600 );
710
788
appendPQExpBuffer (sql , "%sDISABLE_PAGE_SKIPPING" , sep );
711
789
sep = comma ;
712
790
}
713
791
if (vacopts -> skip_locked )
714
792
{
715
793
/* SKIP_LOCKED is supported since v12 */
716
- Assert (PQserverVersion ( conn ) >= 120000 );
794
+ Assert (serverVersion >= 120000 );
717
795
appendPQExpBuffer (sql , "%sSKIP_LOCKED" , sep );
718
796
sep = comma ;
719
797
}
@@ -753,15 +831,7 @@ prepare_vacuum_command(PQExpBuffer sql, PGconn *conn,
753
831
}
754
832
}
755
833
756
- if (table )
757
- {
758
- appendPQExpBufferChar (sql , ' ' );
759
- if (table_pre_qualified )
760
- appendPQExpBufferStr (sql , table );
761
- else
762
- appendQualifiedRelation (sql , table , conn , progname , echo );
763
- }
764
- appendPQExpBufferChar (sql , ';' );
834
+ appendPQExpBuffer (sql , " %s;" , table );
765
835
}
766
836
767
837
/*
0 commit comments