├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── check_attribute.c ├── check_class.c ├── check_depend.c ├── check_oids.c ├── compat.c ├── compat.h ├── definitions.c ├── log.c ├── meson.build ├── pg_catcheck.c ├── pg_catcheck.h ├── pg_catcheck.proj ├── pgrhash.c ├── select_from_relations.c ├── settings.projinc └── typedefs.list /.gitignore: -------------------------------------------------------------------------------- 1 | /pg_catcheck 2 | /*.o 3 | /.deps 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | pg_catcheck 2 | 3 | Portions Copyright (c) 2013-2014, EnterpriseDB Corporation 4 | Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group 5 | Portions Copyright (c) 1994, The Regents of the University of California 6 | 7 | Permission to use, copy, modify, and distribute this software and its 8 | documentation for any purpose, without fee, and without a written agreement 9 | is hereby granted, provided that the above copyright notice and this 10 | paragraph and the following two paragraphs appear in all copies. 11 | 12 | IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR 13 | DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING 14 | LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS 15 | DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE 16 | POSSIBILITY OF SUCH DAMAGE. 17 | 18 | THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 19 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 20 | AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS 21 | ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO 22 | PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PGFILEDESC = "pg_catcheck - system catalog integrity checker" 2 | PGAPPICON = win32 3 | 4 | PROGRAM = pg_catcheck 5 | OBJS = pg_catcheck.o check_attribute.o check_class.o check_depend.o \ 6 | check_oids.o compat.o definitions.o log.o pgrhash.o \ 7 | select_from_relations.o 8 | 9 | PG_CPPFLAGS = -I$(libpq_srcdir) 10 | PG_LIBS = $(libpq_pgport) $(PTHREAD_LIBS) 11 | 12 | PG_CONFIG = pg_config 13 | PGXS := $(shell $(PG_CONFIG) --pgxs) 14 | include $(PGXS) 15 | 16 | ifneq ($(PORTNAME), win32) 17 | override CFLAGS += $(PTHREAD_CFLAGS) 18 | endif 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | What is pg_catcheck? 2 | ==================== 3 | 4 | pg_catcheck is a simple tool for diagnosing system catalog corruption. 5 | If you suspect that your system catalogs are corrupted, this tool may 6 | help you figure out exactly what problems you have and how serious they 7 | are. If you are paranoid, you can run it routinely to search for system 8 | catalog corruption that might otherwise go undetected. However, pg_catcheck 9 | is not a general corruption detector. For that, you should use PostgreSQL's 10 | checksum feature (`initdb -k`). 11 | 12 | PostgreSQL stores the metadata for SQL objects such as tables and functions 13 | using special tables called system catalog tables. Users do not normally 14 | modify these tables directly, but instead modify them using SQL commands 15 | such as CREATE, ALTER, and DROP. If the system catalog tables become 16 | corrupted, you may experience errors when attempting to access your data. 17 | Sometimes, it can be impossible to back up your data using pg_dump without 18 | correcting these errors. pg_catcheck won't tell you how to your database 19 | got corrupted in the first place, and it won't tell you how to fix it. 20 | But it will usually be able to give you detailed information about what 21 | is broken, which may make it easier for you (or your PostgreSQL support 22 | provider) to understand what has gone wrong and explain the options for 23 | recovery. 24 | 25 | How Do I Run pg_catcheck? 26 | ========================= 27 | 28 | pg_catcheck takes the same arguments as most other PostgreSQL utilites, 29 | such as -h for the host or -p for the port. You can also pass it a 30 | connection string or URL, just like psql. For a full list of options, 31 | run `pg_catcheck --help`. If pg_catcheck isn't already installed, you might 32 | need to build it first. If no pre-compiled binary package is available for 33 | you to install, see the instructions below for "Building on UNIX/Linux" and 34 | "Building on Windows". 35 | 36 | When you run pg_catcheck, it will normally print out a line that looks like 37 | this: 38 | 39 | progress: done (0 inconsistencies, 0 warnings, 0 errors) 40 | 41 | If you see that line, it means pg_catcheck didn't find any problems. 42 | Otherwise, pg_catcheck will generally print two lines of output for each 43 | problem it finds, like this: 44 | 45 | notice: pg_class row has invalid relnamespace "24580": no matching entry in pg_namespace 46 | row identity: oid="24581" relname="foo" relkind="r" 47 | notice: pg_type row has invalid typnamespace "24580": no matching entry in pg_namespace 48 | row identity: oid="24583" 49 | notice: pg_type row has invalid typnamespace "24580": no matching entry in pg_namespace 50 | row identity: oid="24582" 51 | notice: pg_depend row has invalid refobjid "24580": no matching entry in pg_namespace 52 | row identity: classid="1259" objid="24581" objsubid="0" refclassid="2615" refobjid="24580" refobjsubid="0" deptype="n" 53 | progress: done (4 inconsistencies, 0 warnings, 0 errors) 54 | 55 | If the final output line mentions inconsistencies, that means that it found 56 | problems with the logical structure of your system catalogs. Warnings or 57 | errors indicate more serious problems, like not being able to read the system 58 | catalogs at all. In this particular example, there are four errors: one 59 | pg_class row, two pg_type rows, and one pg_depend row all reference an OID 60 | 24580 which they expect to find in pg_namespace. In reality, no such row 61 | exists. 62 | 63 | There are several ways to recover from an error of this type. You could 64 | modify the OIDs in the referring rows so that they refer to a pg_namespace 65 | entry that does exist. This might enable you to recover access to the 66 | underlying data. Note that in this case all four references pertain to the 67 | same table (pg_class OID 24581, which has pg_type OIDs 24582 and 24583 for 68 | its record and array-of-record types) so you would probably want to make 69 | all of those references point to the same namespace. Alternatively, if the 70 | dangling references are objects you don't care about (e.g. if the backing 71 | file for the pg_class entry doesn't even exist on disk), you could simply 72 | delete the referring rows also. This is often enough to make pg_dump run 73 | successfully, which is often the main goal. 74 | 75 | Unless you are sure you understand what pg_catcheck is telling you, you 76 | may wish to consult with a PostgreSQL expert. Changing the system catalogs 77 | manually can make a bad situation worse and lead to data loss, and should 78 | not be attempted unless you are knowledgeable about how PostgreSQL uses these 79 | catalogs. 80 | 81 | pg_catcheck also provides an option to run "SELECT * FROM table_name LIMIT 0" 82 | on each table in the database, which will detect missing or inaccessible 83 | relation files. The --select-from-relations option enables this check. 84 | 85 | What is the license for pg_catcheck? Can I contribute? 86 | ======================================================= 87 | 88 | pg_catcheck was initially developed by EnterpriseDB and is released under 89 | the same license as PostgreSQL itself. Patches are welcome. Please subscribe 90 | to our mailing list, pg-catcheck@enterprisedb.com, by visiting: 91 | 92 | https://groups.google.com/a/enterprisedb.com/forum/#!forum/pg-catcheck 93 | 94 | How do I get support for pg_catcheck? 95 | ===================================== 96 | 97 | As with any open source project, you may be able to obtain support via the 98 | public mailing list, which is pg-catcheck@enterprisedb.com; to subscribe, 99 | visit: 100 | 101 | https://groups.google.com/a/enterprisedb.com/forum/#!forum/pg-catcheck 102 | 103 | If you need commercial support, please contact the EnterpriseDB sales 104 | team, or check whether your existing PostgreSQL support provider can also 105 | support pg_catcheck. 106 | 107 | What versions of PostgreSQL does pg_catcheck support? 108 | ===================================================== 109 | 110 | pg_catcheck should work when run against a server running PostgreSQL 8.4 111 | or higher. It also should also work when run against a server running 112 | EnterpriseDB's Advanced Server product, version 8.4 or higher. To 113 | compile pg_catcheck, you will need to build against a server source tree 114 | version 9.0 or higher, because it relies on the function PQconnectdbParams(), 115 | which did not exist in 8.4. 116 | 117 | Building on UNIX/Linux 118 | ====================== 119 | 120 | * Make sure that you have a working pg_config executable in your path. 121 | (If you are using a binary installation of PostgreSQL, you might need 122 | to install additional packages, such as postgresql-devel or libpq-dev.) 123 | * Run "make" and, if desired, "make install". 124 | * To remove generated files, run "make clean". 125 | 126 | Building on Windows 127 | =================== 128 | 129 | * Build PostgreSQL with MSVC as described in 130 | http://www.postgresql.org/docs/devel/static/install-windows-full.html 131 | * Start the Visual Studio Command Prompt. If you wish to build a 64-bit 132 | version, you must use the 64-bit version of the command. 133 | * "cd" to the source directory (e.g. cd c:\pg_catcheck) 134 | * Use a command like "msbuild /p:PGPATH=C:\postgresql-9.4.0 /p:DEBUG=0 135 | /p:ARCH=x64" to perform the actual build. PGPATH should be set to the 136 | location of the PostgreSQL source code, and DEBUG should be set to 1 for 137 | a debug build. 138 | * If you wish to remove the generated files, use "msbuild /target:clean". 139 | -------------------------------------------------------------------------------- /check_attribute.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * check_attribute.c 4 | * 5 | * Custom checks for pg_attribute fields. 6 | * 7 | *------------------------------------------------------------------------- 8 | */ 9 | 10 | #include "postgres_fe.h" 11 | #include "pg_catcheck.h" 12 | 13 | /* 14 | * pg_attribute_d.h was added by commit 372728b0d, which first appeared in 15 | * v11. But including pg_attribute.h still worked fine until commit 16 | * d939cb2fd appeared in v17. Realistically, pg_attribute_d.h should work 17 | * for any version anyone still cares about, but for now, just enable it 18 | * for new versions. 19 | */ 20 | #if PG_VERSION_NUM >= 170000 21 | #include "catalog/pg_attribute_d.h" 22 | #else 23 | #include "catalog/pg_attribute.h" 24 | #endif 25 | 26 | typedef struct 27 | { 28 | pg_catalog_table *pg_class; 29 | int attrelid_result_column; 30 | int relnatts_result_column; 31 | } attnum_cache; 32 | 33 | /* 34 | * Set up to check attnum. 35 | */ 36 | void 37 | prepare_to_check_attnum(pg_catalog_table *tab, pg_catalog_column *tabcol) 38 | { 39 | add_table_dependency(tab, find_table_by_name("pg_class")); 40 | } 41 | 42 | /* 43 | * Sanity-check the relnatts field. 44 | */ 45 | void 46 | check_attnum(pg_catalog_table *tab, pg_catalog_column *tabcol, int rownum) 47 | { 48 | char *val = PQgetvalue(tab->data, rownum, tabcol->result_column); 49 | char *attrelid_val; 50 | char *relnatts_val; 51 | char *endptr; 52 | attnum_cache *cache; 53 | int class_rownum; 54 | long attnum; 55 | long relnatts; 56 | long min_attno; 57 | 58 | /* Convert the value to a number. */ 59 | attnum = strtol(val, &endptr, 10); 60 | if (*endptr != '\0') 61 | { 62 | pgcc_report(tab, tabcol, rownum, "must be an integer\n"); 63 | return; 64 | } 65 | 66 | /* Our attribute number should not be zero. */ 67 | if (attnum == 0) 68 | { 69 | pgcc_report(tab, tabcol, rownum, "must not be zero\n"); 70 | return; 71 | } 72 | 73 | /* And it should be at least -7 for PostgreSQL, -8 for EnterpriseDB. */ 74 | min_attno = remote_is_edb ? -8 : -7; 75 | if (attnum < min_attno) 76 | { 77 | pgcc_report(tab, tabcol, rownum, "must be at least %ld\n", 78 | min_attno); 79 | return; 80 | } 81 | 82 | /* Find the pg_attribute table; cache result in check_private. */ 83 | if (tabcol->check_private == NULL) 84 | { 85 | cache = pg_malloc(sizeof(attnum_cache)); 86 | cache->pg_class = find_table_by_name("pg_class"); 87 | cache->attrelid_result_column = PQfnumber(tab->data, "attrelid"); 88 | cache->relnatts_result_column = PQfnumber(cache->pg_class->data, 89 | "relnatts"); 90 | tabcol->check_private = cache; 91 | } 92 | else 93 | cache = tabcol->check_private; 94 | 95 | /* 96 | * Skip max-bound checking if the pg_class data is not available, or if 97 | * the pg_class.relnatts or pg_attribute.attrelid column is not available. 98 | */ 99 | if (cache->pg_class->ht == NULL || cache->relnatts_result_column == -1 || 100 | cache->attrelid_result_column == -1) 101 | return; 102 | 103 | /* Find row number of this table in pg_class. */ 104 | attrelid_val = PQgetvalue(tab->data, rownum, 105 | cache->attrelid_result_column); 106 | class_rownum = pgrhash_get(cache->pg_class->ht, &attrelid_val); 107 | if (class_rownum == -1) 108 | return; /* It's not our job to complain about 109 | * attrelid. */ 110 | 111 | /* Get relnatts, as a number. */ 112 | relnatts_val = PQgetvalue(cache->pg_class->data, class_rownum, 113 | cache->relnatts_result_column); 114 | relnatts = strtol(relnatts_val, &endptr, 10); 115 | if (*endptr != '\0' || relnatts < 0) 116 | return; /* It's not our job to complain about 117 | * relnatts. */ 118 | 119 | /* Our attribute number should be less than relnatts. */ 120 | if (attnum > relnatts) 121 | pgcc_report(tab, tabcol, rownum, 122 | "exceeds relnatts value of %ld\n", 123 | relnatts); 124 | } 125 | -------------------------------------------------------------------------------- /check_class.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * check_class.c 4 | * 5 | * Custom checks for pg_class fields. 6 | * 7 | *------------------------------------------------------------------------- 8 | */ 9 | 10 | #include "postgres_fe.h" 11 | #include "pg_catcheck.h" 12 | 13 | typedef struct 14 | { 15 | pg_catalog_table *pg_attribute; 16 | int oid_result_column; 17 | } relnatts_cache; 18 | 19 | /* 20 | * Set up to check relnatts. 21 | */ 22 | void 23 | prepare_to_check_relnatts(pg_catalog_table *tab, pg_catalog_column *tabcol) 24 | { 25 | add_table_dependency(tab, find_table_by_name("pg_attribute")); 26 | } 27 | 28 | /* 29 | * Sanity-check the relnatts field. 30 | */ 31 | void 32 | check_relnatts(pg_catalog_table *tab, pg_catalog_column *tabcol, int rownum) 33 | { 34 | char *val = PQgetvalue(tab->data, rownum, tabcol->result_column); 35 | char *endptr; 36 | relnatts_cache *cache; 37 | long relnatts; 38 | int attno; 39 | char buf[512]; 40 | char *keys[2]; 41 | 42 | /* Convert the value to a number. */ 43 | relnatts = strtol(val, &endptr, 10); 44 | if (*endptr != '\0' || val < 0) 45 | pgcc_report(tab, tabcol, rownum, "must be a non-negative integer\n"); 46 | 47 | /* Find the pg_attribute table; cache result in check_private. */ 48 | if (tabcol->check_private == NULL) 49 | { 50 | cache = pg_malloc(sizeof(relnatts_cache)); 51 | cache->pg_attribute = find_table_by_name("pg_attribute"); 52 | cache->oid_result_column = PQfnumber(tab->data, "oid"); 53 | tabcol->check_private = cache; 54 | } 55 | else 56 | cache = tabcol->check_private; 57 | 58 | /* 59 | * Skip detailed checking if pg_attribute data is not available, or if the 60 | * oid column of pg_class is not available. 61 | */ 62 | if (cache->pg_attribute->ht == NULL || cache->oid_result_column == -1) 63 | return; 64 | 65 | /* Set up for pg_attribute hash table probes. */ 66 | keys[0] = PQgetvalue(tab->data, rownum, cache->oid_result_column); 67 | keys[1] = buf; 68 | 69 | /* 70 | * Check that all positive-numbered attributes we expect to find are in 71 | * fact present. 72 | * 73 | * TODO: We could check for negative-numbered attributes as well, but 74 | * whether or not those are present will depend on relkind inter alia. 75 | */ 76 | for (attno = 1; attno <= relnatts; ++attno) 77 | { 78 | snprintf(buf, sizeof buf, "%d", attno); 79 | if (pgrhash_get(cache->pg_attribute->ht, keys) == -1) 80 | pgcc_report(tab, tabcol, rownum, 81 | "attribute %d does not exist in pg_attribute\n", 82 | attno); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /check_depend.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * check_depend.c 4 | * 5 | * A number of PostgreSQL system catalogs store references to SQL objects 6 | * of arbitrary type by recording a class ID (the OID of the system 7 | * catalog that contains the referenced object) and an object ID (the OID 8 | * of the referenced object within that catalog). In cases where the 9 | * referenced object may be a table column, there is also a sub-ID; 10 | * when the referenced object is a table column, (class ID, sub-ID) should 11 | * match the pg_attribute row's (attrelid, attnum). In all other cases, 12 | * the sub-ID should be zero. 13 | * 14 | * The code in this file aims to validate the class ID, object ID, and 15 | * sub-ID. There is some duplication in the code structure, because to 16 | * check the object ID, we must validate the class ID and look up the 17 | * corresponding table. However, we try hard not to complain about what 18 | * is in essence the same problem more than once, and to complain about 19 | * it with respect to the correct column. 20 | * 21 | * The name of this file comes from the fact that the classic example of 22 | * the class ID/object ID/sub-ID notation is in the pg_depend catalog, 23 | * but we actually use this code to validate other tables that use a 24 | * similar convention, such as pg_description. 25 | * 26 | *------------------------------------------------------------------------- 27 | */ 28 | 29 | #include "postgres_fe.h" 30 | #include "pg_catcheck.h" 31 | 32 | typedef enum 33 | { 34 | DEPEND_COLUMN_STYLE_OBJID, /* pg_(sh)depend, referring side */ 35 | DEPEND_COLUMN_STYLE_REFOBJID, /* pg_(sh)depend, referenced side */ 36 | DEPEND_COLUMN_STYLE_OBJOID, /* pg_(sh)description, pg_(sh)seclabel */ 37 | EDB_DDLTIME_COLUMN_STYLE_OBJID /* edb_last_ddl_time(_shared) */ 38 | } depend_column_style; 39 | 40 | typedef struct check_depend_cache 41 | { 42 | depend_column_style style; 43 | bool is_broken; 44 | int database_result_column; 45 | int class_result_column; 46 | int object_result_column; 47 | int deptype_result_column; 48 | pgrhash *duplicate_owner_ht; 49 | } check_depend_cache; 50 | 51 | typedef struct class_id_mapping_type 52 | { 53 | char *oid; 54 | pg_catalog_table *tab; 55 | } class_id_mapping_type; 56 | 57 | /* 58 | * EnterpriseDB versions prior to 9.4 are expected to have a number of 59 | * dangling dependency entries, unless initialized with --no-redwood-compat. 60 | * We avoid complaining about these because (1) they're known and basically 61 | * harmless and (2) we don't want to give the misimpression of real 62 | * corruption. 63 | */ 64 | 65 | typedef struct exception_list 66 | { 67 | char *table_name; 68 | char *class; 69 | char *object; 70 | } exception_list; 71 | 72 | exception_list edb84_exception_list[] = { 73 | {"pg_depend", "1255", "877"}, 74 | {"pg_depend", "1255", "883"}, 75 | {"pg_depend", "1255", "1777"}, 76 | {"pg_depend", "1255", "1780"}, 77 | {"pg_depend", "1255", "2049"}, 78 | {"pg_depend", "2617", "2779"}, 79 | {"pg_depend", "2617", "2780"}, 80 | {NULL} 81 | }; 82 | 83 | exception_list edb90_exception_list[] = { 84 | {"pg_depend", "1255", "877"}, 85 | {"pg_depend", "1255", "883"}, 86 | {"pg_depend", "1255", "1777"}, 87 | {"pg_depend", "1255", "1780"}, 88 | {"pg_depend", "1255", "2049"}, 89 | {"pg_depend", "2617", "2779"}, 90 | {"pg_depend", "2617", "2780"}, 91 | {NULL} 92 | }; 93 | 94 | exception_list edb91_92_exception_list[] = { 95 | {"pg_depend", "1255", "877"}, 96 | {"pg_depend", "1255", "883"}, 97 | {"pg_depend", "1255", "1777"}, 98 | {"pg_depend", "1255", "1780"}, 99 | {"pg_depend", "1255", "2049"}, 100 | {"pg_depend", "2617", "2779"}, 101 | {"pg_depend", "2617", "2780"}, 102 | {"pg_description", "2617", "2779"}, 103 | {"pg_description", "2617", "2780"}, 104 | {NULL} 105 | }; 106 | 107 | exception_list edb93_exception_list[] = { 108 | {"pg_depend", "1255", "877"}, 109 | {"pg_depend", "1255", "883"}, 110 | {"pg_depend", "1255", "1777"}, 111 | {"pg_depend", "1255", "1780"}, 112 | {"pg_depend", "1255", "2049"}, 113 | {NULL} 114 | }; 115 | 116 | static bool class_id_mappings_attempted; 117 | static int num_class_id_mapping; 118 | static class_id_mapping_type *class_id_mapping; 119 | static char *pg_class_oid; 120 | static pg_catalog_table *pg_attribute_table; 121 | static pg_catalog_table *pg_type_table; 122 | 123 | static pg_catalog_table *lookup_class_id(char *oid); 124 | static void build_class_id_mappings(void); 125 | static bool table_key_is_oid(pg_catalog_table *tab); 126 | static check_depend_cache *build_depend_cache(pg_catalog_table *tab, 127 | pg_catalog_column *tabcol); 128 | static bool not_for_this_database(check_depend_cache *cache, 129 | pg_catalog_table *tab, pg_catalog_column *tabcol, 130 | int rownum); 131 | static depend_column_style get_style(char *table_name, char *column_name); 132 | static bool check_for_exception(char *table_name, char *classval, 133 | char *objval); 134 | 135 | /* 136 | * Set up to check a class ID. 137 | */ 138 | void 139 | prepare_to_check_dependency_class_id(pg_catalog_table *tab, 140 | pg_catalog_column *tabcol) 141 | { 142 | pg_catalog_table *pg_class = find_table_by_name("pg_class"); 143 | pg_catalog_column *pg_class_relname; 144 | pg_catalog_column *pg_class_relnamespace; 145 | 146 | /* 147 | * We need pg_class to figure out system catalog table OIDs. 148 | */ 149 | add_table_dependency(tab, pg_class); 150 | pg_class_relname = find_column_by_name(pg_class, "relname"); 151 | pg_class_relname->needed = true; 152 | pg_class_relnamespace = find_column_by_name(pg_class, "relnamespace"); 153 | pg_class_relnamespace->needed = true; 154 | 155 | /* We need this to determine whether the class ID can legally zero. */ 156 | if (get_style(tab->table_name, tabcol->name) == DEPEND_COLUMN_STYLE_OBJID) 157 | { 158 | pg_catalog_column *deptype = find_column_by_name(tab, "deptype"); 159 | 160 | deptype->needed = true; 161 | } 162 | } 163 | 164 | /* 165 | * Set up to check an object ID. 166 | */ 167 | void 168 | prepare_to_check_dependency_id(pg_catalog_table *tab, pg_catalog_column *tabcol) 169 | { 170 | pg_catalog_table *cattab; 171 | pg_catalog_column *classid; 172 | 173 | /* 174 | * Just as when checking a class ID, we need pg_class to map class IDs to 175 | * catalog tables. 176 | */ 177 | prepare_to_check_dependency_class_id(tab, tabcol); 178 | 179 | /* 180 | * All catalog tables that have an OID column must be loaded before we can 181 | * check dependency IDs. 182 | */ 183 | for (cattab = pg_catalog_tables; cattab->table_name != NULL; ++cattab) 184 | if (table_key_is_oid(cattab)) 185 | add_table_dependency(tab, cattab); 186 | 187 | /* Force the necessary classid column to be selected. */ 188 | switch (get_style(tab->table_name, tabcol->name)) 189 | { 190 | case DEPEND_COLUMN_STYLE_OBJID: 191 | case EDB_DDLTIME_COLUMN_STYLE_OBJID: 192 | classid = find_column_by_name(tab, "classid"); 193 | break; 194 | case DEPEND_COLUMN_STYLE_REFOBJID: 195 | classid = find_column_by_name(tab, "refclassid"); 196 | break; 197 | case DEPEND_COLUMN_STYLE_OBJOID: 198 | classid = find_column_by_name(tab, "classoid"); 199 | break; 200 | default: 201 | pgcc_log(PGCC_FATAL, "unexpected depend column style"); 202 | return; /* placate compiler */ 203 | } 204 | classid->needed = true; 205 | } 206 | 207 | /* 208 | * Set up to check a sub-ID. 209 | */ 210 | void 211 | prepare_to_check_dependency_subid(pg_catalog_table *tab, 212 | pg_catalog_column *tabcol) 213 | { 214 | pg_catalog_column *classid; 215 | pg_catalog_column *objectid; 216 | 217 | /* 218 | * Just as when checking a class ID, we need pg_class to map class IDs to 219 | * catalog tables. Specifically, we've got to be able to identify the OID 220 | * of pg_class itself, so that we know whether a non-zero sub-ID is legal. 221 | * Currently, that OID is the same in all server versions we support, so 222 | * we could hard-code it, but there's little harm in doing it this way: if 223 | * pg_class is too botched to interpret, the chances of anything else 224 | * making much sense are slim to none. 225 | */ 226 | prepare_to_check_dependency_class_id(tab, tabcol); 227 | 228 | /* We need the pg_attribute table to check sub-IDs. */ 229 | add_table_dependency(tab, find_table_by_name("pg_attribute")); 230 | 231 | /* Make sure we have the class and object IDs. */ 232 | switch (get_style(tab->table_name, tabcol->name)) 233 | { 234 | case DEPEND_COLUMN_STYLE_OBJID: 235 | case EDB_DDLTIME_COLUMN_STYLE_OBJID: 236 | classid = find_column_by_name(tab, "classid"); 237 | objectid = find_column_by_name(tab, "objid"); 238 | break; 239 | case DEPEND_COLUMN_STYLE_REFOBJID: 240 | classid = find_column_by_name(tab, "refclassid"); 241 | objectid = find_column_by_name(tab, "refobjid"); 242 | break; 243 | case DEPEND_COLUMN_STYLE_OBJOID: 244 | classid = find_column_by_name(tab, "classoid"); 245 | objectid = find_column_by_name(tab, "objoid"); 246 | break; 247 | default: 248 | pgcc_log(PGCC_FATAL, "unexpected depend column style"); 249 | return; /* placate compiler */ 250 | } 251 | classid->needed = true; 252 | objectid->needed = true; 253 | } 254 | 255 | /* 256 | * Check a class ID. 257 | * 258 | * This is basically just testing that the class ID is a system catalog 259 | * table that we know about and that's supposed to exists in this server 260 | * version, or else 0 if that's a legal value in this context. 261 | */ 262 | void 263 | check_dependency_class_id(pg_catalog_table *tab, pg_catalog_column *tabcol, 264 | int rownum) 265 | { 266 | char *val; 267 | check_depend_cache *cache; 268 | 269 | cache = build_depend_cache(tab, tabcol); 270 | if (cache->is_broken) 271 | return; 272 | if (not_for_this_database(cache, tab, tabcol, rownum)) 273 | return; 274 | 275 | val = PQgetvalue(tab->data, rownum, tabcol->result_column); 276 | 277 | /* 278 | * We normally expect that the class ID is non-zero, but "pin" depedencies 279 | * are an exception. 280 | */ 281 | if (strcmp(val, "0") == 0) 282 | { 283 | bool complain = true; 284 | 285 | if (cache->style == DEPEND_COLUMN_STYLE_OBJID) 286 | { 287 | char *deptype; 288 | 289 | deptype = PQgetvalue(tab->data, rownum, 290 | cache->deptype_result_column); 291 | 292 | if (strcmp(deptype, "p") == 0) 293 | complain = false; 294 | } 295 | 296 | if (complain) 297 | pgcc_report(tab, tabcol, rownum, "unexpected zero value\n"); 298 | return; 299 | } 300 | 301 | if (lookup_class_id(val) == NULL) 302 | { 303 | /* 304 | * Workaround for an old EnterpriseDB bug: 8.4 installed a bogus 305 | * dependency with reclassid 16722. 306 | */ 307 | if (remote_is_edb && remote_version <= 90000 && 308 | strcmp(val, "16722") == 0) 309 | { 310 | pgcc_log(PGCC_DEBUG, "ignoring reference to class ID 16722\n"); 311 | return; 312 | } 313 | pgcc_report(tab, tabcol, rownum, "not a system catalog OID\n"); 314 | } 315 | } 316 | 317 | /* 318 | * Check a dependency ID. 319 | * 320 | * We have to examine the class ID to figure out which table ought to 321 | * contain the indicated object. We then look up that table and check 322 | * whether the value appears in its OID column. 323 | */ 324 | void 325 | check_dependency_id(pg_catalog_table *tab, pg_catalog_column *tabcol, 326 | int rownum) 327 | { 328 | char *classval; 329 | char *val; 330 | pg_catalog_table *object_tab; 331 | check_depend_cache *cache; 332 | 333 | cache = build_depend_cache(tab, tabcol); 334 | if (cache->is_broken) 335 | return; 336 | if (not_for_this_database(cache, tab, tabcol, rownum)) 337 | return; 338 | 339 | /* 340 | * Check for multiple owner dependencies for the same object. 341 | * 342 | * FIXME: Current pg_catcheck design don't support table-level checks, 343 | * all checks are column-level. We might want to re-architect this at 344 | * some point in the future. For now, we check this here. 345 | */ 346 | if (cache->duplicate_owner_ht != NULL && 347 | strcmp(PQgetvalue(tab->data, rownum, 348 | PQfnumber(tab->data, "deptype")), "o") == 0 && 349 | pgrhash_insert(cache->duplicate_owner_ht, rownum) != -1) 350 | pgcc_report(tab, NULL, rownum, "duplicate owner dependency\n"); 351 | 352 | /* Fetch the class ID and object ID. */ 353 | classval = PQgetvalue(tab->data, rownum, cache->class_result_column); 354 | val = PQgetvalue(tab->data, rownum, tabcol->result_column); 355 | 356 | /* If the class ID is zero, the object ID should be zero as well. */ 357 | if (strcmp(classval, "0") == 0) 358 | { 359 | if (strcmp(val, "0") != 0) 360 | pgcc_report(tab, tabcol, rownum, 361 | "class ID is zero, but object ID is non-zero\n"); 362 | return; 363 | } 364 | 365 | /* Find the correct table. */ 366 | object_tab = lookup_class_id(classval); 367 | if (object_tab == NULL || object_tab->ht == NULL) 368 | return; 369 | 370 | /* 371 | * Workaround for EnterpriseDB bug: EnterpriseDB versions prior to 9.4 372 | * would sometimes create bogus dependencies on type ID 0. Since we'll 373 | * never create a real type with that OID, this was (as far as we know) 374 | * harmless, so just ignore them. 375 | */ 376 | if (pg_type_table == NULL) 377 | pg_type_table = find_table_by_name("pg_type"); 378 | if (remote_version < 90400 && remote_is_edb && object_tab == pg_type_table 379 | && strcmp(val, "0") == 0) 380 | { 381 | pgcc_log(PGCC_DEBUG, 382 | "ignoring reference to pg_type OID 0\n"); 383 | return; 384 | } 385 | 386 | /* 387 | * lookup_class_id() will only return tables where the only key column is 388 | * the OID column. So this is safe. 389 | */ 390 | if (pgrhash_get(object_tab->ht, &val) == -1 && 391 | !check_for_exception(tab->table_name, classval, val)) 392 | pgcc_report(tab, tabcol, rownum, "no matching entry in %s\n", 393 | object_tab->table_name); 394 | } 395 | 396 | /* 397 | * Check a dependency sub-ID. 398 | * 399 | * This should always be zero except in the where the class ID points to 400 | * pg_class. In that case, should be able to find in 401 | * pg_attribute. The object ID will appear in attrelid and the sub-ID in attnum. 402 | */ 403 | void 404 | check_dependency_subid(pg_catalog_table *tab, pg_catalog_column *tabcol, 405 | int rownum) 406 | { 407 | char *classval; 408 | char *vals[2]; 409 | check_depend_cache *cache; 410 | 411 | cache = build_depend_cache(tab, tabcol); 412 | if (cache->is_broken) 413 | return; 414 | if (not_for_this_database(cache, tab, tabcol, rownum)) 415 | return; 416 | 417 | /* 418 | * We find pg_attribute on our first trip through this function and avoid 419 | * repeating the lookup thereafter using a global variable. 420 | */ 421 | if (pg_attribute_table == NULL) 422 | pg_attribute_table = find_table_by_name("pg_attribute"); 423 | 424 | /* Fetch the class ID, object ID, and sub-ID. */ 425 | classval = PQgetvalue(tab->data, rownum, cache->class_result_column); 426 | vals[0] = PQgetvalue(tab->data, rownum, cache->object_result_column); 427 | vals[1] = PQgetvalue(tab->data, rownum, tabcol->result_column); 428 | 429 | /* Sub-ID is always permitted to be zero. */ 430 | if (strcmp(vals[1], "0") == 0) 431 | return; 432 | 433 | /* 434 | * If we get here, the sub-ID is non-zero. Therefore, the class ID should 435 | * definitely point to pg_class; if it does not, that's an inconsistency. 436 | * If it does point to pg_class, then a matching pg_attribute row should 437 | * exist. 438 | */ 439 | if (strcmp(classval, pg_class_oid) != 0) 440 | pgcc_report(tab, tabcol, rownum, 441 | "class ID %s is not pg_class, but sub-ID is non-zero\n", 442 | classval); 443 | else if (pg_attribute_table->ht) /* We might have failed to read it. */ 444 | { 445 | if (pgrhash_get(pg_attribute_table->ht, vals) == -1) 446 | pgcc_report(tab, tabcol, rownum, "no matching entry in %s\n", 447 | pg_attribute_table->table_name); 448 | } 449 | } 450 | 451 | /* 452 | * Given a text-form OID found in an objid or refobjid table, search for a 453 | * corresponding catalog table. 454 | * 455 | * NB: We could make this more efficient by teaching build_class_id_mappings() 456 | * to sort the array, and then using binary search. 457 | */ 458 | static pg_catalog_table * 459 | lookup_class_id(char *oid) 460 | { 461 | int i; 462 | 463 | Assert(class_id_mappings_attempted && class_id_mapping != NULL); 464 | 465 | /* For LargeObjectRelationId, substitute LargeObjectMetadataOidIndexId. */ 466 | if (strcmp(oid, "2613") == 0) 467 | oid = "2995"; 468 | 469 | for (i = 0; i < num_class_id_mapping; ++i) 470 | if (strcmp(class_id_mapping[i].oid, oid) == 0) 471 | return class_id_mapping[i].tab; 472 | 473 | return NULL; 474 | } 475 | 476 | /* 477 | * Build a set of mappings from text-form OIDs which PQgetvalue might hand 478 | * back for an objid or refobjid column to pg_catalog_table objects. 479 | */ 480 | static void 481 | build_class_id_mappings(void) 482 | { 483 | int map_used = 0; 484 | int map_size = 10; 485 | class_id_mapping_type *map; 486 | pg_catalog_table *pg_class_tab; 487 | int oid_column; 488 | int relnamespace_column; 489 | int relname_column; 490 | int ntups = 0; 491 | int i; 492 | 493 | /* 494 | * If we fall out of this function due to some kind of unexpected error, 495 | * we do not want to retry, as we'll just hit the same problem the second 496 | * time. Set a flag so that callers can detect this case. 497 | */ 498 | class_id_mappings_attempted = true; 499 | 500 | /* Find the pg_class table. */ 501 | pg_class_tab = find_table_by_name("pg_class"); 502 | if (pg_class_tab->data != NULL) 503 | ntups = PQntuples(pg_class_tab->data); 504 | if (ntups == 0) 505 | { 506 | pgcc_log(PGCC_WARNING, 507 | "can't identify class IDs: no pg_class data\n"); 508 | return; 509 | } 510 | 511 | /* Find the pg_class columns we need. */ 512 | oid_column = PQfnumber(pg_class_tab->data, "oid"); 513 | relnamespace_column = PQfnumber(pg_class_tab->data, "relnamespace"); 514 | relname_column = PQfnumber(pg_class_tab->data, "relname"); 515 | if (oid_column == -1 || relname_column == -1 || relnamespace_column == -1) 516 | { 517 | pgcc_log(PGCC_WARNING, 518 | "can't identify class IDs: missing pg_class columns\n"); 519 | return; 520 | } 521 | 522 | /* Initialize map data structure. */ 523 | map = pg_malloc(sizeof(class_id_mapping_type) * map_size); 524 | 525 | /* Scan pg_class rows to construct mapping table. */ 526 | for (i = 0; i < ntups; ++i) 527 | { 528 | pg_catalog_table *tab; 529 | char *relnamespace; 530 | char *relname; 531 | 532 | /* Skip tables that are not part of the pg_catalog namespace. */ 533 | relnamespace = PQgetvalue(pg_class_tab->data, i, relnamespace_column); 534 | if (strcmp(relnamespace, "11") != 0) 535 | continue; 536 | 537 | /* See if it's a catalog table we know about. */ 538 | relname = PQgetvalue(pg_class_tab->data, i, relname_column); 539 | for (tab = pg_catalog_tables; tab->table_name != NULL; ++tab) 540 | { 541 | /* Skip table if name does not match. */ 542 | if (strcmp(tab->table_name, relname) != 0) 543 | continue; 544 | 545 | /* Ignore matching table if it's not available. */ 546 | if (!tab->available) 547 | break; 548 | 549 | /* Ignore matching table if not keyed by OID. */ 550 | if (!table_key_is_oid(tab)) 551 | break; 552 | 553 | /* Increase map space if required. */ 554 | if (map_used >= map_size) 555 | { 556 | map_size *= 2; 557 | map = pg_realloc(map, 558 | sizeof(class_id_mapping_type) * map_size); 559 | } 560 | 561 | /* Create map entry. */ 562 | map[map_used].oid = PQgetvalue(pg_class_tab->data, i, oid_column); 563 | map[map_used].tab = tab; 564 | 565 | /* 566 | * Special bookkeeping for pg_class itself, due to its role in 567 | * checking sub-IDs. 568 | */ 569 | if (pg_class_tab == tab) 570 | pg_class_oid = map[map_used].oid; 571 | 572 | ++map_used; 573 | break; 574 | } 575 | } 576 | 577 | /* Avoid installing a bogus empty mapping. */ 578 | if (map_used == 0) 579 | { 580 | pgcc_log(PGCC_WARNING, 581 | "can't identify class IDs: no catalog tables found in pg_class\n"); 582 | return; 583 | } 584 | 585 | /* 586 | * Avoid installing mapping that doesn't include pg_class itself. 587 | * 588 | * This is probably a good sanity check on general principal, but what 589 | * truly makes it necessary is that sub-ID verification needs 590 | * pg_class_oid. 591 | */ 592 | if (pg_class_oid == NULL) 593 | { 594 | pgcc_log(PGCC_WARNING, 595 | "can't identify class IDs: pg_class not found in pg_class\n"); 596 | return; 597 | } 598 | 599 | /* Install new mapping table. */ 600 | class_id_mapping = map; 601 | num_class_id_mapping = map_used; 602 | } 603 | 604 | /* 605 | * Is oid the only key column for this table? 606 | */ 607 | static bool 608 | table_key_is_oid(pg_catalog_table *tab) 609 | { 610 | bool has_oid_key = false; 611 | bool has_other_key = false; 612 | pg_catalog_column *tabcol; 613 | 614 | for (tabcol = tab->cols; tabcol->name != NULL; ++tabcol) 615 | { 616 | if (!tabcol->is_key_column) 617 | continue; 618 | if (strcmp(tabcol->name, "oid") == 0) 619 | has_oid_key = true; 620 | else 621 | has_other_key = true; 622 | } 623 | 624 | return has_oid_key && !has_other_key; 625 | } 626 | 627 | /* 628 | * Cache per-column dependency checking information, basically column indexes 629 | * into the PGresult structure so that we can quickly find the class ID for 630 | * an object ID and the class and object ID for a sub-ID. 631 | */ 632 | static check_depend_cache * 633 | build_depend_cache(pg_catalog_table *tab, pg_catalog_column *tabcol) 634 | { 635 | check_depend_cache *cache; 636 | bool columns_missing = false; 637 | 638 | /* If we've already built the cache, just return the existing data. */ 639 | if (tabcol->check_private != NULL) 640 | return tabcol->check_private; 641 | 642 | /* Create and initialize the cache object. */ 643 | cache = pg_malloc0(sizeof(check_depend_cache)); 644 | cache->style = get_style(tab->table_name, tabcol->name); 645 | cache->is_broken = false; 646 | switch (cache->style) 647 | { 648 | case DEPEND_COLUMN_STYLE_OBJID: 649 | /* special case for pg_shdepend */ 650 | cache->database_result_column = PQfnumber(tab->data, "dbid"); 651 | cache->class_result_column = PQfnumber(tab->data, "classid"); 652 | cache->object_result_column = PQfnumber(tab->data, "objid"); 653 | break; 654 | case EDB_DDLTIME_COLUMN_STYLE_OBJID: 655 | cache->database_result_column = -1; 656 | cache->class_result_column = PQfnumber(tab->data, "classid"); 657 | cache->object_result_column = PQfnumber(tab->data, "objid"); 658 | break; 659 | case DEPEND_COLUMN_STYLE_REFOBJID: 660 | cache->database_result_column = -1; 661 | cache->class_result_column = PQfnumber(tab->data, "refclassid"); 662 | cache->object_result_column = PQfnumber(tab->data, "refobjid"); 663 | break; 664 | case DEPEND_COLUMN_STYLE_OBJOID: 665 | cache->database_result_column = -1; 666 | cache->class_result_column = PQfnumber(tab->data, "classoid"); 667 | cache->object_result_column = PQfnumber(tab->data, "objoid"); 668 | break; 669 | default: 670 | pgcc_log(PGCC_FATAL, "unexpected depend column style"); 671 | break; 672 | } 673 | 674 | /* Verify that PQfnumber() worked as expected. */ 675 | if (cache->class_result_column == -1) 676 | columns_missing = true; 677 | if (cache->object_result_column == -1) 678 | columns_missing = true; 679 | 680 | /* Look up the column number for the deptype column, if expected. */ 681 | if (cache->style == DEPEND_COLUMN_STYLE_OBJID) 682 | { 683 | cache->deptype_result_column = PQfnumber(tab->data, "deptype"); 684 | if (cache->deptype_result_column == -1) 685 | columns_missing = true; 686 | } 687 | 688 | /* 689 | * If we failed to find the relevant columns, then mark the cache as 690 | * broken, which will cause the individual rows not to be checked. 691 | * Otherwise, we'd end up having to emit a message for every row, which 692 | * would be too much chatter. 693 | */ 694 | if (columns_missing) 695 | { 696 | pgcc_log(PGCC_WARNING, 697 | "can't identify class IDs: columns missing from %s\n", 698 | tab->table_name); 699 | cache->is_broken = true; 700 | } 701 | 702 | /* 703 | * For the convenience of our callers, we also try to build the global 704 | * cache data here, namely the mappings from class IDs to pg_catalog_table 705 | * objects. Unlike the data stored in the cache object, these mappings 706 | * can be reused across all columns where we check dependencies. 707 | */ 708 | if (!class_id_mappings_attempted) 709 | build_class_id_mappings(); 710 | 711 | /* 712 | * If we failed to build the mappings, either now or previously, mark the 713 | * cache object as broken, to avoid log chatter for each separate row. 714 | */ 715 | if (class_id_mapping == NULL) 716 | cache->is_broken = true; 717 | 718 | /* 719 | * If needed, create hash table for duplicate-owner-dependency cheecking. 720 | */ 721 | if (!cache->is_broken && cache->database_result_column != -1 && 722 | cache->deptype_result_column != -1) 723 | { 724 | int keycols[3]; 725 | 726 | keycols[0] = cache->database_result_column; 727 | keycols[1] = cache->class_result_column; 728 | keycols[2] = cache->object_result_column; 729 | cache->duplicate_owner_ht = pgrhash_create(tab->data, 3, keycols); 730 | } 731 | 732 | /* We're done. */ 733 | tabcol->check_private = cache; 734 | return cache; 735 | } 736 | 737 | /* 738 | * Determine whether this dependency should be ignored because it's not 739 | * part of this database. This will only ever return true when we're checking 740 | * a table that has a dbid column, which currently means just pg_shdepend. 741 | */ 742 | static bool 743 | not_for_this_database(check_depend_cache *cache, pg_catalog_table *tab, 744 | pg_catalog_column *tabcol, int rownum) 745 | { 746 | char *dbval; 747 | 748 | /* If there's no dbid column, then it's part of this database. */ 749 | if (cache->database_result_column == -1) 750 | return false; 751 | 752 | /* Look up the value in that column. */ 753 | dbval = PQgetvalue(tab->data, rownum, cache->database_result_column); 754 | 755 | /* 0 means it's a global object, so it's fine to check it here. */ 756 | if (strcmp(dbval, "0") == 0) 757 | return false; 758 | 759 | /* 760 | * If we don't know the database OID, skip the check, to avoid bogus 761 | * complaints. 762 | */ 763 | if (database_oid == NULL) 764 | return true; 765 | 766 | /* Straightforward comparison. */ 767 | return strcmp(database_oid, dbval) != 0; 768 | } 769 | 770 | /* 771 | * Determine which naming style applies to this table and column. 772 | * 773 | * There are three naming conventions that are used for references to objects 774 | * in arbitrary catalogs. pg_depend and pg_shdepend use classid/objid/objsubid 775 | * for one side of the dependency and refclassid/refobjid/refobjsubid for the 776 | * other. Other tables that contain similar information, such as 777 | * pg_description, pg_shdescription, pg_seclabel, and pg_shseclabel, use 778 | * objoid/classoid/objsubid. 779 | * 780 | * edb_last_ddl_time and edb_last_ddl_time_shared use objid/classid/objsubid 781 | * like pg_depend but need a separate enum value because they do not have a 782 | * deptype column, which prepare_to_check_dependency_class_id() would expect 783 | * if we used DEPEND_COLUMN_STYLE_OBJID. 784 | */ 785 | static depend_column_style 786 | get_style(char *table_name, char *column_name) 787 | { 788 | if (strncmp(column_name, "ref", 3) == 0) 789 | return DEPEND_COLUMN_STYLE_REFOBJID; 790 | else if (strstr(table_name, "depend")) 791 | return DEPEND_COLUMN_STYLE_OBJID; 792 | else if (strstr(table_name, "edb_last_ddl_time")) 793 | return EDB_DDLTIME_COLUMN_STYLE_OBJID; 794 | else 795 | return DEPEND_COLUMN_STYLE_OBJOID; 796 | } 797 | 798 | /* 799 | * Check whether a detected inconsistency is one that we were expecting. 800 | */ 801 | static bool 802 | check_for_exception(char *table_name, char *classval, char *objval) 803 | { 804 | exception_list *exc; 805 | 806 | if (!remote_is_edb || remote_version >= 90400) 807 | return false; 808 | 809 | if (remote_version >= 90300) 810 | exc = edb93_exception_list; 811 | else if (remote_version >= 90100) 812 | exc = edb91_92_exception_list; 813 | else if (remote_version >= 90000) 814 | exc = edb90_exception_list; 815 | else 816 | exc = edb84_exception_list; 817 | 818 | while (exc->table_name != NULL) 819 | { 820 | if (strcmp(exc->table_name, table_name) == 0 && 821 | strcmp(exc->class, classval) == 0 && 822 | strcmp(exc->object, objval) == 0) 823 | { 824 | pgcc_log(PGCC_DEBUG, 825 | "ignoring reference to class ID %s object ID %s in %s\n", 826 | exc->class, exc->object, exc->table_name); 827 | return true; 828 | } 829 | ++exc; 830 | } 831 | 832 | return false; 833 | } 834 | -------------------------------------------------------------------------------- /check_oids.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * check_oids.c 4 | * 5 | * Many PostgreSQL system catalogs have OID, OID array, or OID vector 6 | * columns where each OID identifies a row in some other catalog table. 7 | * Although not marked as such, these are essentially foreign key 8 | * relationships. The code in this file aims to validate that every 9 | * object referenced in such a column actually exists. 10 | * 11 | *------------------------------------------------------------------------- 12 | */ 13 | 14 | #include "postgres_fe.h" 15 | #include "pg_catcheck.h" 16 | 17 | static void do_oid_check(pg_catalog_table *tab, pg_catalog_column *tabcol, 18 | int rownum, pg_catalog_check_oid * check_oid, 19 | pg_catalog_table *reftab, char *value); 20 | 21 | /* 22 | * Set up for an OID referential integrity check. 23 | * 24 | * We simply need to make sure that the referenced table will be available. 25 | */ 26 | void 27 | prepare_to_check_oid_reference(pg_catalog_table *tab, 28 | pg_catalog_column *tabcol) 29 | { 30 | pg_catalog_check_oid *check_oid = tabcol->check; 31 | pg_catalog_table *reftab; 32 | 33 | reftab = find_table_by_name(check_oid->oid_references_table); 34 | add_table_dependency(tab, reftab); 35 | } 36 | 37 | /* 38 | * Perform an OID referential integrity check. 39 | */ 40 | void 41 | check_oid_reference(pg_catalog_table *tab, pg_catalog_column *tabcol, 42 | int rownum) 43 | { 44 | pg_catalog_check_oid *check_oid = tabcol->check; 45 | char *val = PQgetvalue(tab->data, rownum, tabcol->result_column); 46 | pg_catalog_table *reftab; 47 | 48 | /* 49 | * Find the hash table we need in order to perform the check. 50 | * 51 | * Since find_table_by_name is O(n) in the number of catalog tables being 52 | * checked, we cache the result, so that we only need to do that work 53 | * once. 54 | */ 55 | if (tabcol->check_private == NULL) 56 | { 57 | reftab = find_table_by_name(check_oid->oid_references_table); 58 | tabcol->check_private = reftab; 59 | } 60 | else 61 | reftab = tabcol->check_private; 62 | 63 | /* 64 | * The table might not be available in this server version, or we might 65 | * have failed to read it. There's actually one real case where the 66 | * referenced table was adding later than referring table: pg_largeobject 67 | * has existed for a long time, but pg_largeobject_metadata is newer. 68 | */ 69 | if (!reftab->ht) 70 | return; 71 | 72 | switch (check_oid->type) 73 | { 74 | case CHECK_OID_REFERENCE: 75 | 76 | /* 77 | * Simple OID reference. Easy! 78 | * 79 | * We don't use do do_oid_check() here because the error message 80 | * is a little different in this case. 81 | */ 82 | { 83 | if (check_oid->zero_oid_ok && strcmp(val, "0") == 0) 84 | return; 85 | if (pgrhash_get(reftab->ht, &val) == -1) 86 | pgcc_report(tab, tabcol, rownum, 87 | "no matching entry in %s\n", reftab->table_name); 88 | } 89 | break; 90 | 91 | case CHECK_OID_VECTOR_REFERENCE: 92 | /* Space-separated list of values. */ 93 | { 94 | char *s = val; 95 | char buf[32]; 96 | 97 | for (;;) 98 | { 99 | /* Find next word boundary. */ 100 | while (*s != '\0' && *s != ' ') 101 | ++s; 102 | 103 | /* If it's the last word, we're done here! */ 104 | if (*s == '\0') 105 | { 106 | if (s > val) 107 | { 108 | /* If last entry is non-empty, check it. */ 109 | do_oid_check(tab, tabcol, rownum, check_oid, 110 | reftab, val); 111 | } 112 | break; 113 | } 114 | 115 | /* Make a copy of the data we need to check. */ 116 | if (s - val >= sizeof buf) 117 | { 118 | /* OIDs can't be this long. */ 119 | pgcc_report(tab, tabcol, rownum, 120 | "contains a token of %ld characters\n", s - val); 121 | return; 122 | } 123 | memcpy(buf, val, s - val); 124 | buf[s - val] = '\0'; 125 | 126 | /* Check it and move ahead one character. */ 127 | do_oid_check(tab, tabcol, rownum, check_oid, reftab, buf); 128 | val = ++s; 129 | } 130 | } 131 | break; 132 | 133 | case CHECK_OID_ARRAY_REFERENCE: 134 | /* Opening curly brace, comma-separated values, closing brace. */ 135 | { 136 | char *s = val; 137 | char buf[32]; 138 | bool bad = false; 139 | 140 | /* Allow a completely empty field. */ 141 | if (*s == '\0') 142 | break; 143 | 144 | /* Otherwise, expect the opening delimeter. */ 145 | if (*s == '{') 146 | val = ++s; 147 | else 148 | bad = true; 149 | 150 | while (!bad) 151 | { 152 | /* Find next delimeter. */ 153 | while (*s != '\0' && *s != ',' && *s != '}') 154 | ++s; 155 | 156 | /* 157 | * If we hit '\0' before '}', that's bad; and if we hit 158 | * two consecutive delimeters, that's also bad. 159 | */ 160 | if (val == s || *s == '\0') 161 | { 162 | bad = true; 163 | break; 164 | } 165 | 166 | /* Make a copy of the data we need to check. */ 167 | if (s - val >= sizeof buf) 168 | { 169 | /* OIDs can't be this long. */ 170 | pgcc_report(tab, tabcol, rownum, 171 | "contains a token of %ld characters\n", s - val); 172 | return; 173 | } 174 | memcpy(buf, val, s - val); 175 | buf[s - val] = '\0'; 176 | 177 | /* Check it. */ 178 | if (!check_oid->zero_oid_ok || strcmp(buf, "0") != 0) 179 | do_oid_check(tab, tabcol, rownum, check_oid, reftab, 180 | buf); 181 | 182 | /* Expect end of string if at '}'. */ 183 | if (*s == '}') 184 | { 185 | val = ++s; 186 | if (*s != '\0') 187 | bad = true; 188 | break; 189 | } 190 | 191 | /* Skip comma and continue. */ 192 | val = ++s; 193 | } 194 | 195 | if (bad) 196 | pgcc_report(tab, tabcol, rownum, "not a valid 1-D array"); 197 | } 198 | break; 199 | 200 | default: 201 | Assert(false); 202 | break; 203 | } 204 | } 205 | 206 | /* 207 | * Check one of possibly several OIDs found in a single column. 208 | */ 209 | static void 210 | do_oid_check(pg_catalog_table *tab, pg_catalog_column *tabcol, int rownum, 211 | pg_catalog_check_oid * check_oid, 212 | pg_catalog_table *reftab, char *value) 213 | { 214 | if (check_oid->zero_oid_ok && strcmp(value, "0") == 0) 215 | return; 216 | if (pgrhash_get(reftab->ht, &value) == -1) 217 | pgcc_report(tab, tabcol, rownum, 218 | "\"%s\" not found in %s\n", value, reftab->table_name); 219 | } 220 | -------------------------------------------------------------------------------- /compat.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * compat.c 4 | * compatibility definitions for older server versions 5 | * 6 | *------------------------------------------------------------------------- 7 | */ 8 | 9 | #include "postgres_fe.h" 10 | 11 | #include "pg_catcheck.h" 12 | 13 | #if PG_VERSION_NUM < 90300 14 | 15 | /* 9.3 and higher have this in fe_memutils.c */ 16 | void * 17 | pg_malloc(size_t size) 18 | { 19 | void *tmp; 20 | 21 | /* Avoid unportable behavior of malloc(0) */ 22 | if (size == 0) 23 | size = 1; 24 | tmp = malloc(size); 25 | if (!tmp) 26 | { 27 | fprintf(stderr, _("out of memory\n")); 28 | exit(EXIT_FAILURE); 29 | } 30 | return tmp; 31 | } 32 | 33 | /* 9.3 and higher have this in fe_memutils.c */ 34 | void * 35 | pg_malloc0(size_t size) 36 | { 37 | void *tmp; 38 | 39 | tmp = pg_malloc(size); 40 | MemSet(tmp, 0, size); 41 | return tmp; 42 | } 43 | 44 | /* 9.3 and higher have this in fe_memutils.c */ 45 | void * 46 | pg_realloc(void *ptr, size_t size) 47 | { 48 | void *tmp; 49 | 50 | /* Avoid unportable behavior of realloc(NULL, 0) */ 51 | if (ptr == NULL && size == 0) 52 | size = 1; 53 | tmp = realloc(ptr, size); 54 | if (!tmp) 55 | { 56 | fprintf(stderr, _("out of memory\n")); 57 | exit(EXIT_FAILURE); 58 | } 59 | return tmp; 60 | } 61 | 62 | /* 9.3 and higher have this in fe_memutils.c */ 63 | char * 64 | pg_strdup(const char *in) 65 | { 66 | char *tmp; 67 | 68 | if (!in) 69 | { 70 | fprintf(stderr, 71 | _("cannot duplicate null pointer (internal error)\n")); 72 | exit(EXIT_FAILURE); 73 | } 74 | tmp = strdup(in); 75 | if (!tmp) 76 | { 77 | fprintf(stderr, _("out of memory\n")); 78 | exit(EXIT_FAILURE); 79 | } 80 | return tmp; 81 | } 82 | 83 | /* 9.3 and higher have this in fe_memutils.c */ 84 | void 85 | pg_free(void *ptr) 86 | { 87 | if (ptr != NULL) 88 | free(ptr); 89 | } 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /compat.h: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * compat.h 4 | * 5 | * Compatibility with older PostgreSQL source trees. 6 | * 7 | *------------------------------------------------------------------------- 8 | */ 9 | 10 | #ifndef COMPAT_H 11 | #define COMPAT_H 12 | 13 | #if PG_VERSION_NUM < 90300 14 | 15 | /* Just disable assertions for builds against older source trees. */ 16 | #define Assert(p) 17 | 18 | /* Convenience routines for frontend memory allocation were added in 9.3. */ 19 | extern char *pg_strdup(const char *in); 20 | extern void *pg_malloc(size_t size); 21 | extern void *pg_malloc0(size_t size); 22 | extern void *pg_realloc(void *pointer, size_t size); 23 | extern void pg_free(void *pointer); 24 | 25 | #endif /* PG_VERSION_NUM < 90300 */ 26 | 27 | #if PG_VERSION_NUM < 90100 28 | 29 | /* 30 | * PG_PRINTF_ATTRIBUTE is essential for suppressing compiler warning from 31 | * printf-like functions, but it wasn't added until 9.1. 32 | */ 33 | #ifdef WIN32 34 | #define PG_PRINTF_ATTRIBUTE gnu_printf 35 | #else 36 | #define PG_PRINTF_ATTRIBUTE printf 37 | #endif 38 | 39 | #endif 40 | 41 | #endif /* COMPAT_H */ 42 | -------------------------------------------------------------------------------- /definitions.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * definitions.c 4 | * 5 | * This file defines the data structures that drive our checking 6 | * strategies. We define the names of each column, the versions to which 7 | * it applies, whether or not it forms part of the table's key, whether it 8 | * should be included in diagnostics regarding that table, and, if 9 | * applicable, the type of check that should be performed on it. 10 | * 11 | * Some columns, such as OID columns, are included even though no check 12 | * is defined. This is because they're part of the key: some other table 13 | * might contain that OID, and we'll need to look it up in the referenced 14 | * table. Note that we don't bother defining the key for all tables that 15 | * have one; even if a table has a unique key, there's no point in 16 | * building a hash table to allow lookups into that table by key unless 17 | * we require the ability to perform suchh lookups. 18 | * 19 | *------------------------------------------------------------------------- 20 | */ 21 | 22 | #include "postgres_fe.h" 23 | 24 | #include "pg_catcheck.h" 25 | 26 | static struct pg_catalog_check_oid check_am_oid = 27 | {CHECK_OID_REFERENCE, false, "pg_am"}; 28 | static struct pg_catalog_check_oid check_am_optional_oid = 29 | {CHECK_OID_REFERENCE, true, "pg_am"}; 30 | static struct pg_catalog_check_oid check_attnum_value = 31 | {CHECK_ATTNUM}; 32 | static struct pg_catalog_check_oid check_authid_oid = 33 | {CHECK_OID_REFERENCE, false, "pg_authid"}; 34 | static struct pg_catalog_check_oid check_authid_oid_array_zero_ok = 35 | {CHECK_OID_ARRAY_REFERENCE, true, "pg_authid"}; 36 | static struct pg_catalog_check_oid check_authid_optional_oid = 37 | {CHECK_OID_REFERENCE, true, "pg_authid"}; 38 | static struct pg_catalog_check_oid check_class_oid = 39 | {CHECK_OID_REFERENCE, false, "pg_class"}; 40 | static struct pg_catalog_check_oid check_class_oid_array = 41 | {CHECK_OID_ARRAY_REFERENCE, false, "pg_class"}; 42 | static struct pg_catalog_check_oid check_class_optional_oid = 43 | {CHECK_OID_REFERENCE, true, "pg_class"}; 44 | static struct pg_catalog_check_oid check_constraint_oid = 45 | {CHECK_OID_REFERENCE, false, "pg_constraint"}; 46 | static struct pg_catalog_check_oid check_collation_optional_oid = 47 | {CHECK_OID_REFERENCE, true, "pg_collation"}; 48 | static struct pg_catalog_check_oid check_collation_optional_oid_vector = 49 | {CHECK_OID_VECTOR_REFERENCE, true, "pg_collation"}; 50 | static struct pg_catalog_check_oid check_constraint_optional_oid = 51 | {CHECK_OID_REFERENCE, true, "pg_constraint"}; 52 | static struct pg_catalog_check_oid check_database_optional_oid = 53 | {CHECK_OID_REFERENCE, true, "pg_database"}; 54 | static struct pg_catalog_check check_dependency_id_value = 55 | {CHECK_DEPENDENCY_ID}; 56 | static struct pg_catalog_check check_dependency_class_id_value = 57 | {CHECK_DEPENDENCY_CLASS_ID}; 58 | static struct pg_catalog_check check_dependency_subid_value = 59 | {CHECK_DEPENDENCY_SUBID}; 60 | static struct pg_catalog_check_oid check_edb_partdef = 61 | {CHECK_OID_REFERENCE, false, "edb_partdef"}; 62 | static struct pg_catalog_check_oid check_edb_partition_optional_oid = 63 | {CHECK_OID_REFERENCE, true, "edb_partition"}; 64 | static struct pg_catalog_check_oid check_foreign_data_wrapper_oid = 65 | {CHECK_OID_REFERENCE, false, "pg_foreign_data_wrapper"}; 66 | static struct pg_catalog_check_oid check_foreign_server_oid = 67 | {CHECK_OID_REFERENCE, false, "pg_foreign_server"}; 68 | static struct pg_catalog_check_oid check_foreign_server_optional_oid = 69 | {CHECK_OID_REFERENCE, true, "pg_foreign_server"}; 70 | static struct pg_catalog_check_oid check_index_optional_oid = 71 | {CHECK_OID_REFERENCE, true, "pg_index"}; 72 | static struct pg_catalog_check_oid check_language_oid = 73 | {CHECK_OID_REFERENCE, false, "pg_language"}; 74 | static struct pg_catalog_check_oid check_largeobject_metadata_oid = 75 | {CHECK_OID_REFERENCE, false, "pg_largeobject_metadata"}; 76 | static struct pg_catalog_check_oid check_namespace_oid = 77 | {CHECK_OID_REFERENCE, false, "pg_namespace"}; 78 | static struct pg_catalog_check_oid check_namespace_optional_oid = 79 | {CHECK_OID_REFERENCE, true, "pg_namespace"}; 80 | static struct pg_catalog_check_oid check_opclass_oid = 81 | {CHECK_OID_REFERENCE, false, "pg_opclass"}; 82 | static struct pg_catalog_check_oid check_opclass_oid_vector = 83 | {CHECK_OID_VECTOR_REFERENCE, false, "pg_opclass"}; 84 | static struct pg_catalog_check_oid check_operator_oid = 85 | {CHECK_OID_REFERENCE, false, "pg_operator"}; 86 | static struct pg_catalog_check_oid check_operator_optional_oid = 87 | {CHECK_OID_REFERENCE, true, "pg_operator"}; 88 | static struct pg_catalog_check_oid check_operator_oid_array = 89 | {CHECK_OID_ARRAY_REFERENCE, false, "pg_operator"}; 90 | static struct pg_catalog_check_oid check_opfamily_oid = 91 | {CHECK_OID_REFERENCE, false, "pg_opfamily"}; 92 | static struct pg_catalog_check_oid check_opfamily_optional_oid = 93 | {CHECK_OID_REFERENCE, true, "pg_opfamily"}; 94 | static struct pg_catalog_check_oid check_proc_oid = 95 | {CHECK_OID_REFERENCE, false, "pg_proc"}; 96 | static struct pg_catalog_check_oid check_proc_optional_oid = 97 | {CHECK_OID_REFERENCE, true, "pg_proc"}; 98 | static struct pg_catalog_check_oid check_profile_oid = 99 | {CHECK_OID_REFERENCE, true, "edb_profile"}; 100 | static struct pg_catalog_check_oid check_relnatts_value = 101 | {CHECK_RELNATTS}; 102 | static struct pg_catalog_check_oid check_tablespace_oid = 103 | {CHECK_OID_REFERENCE, false, "pg_tablespace"}; 104 | static struct pg_catalog_check_oid check_tablespace_optional_oid = 105 | {CHECK_OID_REFERENCE, true, "pg_tablespace"}; 106 | static struct pg_catalog_check_oid check_ts_config_oid = 107 | {CHECK_OID_REFERENCE, true, "pg_ts_config"}; 108 | static struct pg_catalog_check_oid check_ts_dict_oid = 109 | {CHECK_OID_REFERENCE, true, "pg_ts_dict"}; 110 | static struct pg_catalog_check_oid check_ts_parser_oid = 111 | {CHECK_OID_REFERENCE, true, "pg_ts_parser"}; 112 | static struct pg_catalog_check_oid check_ts_template_oid = 113 | {CHECK_OID_REFERENCE, true, "pg_ts_template"}; 114 | static struct pg_catalog_check_oid check_type_oid = 115 | {CHECK_OID_REFERENCE, false, "pg_type"}; 116 | static struct pg_catalog_check_oid check_type_oid_array = 117 | {CHECK_OID_ARRAY_REFERENCE, false, "pg_type"}; 118 | static struct pg_catalog_check_oid check_type_oid_vector = 119 | {CHECK_OID_VECTOR_REFERENCE, false, "pg_type"}; 120 | static struct pg_catalog_check_oid check_type_optional_oid = 121 | {CHECK_OID_REFERENCE, true, "pg_type"}; 122 | static struct pg_catalog_check_oid check_queue_oid = 123 | {CHECK_OID_REFERENCE, false, "edb_queue"}; 124 | static struct pg_catalog_check_oid check_publication_oid = 125 | {CHECK_OID_REFERENCE, false, "pg_publication"}; 126 | static struct pg_catalog_check_oid check_database_oid = 127 | {CHECK_OID_REFERENCE, false, "pg_database"}; 128 | static struct pg_catalog_check_oid check_subscription_oid = 129 | {CHECK_OID_REFERENCE, false, "pg_subscription"}; 130 | static struct pg_catalog_check_oid check_redaction_policy_oid = 131 | {CHECK_OID_REFERENCE, false, "edb_redaction_policy"}; 132 | static struct pg_catalog_check_oid check_statistic_ext_oid = 133 | {CHECK_OID_REFERENCE, false, "pg_statistic_ext"}; 134 | static struct pg_catalog_check_oid check_trigger_optional_oid = 135 | {CHECK_OID_REFERENCE, true, "pg_trigger"}; 136 | 137 | /* pg_catalog_table & pg_catalog_column */ 138 | static struct pg_catalog_column pg_class_column[] = 139 | { 140 | /* pg_class */ 141 | {"oid", NULL, 0, 0, false, true, true}, 142 | {"relowner", NULL, 0, 0, false, false, false, &check_authid_oid}, 143 | {"relnamespace", NULL, 0, 0, false, false, false, &check_namespace_oid}, 144 | {"relname", NULL, 0, 0, false, false, true}, 145 | {"reltype", NULL, 0, 0, false, false, false, &check_type_optional_oid}, 146 | {"reloftype", NULL, 90000, 0, false, false, false, &check_type_optional_oid}, 147 | {"relkind", NULL, 0, 0, false, false, true}, 148 | {"relam", NULL, 0, 0, false, false, false, &check_am_optional_oid}, 149 | {"relnatts", NULL, 0, 0, false, false, false, &check_relnatts_value}, 150 | {"reltablespace", NULL, 0, 0, false, false, false, &check_tablespace_optional_oid}, 151 | {"reltoastrelid", NULL, 0, 0, false, false, false, &check_class_optional_oid}, 152 | {NULL} 153 | }; 154 | 155 | static struct pg_catalog_column pg_namespace_column[] = 156 | { 157 | /* pg_namespace */ 158 | {"oid", NULL, 0, 0, false, true, true}, 159 | {"nspname", NULL, 0, 0, false, false, true}, 160 | {"nspowner", NULL, 0, 0, false, false, false, &check_authid_oid}, 161 | {"nspparent", NULL, 0, 0, true, false, false, &check_namespace_optional_oid}, 162 | {"nspobjecttype", NULL, 90200, 0, true, false, false, &check_type_optional_oid}, 163 | {"nspforeignserver", NULL, 0, 0, true, false, false, &check_foreign_server_optional_oid}, 164 | {NULL} 165 | }; 166 | 167 | static struct pg_catalog_column pg_authid_column[] = 168 | { 169 | /* pg_authid */ 170 | {"oid", NULL, 0, 0, false, true, true}, 171 | {"rolname", NULL, 0, 0, false, false, true}, 172 | {"rolprofile", NULL, 90500, 0, true, false, false, &check_profile_oid}, 173 | {NULL} 174 | }; 175 | 176 | static struct pg_catalog_column pg_tablespace_column[] = 177 | { 178 | /* pg_tablespace */ 179 | {"oid", NULL, 0, 0, false, true, true}, 180 | {"spcname", NULL, 0, 0, false, false, false}, 181 | {"spcowner", NULL, 0, 0, false, false, false, &check_authid_oid}, 182 | {NULL} 183 | }; 184 | 185 | static struct pg_catalog_column pg_type_column[] = 186 | { 187 | /* pg_type */ 188 | {"oid", NULL, 0, 0, false, true, true}, 189 | {"typname", NULL, 0, 0, false, false, false}, 190 | {"typowner", NULL, 0, 0, false, false, false, &check_authid_oid}, 191 | {"typnamespace", NULL, 0, 0, false, false, false, &check_namespace_oid}, 192 | {"typrelid", NULL, 0, 0, false, false, false, &check_class_optional_oid}, 193 | {"typelem", NULL, 0, 0, false, false, false, &check_type_optional_oid}, 194 | {"typarray", NULL, 0, 0, false, false, false, &check_type_optional_oid}, 195 | {"typbasetype", NULL, 0, 0, false, false, false, &check_type_optional_oid}, 196 | {"typcollation", NULL, 90100, 0, false, false, false, &check_collation_optional_oid}, 197 | {NULL} 198 | }; 199 | 200 | static struct pg_catalog_column pg_am_column[] = 201 | { 202 | /* pg_am */ 203 | {"oid", NULL, 0, 0, false, true, true}, 204 | {"amkeytype", NULL, 0, 90599, false, false, false, &check_type_optional_oid}, 205 | {NULL} 206 | }; 207 | 208 | static struct pg_catalog_column pg_collation_column[] = 209 | { 210 | /* pg_collation */ 211 | {"oid", NULL, 90100, 0, false, true, true}, 212 | {"collnamespace", NULL, 90100, 0, false, false, false, &check_namespace_oid}, 213 | {"collowner", NULL, 90100, 0, false, false, false, &check_authid_oid}, 214 | {NULL} 215 | }; 216 | 217 | static struct pg_catalog_column pg_proc_column[] = 218 | { 219 | /* pg_proc */ 220 | {"oid", NULL, 0, 0, false, true, true}, 221 | {"pronamespace", NULL, 0, 0, false, false, false, &check_namespace_oid}, 222 | {"proowner", NULL, 0, 0, false, false, false, &check_authid_oid}, 223 | {"prolang", NULL, 0, 0, false, false, false, &check_language_oid}, 224 | {"provariadic", NULL, 0, 0, false, false, false, &check_type_optional_oid}, 225 | {"prorettype", NULL, 0, 0, false, false, false, &check_type_oid}, 226 | {"proargtypes", NULL, 0, 0, false, false, false, &check_type_oid_vector}, 227 | {"proallargtypes", NULL, 0, 0, false, false, false, &check_type_oid_array}, 228 | {NULL} 229 | }; 230 | 231 | static struct pg_catalog_column pg_language_column[] = 232 | { 233 | /* pg_language */ 234 | {"oid", NULL, 0, 0, false, true, true}, 235 | {"lanowner", NULL, 0, 0, false, false, false, &check_authid_oid}, 236 | {"lanplcallfoid", NULL, 0, 0, false, false, false}, 237 | {"laninline", NULL, 90000, 0, false, false, false}, 238 | {"lanvalidator", NULL, 0, 0, false, false, false}, 239 | {NULL} 240 | }; 241 | 242 | static struct pg_catalog_column pg_index_column[] = 243 | { 244 | /* pg_index */ 245 | {"indexrelid", NULL, 0, 0, false, true, true}, 246 | {"indrelid", NULL, 0, 0, false, false, false, &check_class_oid}, 247 | {"indcollation", NULL, 90100, 0, false, false, false, &check_collation_optional_oid_vector}, 248 | {"indclass", NULL, 0, 0, false, false, false, &check_opclass_oid_vector}, 249 | {NULL} 250 | }; 251 | 252 | static struct pg_catalog_column pg_constraint_column[] = 253 | { 254 | /* pg_constraint */ 255 | {"oid", NULL, 0, 0, false, true, true}, 256 | {"conname", NULL, 0, 0, false, false, false}, 257 | {"connamespace", NULL, 0, 0, false, false, false, &check_namespace_oid}, 258 | {"conrelid", NULL, 0, 0, false, false, false, &check_class_optional_oid}, 259 | {"contypid", NULL, 0, 0, false, false, false, &check_type_optional_oid}, 260 | {"conindid", NULL, 90000, 0, false, false, false, &check_index_optional_oid}, 261 | {"confrelid", NULL, 0, 0, false, false, false, &check_class_optional_oid}, 262 | {"conpfeqop", NULL, 0, 0, false, false, false, &check_operator_oid_array}, 263 | {"conppeqop", NULL, 0, 0, false, false, false, &check_operator_oid_array}, 264 | {"conffeqop", NULL, 0, 0, false, false, false, &check_operator_oid_array}, 265 | {"conexclop", NULL, 90000, 0, false, false, false, &check_operator_oid_array}, 266 | {"conparentid", NULL, 110000, 0, false, false, false, &check_constraint_optional_oid}, 267 | {NULL} 268 | }; 269 | 270 | static struct pg_catalog_column pg_database_column[] = 271 | { 272 | /* pg_database */ 273 | {"oid", NULL, 0, 0, false, true, true}, 274 | {"datname", NULL, 0, 0, false, false, false}, 275 | {"datdba", NULL, 0, 0, false, false, false, &check_authid_oid}, 276 | {"dattablespace", NULL, 0, 0, false, false, false, &check_tablespace_oid}, 277 | {NULL} 278 | }; 279 | 280 | static struct pg_catalog_column pg_cast_column[] = 281 | { 282 | /* pg_cast */ 283 | {"oid", NULL, 0, 0, false, true, true}, 284 | {"castsource", NULL, 0, 0, false, false, false, &check_type_oid}, 285 | {"casttarget", NULL, 0, 0, false, false, false, &check_type_oid}, 286 | {"castfunc", NULL, 0, 0, false, false, false, &check_proc_optional_oid}, 287 | {NULL} 288 | }; 289 | 290 | static struct pg_catalog_column pg_conversion_column[] = 291 | { 292 | /* pg_conversion */ 293 | {"oid", NULL, 0, 0, false, true, true}, 294 | {"connamespace", NULL, 0, 0, false, false, false, &check_namespace_oid}, 295 | {"conowner", NULL, 0, 0, false, false, false, &check_authid_oid}, 296 | {"conproc", "pg_catalog.oid", 0, 0, false, false, false, &check_proc_oid}, 297 | {NULL} 298 | }; 299 | 300 | static struct pg_catalog_column pg_extension_column[] = 301 | { 302 | /* pg_extension */ 303 | {"oid", NULL, 90100, 0, false, true, true}, 304 | {"extowner", NULL, 90100, 0, false, false, false, &check_authid_oid}, 305 | {"extnamespace", NULL, 90100, 0, false, false, false, &check_namespace_oid}, 306 | {"extconfig", NULL, 90100, 0, false, false, false, &check_class_oid_array}, 307 | {NULL} 308 | }; 309 | 310 | static struct pg_catalog_column pg_enum_column[] = 311 | { 312 | /* pg_enum */ 313 | {"oid", NULL, 0, 0, false, true, true}, 314 | {"enumtypid", NULL, 0, 0, false, false, false, &check_type_oid}, 315 | {NULL} 316 | }; 317 | 318 | static struct pg_catalog_column pg_trigger_column[] = 319 | { 320 | /* pg_trigger */ 321 | {"oid", NULL, 0, 0, false, true, true}, 322 | {"tgpackageoid", NULL, 120000, 0, true, false, false, &check_namespace_optional_oid}, 323 | {"tgparentid", NULL, 130000, 0, false, false, false, &check_trigger_optional_oid}, 324 | {"tgrelid", NULL, 0, 0, false, false, false, &check_class_oid}, 325 | {"tgfoid", NULL, 0, 0, false, false, false, &check_proc_optional_oid}, 326 | {"tgconstrrelid", NULL, 0, 0, false, false, false, &check_class_optional_oid}, 327 | {"tgconstrindid", NULL, 90000, 0, false, false, false, &check_index_optional_oid}, 328 | {"tgconstraint", NULL, 0, 0, false, false, false, &check_constraint_optional_oid}, 329 | {NULL} 330 | }; 331 | 332 | static struct pg_catalog_column pg_ts_parser_column[] = 333 | { 334 | /* pg_ts_parser */ 335 | {"oid", NULL, 0, 0, false, true, true}, 336 | {"prsnamespace", NULL, 0, 0, false, false, false, &check_namespace_oid}, 337 | {"prsstart", "pg_catalog.oid", 0, 0, false, false, false, &check_proc_oid}, 338 | {"prstoken", "pg_catalog.oid", 0, 0, false, false, false, &check_proc_oid}, 339 | {"prsend", "pg_catalog.oid", 0, 0, false, false, false, &check_proc_oid}, 340 | {"prsheadline", "pg_catalog.oid", 0, 0, false, false, false, &check_proc_oid}, 341 | {"prslextype", "pg_catalog.oid", 0, 0, false, false, false, &check_proc_oid}, 342 | {NULL} 343 | }; 344 | 345 | static struct pg_catalog_column pg_ts_config_column[] = 346 | { 347 | /* pg_ts_config */ 348 | {"oid", NULL, 0, 0, false, true, true}, 349 | {"cfgowner", NULL, 0, 0, false, false, false, &check_authid_oid}, 350 | {"cfgnamespace", NULL, 0, 0, false, false, false, &check_namespace_oid}, 351 | {"cfgparser", NULL, 0, 0, false, false, false, &check_ts_parser_oid}, 352 | {NULL} 353 | }; 354 | 355 | static struct pg_catalog_column pg_ts_template_column[] = 356 | { 357 | /* pg_ts_template */ 358 | {"oid", NULL, 0, 0, false, true, true}, 359 | {"tmplnamespace", NULL, 0, 0, false, false, false, &check_namespace_oid}, 360 | {"tmplinit", "pg_catalog.oid", 0, 0, false, false, false, &check_proc_oid}, 361 | {"tmpllexize", "pg_catalog.oid", 0, 0, false, false, false, &check_proc_oid}, 362 | {NULL} 363 | }; 364 | 365 | static struct pg_catalog_column pg_ts_dict_column[] = 366 | { 367 | /* pg_ts_dict */ 368 | {"oid", NULL, 0, 0, false, true, true}, 369 | {"dictnamespace", NULL, 0, 0, false, false, false, &check_namespace_oid}, 370 | {"dictowner", NULL, 0, 0, false, false, false, &check_authid_oid}, 371 | {"dicttemplate", NULL, 0, 0, false, false, false, &check_ts_template_oid}, 372 | {NULL} 373 | }; 374 | 375 | static struct pg_catalog_column pg_fdw_column[] = 376 | { 377 | /* pg_foreign_data_wrapper */ 378 | {"oid", NULL, 0, 0, false, true, true}, 379 | {"fdwowner", NULL, 0, 0, false, false, false, &check_authid_oid}, 380 | {"fdwhandler", NULL, 90100, 0, false, false, false, &check_proc_optional_oid}, 381 | {"fdwvalidator", NULL, 0, 0, false, false, false, &check_proc_optional_oid}, 382 | {NULL} 383 | }; 384 | 385 | static struct pg_catalog_column pg_foreign_server_column[] = 386 | { 387 | /* pg_foreign_server */ 388 | {"oid", NULL, 0, 0, false, true, true}, 389 | {"srvowner", NULL, 0, 0, false, false, false, &check_authid_oid}, 390 | {"srvfdw", NULL, 0, 0, false, false, false, &check_foreign_data_wrapper_oid}, 391 | {NULL} 392 | }; 393 | 394 | static struct pg_catalog_column pg_user_mapping_column[] = 395 | { 396 | /* pg_user_mapping */ 397 | {"oid", NULL, 0, 0, false, true, true}, 398 | {"umuser", NULL, 0, 0, false, false, false, &check_authid_optional_oid}, 399 | {"umserver", NULL, 0, 0, false, false, false, &check_foreign_server_oid}, 400 | {NULL} 401 | }; 402 | 403 | static struct pg_catalog_column pg_foreign_table_column[] = 404 | { 405 | /* pg_foreign_table */ 406 | {"ftrelid", NULL, 90100, 0, false, true, true, &check_class_oid}, 407 | {"ftserver", NULL, 90100, 0, false, false, false, &check_foreign_server_oid}, 408 | {NULL} 409 | }; 410 | 411 | static struct pg_catalog_column pg_event_trigger_column[] = 412 | { 413 | /* pg_event_trigger */ 414 | {"oid", NULL, 90300, 0, false, true, true}, 415 | {"evtowner", NULL, 90300, 0, false, false, false, &check_authid_oid}, 416 | {"evtfoid", NULL, 90300, 0, false, false, false, &check_proc_oid}, 417 | {NULL} 418 | }; 419 | 420 | static struct pg_catalog_column pg_opfamily_column[] = 421 | { 422 | /* pg_opfamily */ 423 | {"oid", NULL, 0, 0, false, true, true}, 424 | {"opfname", NULL, 0, 0, false, false, false}, 425 | {"opfmethod", NULL, 0, 0, false, false, false, &check_am_oid}, 426 | {"opfnamespace", NULL, 0, 0, false, false, false, &check_namespace_oid}, 427 | {"opfowner", NULL, 0, 0, false, false, false, &check_authid_oid}, 428 | {NULL} 429 | }; 430 | 431 | static struct pg_catalog_column pg_opclass_column[] = 432 | { 433 | /* pg_opclass */ 434 | {"oid", NULL, 0, 0, false, true, true}, 435 | {"opcname", NULL, 0, 0, false, false, false}, 436 | {"opcmethod", NULL, 0, 0, false, false, false, &check_am_oid}, 437 | {"opcnamespace", NULL, 0, 0, false, false, false, &check_namespace_oid}, 438 | {"opcowner", NULL, 0, 0, false, false, false, &check_authid_oid}, 439 | {"opcfamily", NULL, 0, 0, false, false, false, &check_opfamily_oid}, 440 | {"opcintype", NULL, 0, 0, false, false, false, &check_type_oid}, 441 | {"opckeytype", NULL, 0, 0, false, false, false, &check_type_optional_oid}, 442 | {NULL} 443 | }; 444 | 445 | static struct pg_catalog_column pg_operator_column[] = 446 | { 447 | /* pg_operator */ 448 | {"oid", NULL, 0, 0, false, true, true}, 449 | {"oprname", NULL, 0, 0, false, false, false}, 450 | {"oprnamespace", NULL, 0, 0, false, false, false, &check_namespace_oid}, 451 | {"oprowner", NULL, 0, 0, false, false, false, &check_authid_oid}, 452 | {"oprleft", NULL, 0, 0, false, false, false, &check_type_optional_oid}, 453 | {"oprright", NULL, 0, 0, false, false, false, &check_type_optional_oid}, 454 | {"oprresult", NULL, 0, 0, false, false, false, &check_type_optional_oid}, 455 | {"oprcom", NULL, 0, 0, false, false, false, &check_operator_optional_oid}, 456 | {"oprnegate", NULL, 0, 0, false, false, false, &check_operator_optional_oid}, 457 | {"oprcode", "pg_catalog.oid", 0, 0, false, false, false, &check_proc_optional_oid}, 458 | {"oprrest", "pg_catalog.oid", 0, 0, false, false, false, &check_proc_optional_oid}, 459 | {"oprjoin", "pg_catalog.oid", 0, 0, false, false, false, &check_proc_optional_oid}, 460 | {NULL} 461 | }; 462 | 463 | static struct pg_catalog_column pg_amop_column[] = 464 | { 465 | /* pg_amop */ 466 | {"oid", NULL, 0, 0, false, true, true}, 467 | {"amopfamily", NULL, 0, 0, false, false, false, &check_opfamily_oid}, 468 | {"amoplefttype", NULL, 0, 0, false, false, false, &check_type_oid}, 469 | {"amoprighttype", NULL, 0, 0, false, false, false, &check_type_oid}, 470 | {"amopopr", NULL, 0, 0, false, false, false, &check_operator_oid}, 471 | {"amopmethod", NULL, 0, 0, false, false, false, &check_am_oid}, 472 | {"amopsortfamily", NULL, 90100, 0, false, false, false, &check_opfamily_optional_oid}, 473 | {NULL} 474 | }; 475 | 476 | static struct pg_catalog_column pg_amproc_column[] = 477 | { 478 | /* pg_amproc */ 479 | {"oid", NULL, 0, 0, false, true, true}, 480 | {"amprocfamily", NULL, 0, 0, false, false, false, &check_opfamily_oid}, 481 | {"amproclefttype", NULL, 0, 0, false, false, false, &check_type_oid}, 482 | {"amprocrighttype", NULL, 0, 0, false, false, false, &check_type_oid}, 483 | {"amproc", "pg_catalog.oid", 0, 0, false, false, false, &check_proc_oid}, 484 | {NULL} 485 | }; 486 | 487 | static struct pg_catalog_column pg_default_acl_column[] = 488 | { 489 | /* pg_default_acl */ 490 | {"oid", NULL, 90000, 0, false, true, true}, 491 | {"defaclnamespace", NULL, 90000, 0, false, false, false, &check_namespace_optional_oid}, 492 | {"defaclrole", NULL, 90000, 0, false, false, false, &check_authid_oid}, 493 | {NULL} 494 | }; 495 | 496 | static struct pg_catalog_column pg_rewrite_column[] = 497 | { 498 | /* pg_rewrite_column */ 499 | {"oid", NULL, 0, 0, false, true, true}, 500 | {"rulename", NULL, 0, 0, false, false, false}, 501 | {"ev_class", NULL, 0, 0, false, false, false, &check_class_oid}, 502 | {NULL} 503 | }; 504 | 505 | static struct pg_catalog_column edb_variable_column[] = 506 | { 507 | /* edb_variable_column */ 508 | {"oid", NULL, 0, 0, true, true, true}, 509 | {"varpackage", NULL, 0, 0, true, false, false, &check_namespace_oid}, 510 | {"vartype", NULL, 0, 0, true, false, false, &check_type_optional_oid}, 511 | {NULL} 512 | }; 513 | 514 | static struct pg_catalog_column pg_inherits_column[] = 515 | { 516 | /* pg_inherits */ 517 | {"inhrelid", NULL, 0, 0, false, true, true, &check_class_oid}, 518 | {"inhparent", NULL, 0, 0, false, true, true, &check_class_oid}, 519 | {NULL} 520 | }; 521 | 522 | static struct pg_catalog_column pg_largeobject_metadata_column[] = 523 | { 524 | /* pg_largeobject_metadata */ 525 | {"oid", NULL, 90000, 0, false, true, true}, 526 | {"lomowner", NULL, 90000, 0, false, false, false, &check_authid_oid}, 527 | {NULL} 528 | }; 529 | 530 | static struct pg_catalog_column pg_largeobject_column[] = 531 | { 532 | /* pg_largeobject */ 533 | {"loid", NULL, 0, 0, false, true, true, &check_largeobject_metadata_oid}, 534 | {"pageno", NULL, 0, 0, false, true, true}, 535 | {NULL} 536 | }; 537 | 538 | static struct pg_catalog_column pg_aggregate_column[] = 539 | { 540 | /* pg_aggregate */ 541 | {"aggfnoid", "pg_catalog.oid", 0, 0, false, true, true, &check_proc_oid}, 542 | {"aggtransfn", "pg_catalog.oid", 0, 0, false, false, false, &check_proc_oid}, 543 | {"aggfinalfn", "pg_catalog.oid", 0, 0, false, false, false, &check_proc_optional_oid}, 544 | {"aggsortop", NULL, 0, 0, false, false, false, &check_operator_optional_oid}, 545 | {"aggtranstype", NULL, 0, 0, false, false, false, &check_type_oid}, 546 | {NULL} 547 | }; 548 | 549 | static struct pg_catalog_column pg_ts_config_map_column[] = 550 | { 551 | /* pg_ts_config_map */ 552 | {"mapcfg", NULL, 0, 0, false, true, true, &check_ts_config_oid}, 553 | {"maptokentype", NULL, 0, 0, false, true, true}, 554 | {"mapseqno", NULL, 0, 0, false, true, true}, 555 | {"mapdict", NULL, 0, 0, false, false, false, &check_ts_dict_oid}, 556 | {NULL} 557 | }; 558 | 559 | static struct pg_catalog_column pg_range_column[] = 560 | { 561 | /* pg_range */ 562 | {"rngtypid", NULL, 90200, 0, false, true, true, &check_type_oid}, 563 | {"rngsubtype", NULL, 90200, 0, false, false, false, &check_type_oid}, 564 | {"rngcollation", NULL, 90200, 0, false, false, false, &check_collation_optional_oid}, 565 | {"rngsubopc", NULL, 90200, 0, false, false, false, &check_opclass_oid}, 566 | {"rngcanonical", "pg_catalog.oid", 90200, 0, false, false, false, &check_proc_optional_oid}, 567 | {"rngsubdiff", "pg_catalog.oid", 90200, 0, false, false, false, &check_proc_optional_oid}, 568 | {"rngmultitypid", NULL, 140000, 0, false, false, false, &check_type_oid}, 569 | {NULL} 570 | }; 571 | 572 | static struct pg_catalog_column pg_attribute_column[] = 573 | { 574 | /* pg_attribute */ 575 | {"attrelid", NULL, 0, 0, false, true, true, &check_class_oid}, 576 | {"attname", NULL, 0, 0, false, false, true}, 577 | {"attnum", NULL, 0, 0, false, true, true, &check_attnum_value}, 578 | {"atttypid", NULL, 0, 0, false, false, false, &check_type_optional_oid}, 579 | {"attcollation", NULL, 90100, 0, false, false, false, &check_collation_optional_oid}, 580 | {NULL} 581 | }; 582 | 583 | static struct pg_catalog_column pg_statistic_column[] = 584 | { 585 | /* pg_statistic */ 586 | {"starelid", NULL, 0, 0, false, true, true, &check_class_oid}, 587 | {"staattnum", NULL, 0, 0, false, true, true}, 588 | {"stainherit", NULL, 90000, 0, false, true, true}, 589 | {"staop1", NULL, 0, 0, false, false, false, &check_operator_optional_oid}, 590 | {"staop2", NULL, 0, 0, false, false, false, &check_operator_optional_oid}, 591 | {"staop3", NULL, 0, 0, false, false, false, &check_operator_optional_oid}, 592 | {"staop4", NULL, 0, 0, false, false, false, &check_operator_optional_oid}, 593 | {"stacoll1", NULL, 120000, 0, false, false, false, &check_collation_optional_oid}, 594 | {"stacoll2", NULL, 120000, 0, false, false, false, &check_collation_optional_oid}, 595 | {"stacoll3", NULL, 120000, 0, false, false, false, &check_collation_optional_oid}, 596 | {"stacoll4", NULL, 120000, 0, false, false, false, &check_collation_optional_oid}, 597 | {NULL} 598 | }; 599 | 600 | static struct pg_catalog_column pg_db_role_setting_column[] = 601 | { 602 | /* pg_db_role_setting */ 603 | {"setdatabase", NULL, 90000, 0, false, true, true, &check_database_optional_oid}, 604 | {"setrole", NULL, 90000, 0, false, true, true, &check_authid_optional_oid}, 605 | {NULL} 606 | }; 607 | 608 | static struct pg_catalog_column pg_attrdef_column[] = 609 | { 610 | /* pg_attrdef */ 611 | {"oid", NULL, 0, 0, false, true, true}, 612 | {"adrelid", NULL, 0, 0, false, false, false, &check_class_oid}, 613 | {NULL} 614 | }; 615 | 616 | static struct pg_catalog_column edb_dir_column[] = 617 | { 618 | /* edb_dir */ 619 | {"oid", NULL, 0, 0, true, true, true}, 620 | {"dirowner", NULL, 0, 0, true, false, false, &check_authid_oid}, 621 | {NULL} 622 | }; 623 | 624 | static struct pg_catalog_column edb_partdef_column[] = 625 | { 626 | /* edb_partdef */ 627 | {"oid", NULL, 90100, 90699, true, true, true}, 628 | {"pdefrel", NULL, 90100, 90699, true, false, false, &check_class_oid}, 629 | {NULL} 630 | }; 631 | 632 | static struct pg_catalog_column edb_partition_column[] = 633 | { 634 | /* edb_partition */ 635 | {"oid", NULL, 90100, 90699, true, true, true}, 636 | {"partpdefid", NULL, 90100, 90699, true, false, false, &check_edb_partdef}, 637 | {"partrelid", NULL, 90100, 90699, true, false, false, &check_class_oid}, 638 | {"partparent", NULL, 90100, 90699, true, false, false, &check_edb_partition_optional_oid}, 639 | {"partcons", NULL, 90100, 90699, true, false, false, &check_constraint_oid}, 640 | {NULL} 641 | }; 642 | 643 | /* 644 | * policyobject was originally envisioned to point either to a pg_class OID 645 | * or a pg_synonym OID depending on policykind, but the pg_synonym support was 646 | * never implemented. So for now, we can just check that it's a pg_class OID. 647 | */ 648 | static struct pg_catalog_column edb_policy_column[] = 649 | { 650 | /* edb_policy */ 651 | {"oid", NULL, 90100, 0, true, true, true}, 652 | {"policygroup", NULL, 90100, 0, true, false, false}, 653 | {"policyobject", NULL, 90100, 0, true, false, false, &check_class_oid}, 654 | {"policyproc", NULL, 90100, 0, true, false, false, &check_proc_oid}, 655 | {NULL} 656 | }; 657 | 658 | static struct pg_catalog_column pg_synonym_column[] = 659 | { 660 | /* pg_synonym */ 661 | {"oid", NULL, 0, 0, true, true, true}, 662 | {"synnamespace", NULL, 0, 0, true, false, false, &check_namespace_optional_oid}, 663 | {"synowner", NULL, 0, 0, true, false, false, &check_authid_oid}, 664 | {NULL} 665 | }; 666 | 667 | static struct pg_catalog_column pg_depend_column[] = 668 | { 669 | /* pg_depend */ 670 | {"classid", NULL, 0, 0, false, false, true, &check_dependency_class_id_value}, 671 | {"objid", NULL, 0, 0, false, false, true, &check_dependency_id_value}, 672 | {"objsubid", NULL, 0, 0, false, false, true, &check_dependency_subid_value}, 673 | {"refclassid", NULL, 0, 0, false, false, true, &check_dependency_class_id_value}, 674 | {"refobjid", NULL, 0, 0, false, false, true, &check_dependency_id_value}, 675 | {"refobjsubid", NULL, 0, 0, false, false, true, &check_dependency_subid_value}, 676 | {"deptype", NULL, 0, 0, false, false, true}, 677 | {NULL} 678 | }; 679 | 680 | static struct pg_catalog_column pg_shdepend_column[] = 681 | { 682 | /* pg_shdepend */ 683 | {"dbid", NULL, 0, 0, false, false, true, &check_database_optional_oid}, 684 | {"classid", NULL, 0, 0, false, false, true, &check_dependency_class_id_value}, 685 | {"objid", NULL, 0, 0, false, false, true, &check_dependency_id_value}, 686 | {"objsubid", NULL, 0, 0, false, false, true, &check_dependency_subid_value}, 687 | {"refclassid", NULL, 0, 0, false, false, true, &check_dependency_class_id_value}, 688 | {"refobjid", NULL, 0, 0, false, false, true, &check_dependency_id_value}, 689 | {"deptype", NULL, 0, 0, false, false, true}, 690 | {NULL} 691 | }; 692 | 693 | static struct pg_catalog_column pg_description_column[] = 694 | { 695 | /* pg_description */ 696 | {"classoid", NULL, 0, 0, false, false, true, &check_dependency_class_id_value}, 697 | {"objoid", NULL, 0, 0, false, false, true, &check_dependency_id_value}, 698 | {"objsubid", NULL, 0, 0, false, false, true, &check_dependency_subid_value}, 699 | {NULL} 700 | }; 701 | 702 | static struct pg_catalog_column pg_shdescription_column[] = 703 | { 704 | /* pg_shdescription */ 705 | {"classoid", NULL, 0, 0, false, false, true, &check_dependency_class_id_value}, 706 | {"objoid", NULL, 0, 0, false, false, true, &check_dependency_id_value}, 707 | {NULL} 708 | }; 709 | 710 | static struct pg_catalog_column pg_seclabel_column[] = 711 | { 712 | /* pg_seclabel */ 713 | {"classoid", NULL, 90100, 0, false, false, true, &check_dependency_class_id_value}, 714 | {"objoid", NULL, 90100, 0, false, false, true, &check_dependency_id_value}, 715 | {"objsubid", NULL, 90100, 0, false, false, true, &check_dependency_subid_value}, 716 | {"provider", NULL, 90100, 0, false, false, true}, 717 | {NULL} 718 | }; 719 | 720 | static struct pg_catalog_column pg_shseclabel_column[] = 721 | { 722 | /* pg_shseclabel */ 723 | {"classoid", NULL, 90200, 0, false, false, true, &check_dependency_class_id_value}, 724 | {"objoid", NULL, 90200, 0, false, false, true, &check_dependency_id_value}, 725 | {"provider", NULL, 90200, 0, false, false, true, NULL}, 726 | {NULL} 727 | }; 728 | 729 | static struct pg_catalog_column pg_auth_members_column[] = 730 | { 731 | {"roleid", NULL, 0, 0, false, false, true, &check_authid_oid}, 732 | {"member", NULL, 0, 0, false, false, true, &check_authid_oid}, 733 | {"grantor", NULL, 0, 0, false, false, false, &check_authid_oid}, 734 | {NULL} 735 | }; 736 | 737 | static struct pg_catalog_column pg_policy_column[] = 738 | { 739 | {"oid", NULL, 90500, 0, false, true, true}, 740 | {"polname", NULL, 90500, 0, false, false, false}, 741 | {"polrelid", NULL, 90500, 0, false, false, false, &check_class_oid}, 742 | {"polroles", NULL, 90500, 0, false, false, false, 743 | &check_authid_oid_array_zero_ok}, 744 | {NULL} 745 | }; 746 | 747 | static struct pg_catalog_column edb_profile_column [] = 748 | { 749 | {"oid", NULL, 90500, 0, true, true, true}, 750 | {"prfname", NULL, 90500, 0, true, false, true}, 751 | {NULL} 752 | }; 753 | 754 | static struct pg_catalog_column edb_queue_table_column[] = 755 | { 756 | {"oid", NULL, 90600, 0, true, true, true}, 757 | {"qtname", NULL, 90600, 0, true, false, true}, 758 | {"qtnamespace", NULL, 90600, 0, true, false, false, &check_namespace_oid}, 759 | {"qtrelid", NULL, 90600, 0, true, false, false, &check_class_oid}, 760 | {"qpayloadtype", NULL, 90600, 0, true, false, false, &check_type_oid}, 761 | {NULL} 762 | }; 763 | 764 | static struct pg_catalog_column edb_queue_column [] = 765 | { 766 | {"oid", NULL, 90600, 0, true, true, true}, 767 | {"aqname", NULL, 90600, 0, true, false, true}, 768 | {"aqrelid", NULL, 90600, 0, true, false, false, &check_class_oid}, 769 | {NULL} 770 | }; 771 | 772 | static struct pg_catalog_column edb_password_history_column[] = 773 | { 774 | /* edb_password_history */ 775 | {"passhistroleid", NULL, 90500, 0, true, true, true, &check_authid_oid}, 776 | {"passhistpassword", NULL, 90500, 0, true, true, true}, 777 | {"passhistpasswordsetat", NULL, 90500, 0, true, false, true}, 778 | {NULL} 779 | }; 780 | 781 | static struct pg_catalog_column edb_queue_callback_column[] = 782 | { 783 | /* edb_queue_callback */ 784 | {"oid", NULL, 90600, 0, true, true, true}, 785 | {"qcbqueueid", NULL, 90600, 0, true, false, true, &check_queue_oid}, 786 | {"qcbowner", NULL, 90600, 0, true, false, false, &check_authid_oid}, 787 | {NULL} 788 | }; 789 | 790 | static struct pg_catalog_column edb_resource_group_column[] = 791 | { 792 | /* edb_resource_group */ 793 | {"oid", NULL, 90400, 0, true, true, true}, 794 | {"rgrpname", NULL, 90400, 0, true, false, true}, 795 | {NULL} 796 | }; 797 | 798 | static struct pg_catalog_column pg_init_privs_column[] = 799 | { 800 | /* pg_init_privs */ 801 | {"objoid", NULL, 90600, 0, false, true, true}, 802 | {"classoid", NULL, 90600, 0, false, true, true, &check_class_oid}, 803 | {"objsubid", NULL, 90600, 0, false, true, true}, 804 | {NULL} 805 | }; 806 | 807 | static struct pg_catalog_column pg_partitioned_table_column[] = 808 | { 809 | /* pg_partitioned_table */ 810 | {"partrelid", NULL, 100000, 0, false, true, true, &check_class_oid}, 811 | {"partdefid", NULL, 110000, 0, false, false, false, &check_class_optional_oid}, 812 | {"partclass", NULL, 100000, 0, false, false, false, &check_opclass_oid_vector}, 813 | {"partcollation", NULL, 100000, 0, false, false, false, &check_collation_optional_oid_vector}, 814 | {NULL} 815 | }; 816 | 817 | static struct pg_catalog_column pg_pltemplate_column[] = 818 | { 819 | /* pg_pltemplate */ 820 | {"tmplname", NULL, 0, 0, false, true, true}, 821 | {NULL} 822 | }; 823 | 824 | static struct pg_catalog_column pg_publication_column[] = 825 | { 826 | /* pg_publication */ 827 | {"oid", NULL, 100000, 0, false, true, true}, 828 | {"pubowner", NULL, 100000, 0, false, false, false, &check_authid_oid}, 829 | {NULL} 830 | }; 831 | 832 | static struct pg_catalog_column pg_publication_rel_column[] = 833 | { 834 | /* pg_publication_rel */ 835 | {"oid", NULL, 100000, 0, false, true, true}, 836 | {"prpubid", NULL, 100000, 0, false, false, false, &check_publication_oid}, 837 | {"prrelid", NULL, 100000, 0, false, false, false, &check_class_oid}, 838 | {NULL} 839 | }; 840 | 841 | static struct pg_catalog_column pg_replication_origin_column[] = 842 | { 843 | /* pg_replication_origin */ 844 | {"roident", NULL, 90500, 0, false, true, true}, 845 | {NULL} 846 | }; 847 | 848 | static struct pg_catalog_column pg_sequence_column[] = 849 | { 850 | /* pg_sequence */ 851 | {"seqrelid", NULL, 100000, 0, false, true, true, &check_class_oid}, 852 | {"seqtypid", NULL, 100000, 0, false, false, false, &check_type_oid}, 853 | {NULL} 854 | }; 855 | 856 | static struct pg_catalog_column pg_statistic_ext_column[] = 857 | { 858 | /* pg_statistic_ext */ 859 | {"oid", NULL, 100000, 0, false, true, true}, 860 | {"stxrelid", NULL, 100000, 0, false, false, false, &check_class_oid}, 861 | {"stxnamespace", NULL, 100000, 0, false, false, false, &check_namespace_oid}, 862 | {"stxowner", NULL, 100000, 0, false, false, false, &check_authid_oid}, 863 | {NULL} 864 | }; 865 | 866 | static struct pg_catalog_column pg_statistic_ext_data_column[] = 867 | { 868 | /* pg_statistic_ext_data */ 869 | {"stxoid", NULL, 120000, 0, false, true, true, &check_statistic_ext_oid}, 870 | {NULL} 871 | }; 872 | 873 | static struct pg_catalog_column pg_subscription_column[] = 874 | { 875 | /* pg_subscription */ 876 | {"oid", NULL, 100000, 0, false, true, true}, 877 | {"subdbid", NULL, 100000, 0, false, false, false, &check_database_oid}, 878 | {"subowner", NULL, 100000, 0, false, false, false, &check_authid_oid}, 879 | {NULL} 880 | }; 881 | 882 | static struct pg_catalog_column pg_subscription_rel_column[] = 883 | { 884 | /* pg_subscription_rel */ 885 | {"srsubid", NULL, 100000, 0, false, true, true, &check_subscription_oid}, 886 | {"srrelid", NULL, 100000, 0, false, true, true, &check_class_oid}, 887 | {NULL} 888 | }; 889 | 890 | static struct pg_catalog_column pg_transform_column[] = 891 | { 892 | /* pg_transform */ 893 | {"oid", NULL, 90500, 0, false, true, true}, 894 | {"trftype", NULL, 90500, 0, false, false, false, &check_type_oid}, 895 | {"trflang", NULL, 90500, 0, false, false, false, &check_language_oid}, 896 | {"trffromsql", "pg_catalog.oid", 90500, 0, false, false, false, &check_proc_oid}, 897 | {"trftosql", "pg_catalog.oid", 90500, 0, false, false, false, &check_proc_oid}, 898 | {NULL} 899 | }; 900 | 901 | static struct pg_catalog_column edb_redaction_column[] = 902 | { 903 | /* edb_redaction_column */ 904 | {"oid", NULL, 110000, 0, true, true, true}, 905 | {"rdpolicyid", NULL, 110000, 0, true, false, false, &check_redaction_policy_oid}, 906 | {"rdrelid", NULL, 110000, 0, true, false, false, &check_class_oid}, 907 | {NULL} 908 | }; 909 | 910 | static struct pg_catalog_column edb_redaction_policy_column[] = 911 | { 912 | /* edb_redaction_policy */ 913 | {"oid", NULL, 110000, 0, true, true, true}, 914 | {"rdrelid", NULL, 110000, 0, true, false, false, &check_class_oid}, 915 | {NULL} 916 | }; 917 | 918 | static struct pg_catalog_column edb_last_ddl_time_column[] = 919 | { 920 | /* edb_last_ddl_time */ 921 | {"classid", NULL, 140000, 0, true, false, true, &check_dependency_class_id_value}, 922 | {"objid", NULL, 140000, 0, true, false, true, &check_dependency_id_value}, 923 | {NULL} 924 | }; 925 | 926 | static struct pg_catalog_column edb_last_ddl_time_shared_column[] = 927 | { 928 | /* edb_last_ddl_time_shared */ 929 | {"classid", NULL, 140000, 0, true, false, true, &check_dependency_class_id_value}, 930 | {"objid", NULL, 140000, 0, true, false, true, &check_dependency_id_value}, 931 | {NULL} 932 | }; 933 | 934 | struct pg_catalog_table pg_catalog_tables[] = 935 | { 936 | {"pg_class", pg_class_column}, 937 | {"pg_namespace", pg_namespace_column}, 938 | {"pg_authid", pg_authid_column}, 939 | {"pg_tablespace", pg_tablespace_column}, 940 | {"pg_type", pg_type_column}, 941 | {"pg_am", pg_am_column}, 942 | {"pg_collation", pg_collation_column}, 943 | {"pg_proc", pg_proc_column}, 944 | {"pg_language", pg_language_column}, 945 | {"pg_index", pg_index_column}, 946 | {"pg_constraint", pg_constraint_column}, 947 | {"pg_database", pg_database_column}, 948 | {"pg_cast", pg_cast_column}, 949 | {"pg_conversion", pg_conversion_column}, 950 | {"pg_extension", pg_extension_column}, 951 | {"pg_enum", pg_enum_column}, 952 | {"pg_trigger", pg_trigger_column}, 953 | {"pg_ts_parser", pg_ts_parser_column}, 954 | {"pg_ts_config", pg_ts_config_column}, 955 | {"pg_ts_template", pg_ts_template_column}, 956 | {"pg_ts_dict", pg_ts_dict_column}, 957 | {"pg_foreign_data_wrapper", pg_fdw_column}, 958 | {"pg_foreign_server", pg_foreign_server_column}, 959 | {"pg_user_mapping", pg_user_mapping_column}, 960 | {"pg_foreign_table", pg_foreign_table_column}, 961 | {"pg_event_trigger", pg_event_trigger_column}, 962 | {"pg_opfamily", pg_opfamily_column}, 963 | {"pg_opclass", pg_opclass_column}, 964 | {"pg_operator", pg_operator_column}, 965 | {"pg_amop", pg_amop_column}, 966 | {"pg_amproc", pg_amproc_column}, 967 | {"pg_default_acl", pg_default_acl_column}, 968 | {"pg_rewrite", pg_rewrite_column}, 969 | {"pg_inherits", pg_inherits_column}, 970 | {"pg_largeobject_metadata", pg_largeobject_metadata_column}, 971 | {"pg_largeobject", pg_largeobject_column}, 972 | {"pg_aggregate", pg_aggregate_column}, 973 | {"pg_ts_config_map", pg_ts_config_map_column}, 974 | {"pg_range", pg_range_column}, 975 | {"pg_attrdef", pg_attrdef_column}, 976 | {"pg_attribute", pg_attribute_column}, 977 | {"pg_statistic", pg_statistic_column}, 978 | {"pg_db_role_setting", pg_db_role_setting_column}, 979 | {"pg_depend", pg_depend_column}, 980 | {"pg_shdepend", pg_shdepend_column}, 981 | {"edb_dir", edb_dir_column}, 982 | {"edb_partdef", edb_partdef_column}, 983 | {"edb_partition", edb_partition_column}, 984 | {"edb_policy", edb_policy_column}, 985 | {"pg_synonym", pg_synonym_column}, 986 | {"edb_variable", edb_variable_column}, 987 | {"pg_description", pg_description_column}, 988 | {"pg_shdescription", pg_shdescription_column}, 989 | {"pg_seclabel", pg_seclabel_column}, 990 | {"pg_shseclabel", pg_shseclabel_column}, 991 | {"pg_auth_members", pg_auth_members_column}, 992 | {"pg_policy", pg_policy_column}, 993 | {"edb_profile", edb_profile_column}, 994 | {"edb_queue_table", edb_queue_table_column}, 995 | {"edb_queue", edb_queue_column}, 996 | {"edb_password_history", edb_password_history_column}, 997 | {"edb_queue_callback", edb_queue_callback_column}, 998 | {"edb_resource_group", edb_resource_group_column}, 999 | {"pg_init_privs", pg_init_privs_column}, 1000 | {"pg_partitioned_table", pg_partitioned_table_column}, 1001 | {"pg_pltemplate", pg_pltemplate_column}, 1002 | {"pg_publication", pg_publication_column}, 1003 | {"pg_publication_rel", pg_publication_rel_column}, 1004 | {"pg_replication_origin", pg_replication_origin_column}, 1005 | {"pg_sequence", pg_sequence_column}, 1006 | {"pg_statistic_ext", pg_statistic_ext_column}, 1007 | {"pg_subscription", pg_subscription_column}, 1008 | {"pg_subscription_rel", pg_subscription_rel_column}, 1009 | {"pg_transform", pg_transform_column}, 1010 | {"edb_redaction_column", edb_redaction_column}, 1011 | {"edb_redaction_policy", edb_redaction_policy_column}, 1012 | {"pg_statistic_ext_data", pg_statistic_ext_data_column}, 1013 | {"edb_last_ddl_time", edb_last_ddl_time_column}, 1014 | {"edb_last_ddl_time_shared", edb_last_ddl_time_shared_column}, 1015 | {NULL} 1016 | }; 1017 | -------------------------------------------------------------------------------- /log.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * log.c 4 | * 5 | * Logging support for the system catalog integrity checker. This vaguely 6 | * parallel's the backend and pg_upgrade, but with some foibles specific 7 | * to our particular needs. 8 | * 9 | *------------------------------------------------------------------------- 10 | */ 11 | 12 | #include "postgres_fe.h" 13 | 14 | #include "pg_catcheck.h" 15 | 16 | bool quiet = false; /* Don't display progress messages. */ 17 | int verbose = 0; /* 1 = verbose messages; 2+ = debug messages */ 18 | 19 | static int notices = 0; 20 | static int warnings = 0; 21 | static int errors = 0; 22 | static pgcc_severity highest_message_severity = PGCC_DEBUG; 23 | 24 | static bool pgcc_log_severity(pgcc_severity sev); 25 | 26 | /* 27 | * Log a message. We write messages of level NOTICE and below to standard 28 | * output; anything higher goes to standard error. 29 | */ 30 | void 31 | pgcc_log(pgcc_severity sev, char *fmt,...) 32 | { 33 | va_list args; 34 | 35 | if (!pgcc_log_severity(sev)) 36 | return; 37 | 38 | va_start(args, fmt); 39 | if (sev <= PGCC_NOTICE) 40 | vfprintf(stdout, fmt, args); 41 | else 42 | vfprintf(stderr, fmt, args); 43 | va_end(args); 44 | 45 | if (sev >= PGCC_FATAL) 46 | exit(2); 47 | } 48 | 49 | /* 50 | * Report a catalog inconsistency; this always uses level PGCC_NOTICE. 51 | */ 52 | void 53 | pgcc_report(pg_catalog_table *tab, pg_catalog_column *tabcol, 54 | int rownum, char *fmt,...) 55 | { 56 | va_list args; 57 | pg_catalog_column *displaytabcol; 58 | bool first = true; 59 | 60 | if (!pgcc_log_severity(PGCC_NOTICE)) 61 | return; 62 | if (tabcol != NULL) 63 | printf("%s row has invalid %s \"%s\": ", tab->table_name, tabcol->name, 64 | PQgetvalue(tab->data, rownum, tabcol->result_column)); 65 | 66 | va_start(args, fmt); 67 | vfprintf(stdout, fmt, args); 68 | va_end(args); 69 | 70 | for (displaytabcol = tab->cols; 71 | displaytabcol->name != NULL; 72 | ++displaytabcol) 73 | { 74 | if (displaytabcol->is_display_column) 75 | { 76 | printf("%s%s=\"%s\"", first ? 77 | "row identity: " : " ", displaytabcol->name, 78 | PQgetvalue(tab->data, rownum, displaytabcol->result_column)); 79 | first = false; 80 | } 81 | } 82 | if (!first) 83 | printf("\n"); 84 | } 85 | 86 | /* 87 | * Report that we have completed our checks, and exit with an appropriate 88 | * status code. 89 | */ 90 | void 91 | pgcc_log_completion(void) 92 | { 93 | pgcc_log(PGCC_PROGRESS, 94 | "done (%d inconsistencies, %d warnings, %d errors)\n", 95 | notices, warnings, errors); 96 | if (highest_message_severity > PGCC_NOTICE) 97 | exit(2); 98 | else if (highest_message_severity == PGCC_NOTICE) 99 | exit(1); 100 | exit(0); 101 | } 102 | 103 | /* 104 | * Common code for pgcc_log() and pgcc_report(). 105 | * 106 | * Determine whether a message of the indicated severity should be logged 107 | * given the command-line options specified by the user, and print out the 108 | * severity level on standard output or standard error as appropriate, so that 109 | * it prefixes the message. 110 | * 111 | * Also updates statistics on what messages have been logged so that 112 | * pgcc_log_completion() can make use of that information. 113 | */ 114 | static bool 115 | pgcc_log_severity(pgcc_severity sev) 116 | { 117 | switch (sev) 118 | { 119 | case PGCC_DEBUG: 120 | if (verbose < 2) 121 | return false; 122 | printf("debug: "); 123 | break; 124 | case PGCC_VERBOSE: 125 | if (verbose < 1) 126 | return false; 127 | printf("verbose: "); 128 | break; 129 | case PGCC_PROGRESS: 130 | if (quiet) 131 | return false; 132 | printf("progress: "); 133 | break; 134 | case PGCC_NOTICE: 135 | printf("notice: "); 136 | break; 137 | case PGCC_WARNING: 138 | fprintf(stderr, "warning: "); 139 | break; 140 | case PGCC_ERROR: 141 | fprintf(stderr, "error: "); 142 | break; 143 | case PGCC_FATAL: 144 | fprintf(stderr, "fatal: "); 145 | break; 146 | } 147 | 148 | if (sev == PGCC_NOTICE) 149 | ++notices; 150 | if (sev == PGCC_WARNING) 151 | ++warnings; 152 | if (sev == PGCC_ERROR) 153 | ++errors; 154 | 155 | if (sev >= highest_message_severity) 156 | highest_message_severity = sev; 157 | 158 | return true; 159 | } 160 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | pg_catcheck_sources = files( 2 | 'pg_catcheck.c', 3 | 'check_attribute.c', 4 | 'check_class.c', 5 | 'check_depend.c', 6 | 'check_oids.c', 7 | 'compat.c', 8 | 'definitions.c', 9 | 'log.c', 10 | 'pgrhash.c', 11 | 'select_from_relations.c', 12 | ) 13 | 14 | if host_system == 'windows' 15 | pg_catcheck_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ 16 | '--NAME', 'pg_catcheck', 17 | '--FILEDESC', 'pg_catcheck - system catalog integrity checker',]) 18 | endif 19 | 20 | pg_catcheck = executable('pg_catcheck', 21 | pg_catcheck_sources, 22 | dependencies: [frontend_code, libpq], 23 | kwargs: default_bin_args, 24 | ) 25 | 26 | contrib_targets += pg_catcheck 27 | -------------------------------------------------------------------------------- /pg_catcheck.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * pg_catcheck.c 4 | * 5 | * Driver code for the system catalog integrity checker. Options parsing 6 | * logic as well as code to connect to the database and build and execute 7 | * SQL queries live here, as does other management code that is used to 8 | * plan and drive the flow of the checks. The checks themselves, however, 9 | * are not defined here. 10 | * 11 | * This tool only attempts to detect logical errors (like a dependency in 12 | * pg_depend that points to a non-existent logic), not lower-level 13 | * corruption scenarios (like an index that doesn't match the table). 14 | * Nevertheless, we attempt to be resilient against the possible presence 15 | * of such scenarios by issuing just one query per table fetching only 16 | * the columns we need, and continuing on so far as possible even if some 17 | * queries fail. 18 | * 19 | *------------------------------------------------------------------------- 20 | */ 21 | 22 | #include "postgres_fe.h" 23 | 24 | #include "getopt_long.h" 25 | #include "pqexpbuffer.h" 26 | #include "libpq-fe.h" 27 | 28 | #include "pg_catcheck.h" 29 | #include 30 | 31 | #if PG_VERSION_NUM >= 140000 32 | #include "common/string.h" 33 | #endif 34 | 35 | extern char *optarg; 36 | extern int optind; 37 | 38 | /* variable definitions */ 39 | static char *pghost = ""; 40 | static char *pgport = ""; 41 | static char *login = NULL; 42 | static char *dbName; 43 | static const char *progname; 44 | int remote_version; 45 | bool remote_is_edb; 46 | char *database_oid; 47 | static bool select_from_relations = false; 48 | 49 | #define MINIMUM_SUPPORTED_VERSION 80400 50 | 51 | /* Static functions */ 52 | static int parse_target_version(char *version); 53 | static void select_column(char *column_name, enum trivalue whether); 54 | static void select_table(char *table_name, enum trivalue whether); 55 | static PGconn *do_connect(void); 56 | static void decide_what_to_check(bool selected_columns); 57 | static void perform_checks(PGconn *conn); 58 | static void load_table(PGconn *conn, pg_catalog_table *tab); 59 | static void check_table(PGconn *conn, pg_catalog_table *tab); 60 | static PQExpBuffer build_query_for_table(pg_catalog_table *tab); 61 | static void build_hash_from_query_results(pg_catalog_table *tab); 62 | static void usage(void); 63 | static char *get_database_oid(PGconn *conn); 64 | 65 | #if PG_VERSION_NUM >= 90200 66 | static void load_check_by_singlerow(PGconn *conn, pg_catalog_table *tab, 67 | PQExpBuffer query); 68 | #endif 69 | 70 | /* 71 | * Main program. 72 | */ 73 | int 74 | main(int argc, char **argv) 75 | { 76 | static struct option long_options[] = { 77 | /* systematic long/short named options */ 78 | {"host", required_argument, NULL, 'h'}, 79 | {"port", required_argument, NULL, 'p'}, 80 | {"username", required_argument, NULL, 'U'}, 81 | {"table", required_argument, NULL, 't'}, 82 | {"column", required_argument, NULL, 'c'}, 83 | {"quiet", no_argument, NULL, 'q'}, 84 | {"verbose", no_argument, NULL, 'v'}, 85 | {"select-from-relations", no_argument, NULL, 105}, 86 | {"target-version", required_argument, NULL, 101}, 87 | {"enterprisedb", no_argument, NULL, 102}, 88 | {"postgresql", no_argument, NULL, 103}, 89 | {NULL, 0, NULL, 0} 90 | }; 91 | 92 | int c; 93 | int optindex; 94 | char *env; 95 | PGconn *conn; 96 | int target_version = 0; 97 | bool detect_edb = true; 98 | bool selected_columns = false; 99 | 100 | progname = get_progname(argv[0]); 101 | 102 | if (argc > 1) 103 | { 104 | if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) 105 | { 106 | usage(); 107 | exit(0); 108 | } 109 | if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) 110 | { 111 | puts("pg_catcheck (EnterpriseDB) " PG_VERSION); 112 | exit(0); 113 | } 114 | } 115 | 116 | #ifdef WIN32 117 | /* stderr is buffered on Win32. */ 118 | setvbuf(stderr, NULL, _IONBF, 0); 119 | #endif 120 | 121 | if ((env = getenv("PGHOST")) != NULL && *env != '\0') 122 | pghost = env; 123 | if ((env = getenv("PGPORT")) != NULL && *env != '\0') 124 | pgport = env; 125 | else if ((env = getenv("PGUSER")) != NULL && *env != '\0') 126 | login = env; 127 | 128 | while ((c = getopt_long(argc, argv, "h:p:U:t:T:g:c:C:qv", long_options, &optindex)) != -1) 129 | { 130 | switch (c) 131 | { 132 | case 'h': 133 | pghost = pg_strdup(optarg); 134 | break; 135 | case 'p': 136 | pgport = pg_strdup(optarg); 137 | break; 138 | case 'c': 139 | select_column(optarg, TRI_YES); 140 | selected_columns = true; 141 | break; 142 | case 'C': 143 | select_column(optarg, TRI_NO); 144 | break; 145 | case 't': 146 | select_table(optarg, TRI_YES); 147 | selected_columns = true; 148 | break; 149 | case 'T': 150 | select_table(optarg, TRI_NO); 151 | break; 152 | case 'U': 153 | login = pg_strdup(optarg); 154 | break; 155 | case 'q': 156 | quiet = true; 157 | break; 158 | case 'v': 159 | ++verbose; 160 | break; 161 | case 101: 162 | target_version = parse_target_version(optarg); 163 | break; 164 | case 102: 165 | remote_is_edb = true; 166 | detect_edb = false; 167 | break; 168 | case 103: 169 | remote_is_edb = false; 170 | detect_edb = false; 171 | break; 172 | case 105: 173 | select_from_relations = true; 174 | break; 175 | default: 176 | fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); 177 | exit(1); 178 | break; 179 | } 180 | } 181 | 182 | if (argc > optind) 183 | dbName = argv[optind++]; 184 | else 185 | { 186 | if ((env = getenv("PGDATABASE")) != NULL && *env != '\0') 187 | dbName = env; 188 | else if (login != NULL && *login != '\0') 189 | dbName = login; 190 | else 191 | dbName = ""; 192 | } 193 | 194 | /* Complain if any arguments remain */ 195 | if (optind < argc) 196 | { 197 | fprintf(stderr, _("%s: too many command-line arguments (first is \"%s\")\n"), 198 | progname, argv[optind]); 199 | fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); 200 | exit(1); 201 | } 202 | 203 | /* opening connection... */ 204 | conn = do_connect(); 205 | if (conn == NULL) 206 | exit(1); 207 | 208 | if (PQstatus(conn) == CONNECTION_BAD) 209 | pgcc_log(PGCC_FATAL, "could not connect to server: %s", 210 | PQerrorMessage(conn)); 211 | 212 | /* Detect remote version, or user user-specified value. */ 213 | if (target_version == 0) 214 | { 215 | remote_version = PQserverVersion(conn); 216 | pgcc_log(PGCC_VERBOSE, "detected server version %d\n", 217 | remote_version); 218 | } 219 | else 220 | { 221 | remote_version = target_version; 222 | pgcc_log(PGCC_VERBOSE, "assuming server version %d\n", 223 | remote_version); 224 | } 225 | 226 | /* Warn that we don't support checking really old versions. */ 227 | if (remote_version < MINIMUM_SUPPORTED_VERSION) 228 | pgcc_log(PGCC_WARNING, "server version (%d) is older than the minimum version supported by this tool (%d)\n", 229 | remote_version, MINIMUM_SUPPORTED_VERSION); 230 | 231 | /* 232 | * If neither --enterprisedb nor --postgresql was specified, attempt to 233 | * detect which type of database we're accessing. 234 | */ 235 | if (detect_edb) 236 | { 237 | PGresult *res; 238 | 239 | res = PQexec(conn, "select strpos(version(), 'EnterpriseDB')"); 240 | if (PQresultStatus(res) != PGRES_TUPLES_OK) 241 | { 242 | pgcc_log(PGCC_ERROR, "query failed: %s", PQerrorMessage(conn)); 243 | PQclear(res); 244 | pgcc_log(PGCC_FATAL, 245 | "Please use --enterprisedb or --postgresql to specify the database type.\n"); 246 | } 247 | remote_is_edb = atol(PQgetvalue(res, 0, 0)); 248 | if (remote_is_edb) 249 | pgcc_log(PGCC_VERBOSE, "detected EnterpriseDB server\n"); 250 | else 251 | pgcc_log(PGCC_VERBOSE, "detected PostgreSQL server\n"); 252 | PQclear(res); 253 | } 254 | else 255 | { 256 | if (remote_is_edb) 257 | pgcc_log(PGCC_VERBOSE, "assuming EnterpriseDB server\n"); 258 | else 259 | pgcc_log(PGCC_VERBOSE, "assuming PostgreSQL server\n"); 260 | } 261 | 262 | /* 263 | * At this point, we know the database version and flavor that we'll be 264 | * checking and can fix the list of columns to be checked. 265 | */ 266 | decide_what_to_check(selected_columns); 267 | 268 | /* Cache the OID of the current database, if possible. */ 269 | database_oid = get_database_oid(conn); 270 | 271 | /* Run the checks. */ 272 | perform_checks(conn); 273 | 274 | /* Cleanup */ 275 | PQfinish(conn); 276 | pgcc_log_completion(); 277 | 278 | return 0; 279 | } 280 | 281 | /* 282 | * Parse the target version string. 283 | * 284 | * We expect either something of the form MAJOR.MINOR or else a single number 285 | * in the format used by PQremoteVersion(). 286 | */ 287 | static int 288 | parse_target_version(char *version) 289 | { 290 | int major = 0; 291 | int minor = 0; 292 | char *s; 293 | 294 | /* Try to parse major version number. */ 295 | for (s = version; *s >= '0' && *s <= '9'; ++s) 296 | major = (major * 10) + (*s - '0'); 297 | 298 | /* 299 | * If we found a 5+ digit number, assume it's in PQremoteVersion() format 300 | * and call it good. 301 | */ 302 | if (*s == '\0' && major >= 10000) 303 | return major; 304 | 305 | /* Expecting a period and then a minor version number. */ 306 | if (*s != '.') 307 | goto bad; 308 | for (++s; *s >= '0' && *s <= '9'; ++s) 309 | minor = (minor * 10) + (*s - '0'); 310 | 311 | /* And now we're expecting end of string. */ 312 | if (*s != '\0') 313 | goto bad; 314 | return (major * 10000) + (minor * 100); 315 | 316 | bad: 317 | fprintf(stderr, _("%s: invalid argument for option --target-version\n"), 318 | progname); 319 | fprintf(stderr, _("Target version should be formatted as MAJOR.MINOR.\n")); 320 | exit(1); 321 | } 322 | 323 | /* 324 | * Select or deselect the named table. 325 | */ 326 | static void 327 | select_table(char *table_name, enum trivalue whether) 328 | { 329 | pg_catalog_table *tab; 330 | int nmatched = 0; 331 | 332 | for (tab = pg_catalog_tables; tab->table_name != NULL; ++tab) 333 | { 334 | if (strcmp(table_name, tab->table_name) != 0) 335 | continue; 336 | ++nmatched; 337 | tab->checked = whether; 338 | } 339 | 340 | if (nmatched == 0) 341 | pgcc_log(PGCC_FATAL, "table name \"%s\" not recognized\n", 342 | table_name); 343 | } 344 | 345 | /* 346 | * Select or deselect the named column. 347 | * 348 | * FIXME: Allow table_name.column_name syntax here. 349 | */ 350 | static void 351 | select_column(char *column_name, enum trivalue whether) 352 | { 353 | pg_catalog_table *tab; 354 | int nmatched = 0; 355 | 356 | for (tab = pg_catalog_tables; tab->table_name != NULL; ++tab) 357 | { 358 | pg_catalog_column *tabcol; 359 | 360 | for (tabcol = tab->cols; tabcol->name != NULL; ++tabcol) 361 | { 362 | if (strcmp(column_name, tabcol->name) == 0) 363 | { 364 | tabcol->checked = whether; 365 | ++nmatched; 366 | } 367 | } 368 | } 369 | 370 | if (nmatched == 0) 371 | pgcc_log(PGCC_FATAL, "column name \"%s\" not recognized\n", 372 | column_name); 373 | } 374 | 375 | /* 376 | * Given a table name, find the corresponding pg_catalog_table structure. 377 | */ 378 | pg_catalog_table * 379 | find_table_by_name(char *table_name) 380 | { 381 | pg_catalog_table *tab; 382 | 383 | for (tab = pg_catalog_tables; tab->table_name != NULL; ++tab) 384 | if (strcmp(tab->table_name, table_name) == 0) 385 | return tab; 386 | 387 | pgcc_log(PGCC_FATAL, "no metadata found for table %s\n", table_name); 388 | return NULL; /* placate compiler */ 389 | } 390 | 391 | /* 392 | * Given a table and a column name, find the pg_catalog_column structure. 393 | */ 394 | pg_catalog_column * 395 | find_column_by_name(pg_catalog_table *tab, char *name) 396 | { 397 | pg_catalog_column *tabcol; 398 | 399 | for (tabcol = tab->cols; tabcol->name != NULL; ++tabcol) 400 | if (strcmp(tabcol->name, name) == 0) 401 | return tabcol; 402 | 403 | pgcc_log(PGCC_FATAL, "no metadata found for column %s.%s\n", 404 | tab->table_name, name); 405 | return NULL; /* placate compiler */ 406 | } 407 | 408 | /* 409 | * Connect to the database. 410 | */ 411 | static PGconn * 412 | do_connect(void) 413 | { 414 | PGconn *conn; 415 | static char *password = NULL; 416 | bool new_pass; 417 | 418 | /* 419 | * Start the connection. Loop until we have a password if requested by 420 | * backend. 421 | */ 422 | do 423 | { 424 | #define PARAMS_ARRAY_SIZE 7 425 | 426 | const char *keywords[PARAMS_ARRAY_SIZE]; 427 | const char *values[PARAMS_ARRAY_SIZE]; 428 | 429 | keywords[0] = "host"; 430 | values[0] = pghost; 431 | keywords[1] = "port"; 432 | values[1] = pgport; 433 | keywords[2] = "user"; 434 | values[2] = login; 435 | keywords[3] = "password"; 436 | values[3] = password; 437 | keywords[4] = "dbname"; 438 | values[4] = dbName; 439 | keywords[5] = "fallback_application_name"; 440 | values[5] = progname; 441 | keywords[6] = NULL; 442 | values[6] = NULL; 443 | 444 | new_pass = false; 445 | 446 | conn = PQconnectdbParams(keywords, values, true); 447 | 448 | if (!conn) 449 | { 450 | pgcc_log(PGCC_FATAL, "could not connect to server: %s", 451 | PQerrorMessage(conn)); 452 | return NULL; 453 | } 454 | 455 | if (PQstatus(conn) == CONNECTION_BAD && 456 | PQconnectionNeedsPassword(conn) && 457 | password == NULL) 458 | { 459 | #if (PG_VERSION_NUM >= 100000 && PG_VERSION_NUM < 140000) 460 | char passbuf[100]; 461 | #endif 462 | 463 | PQfinish(conn); 464 | #if PG_VERSION_NUM >= 140000 465 | password = simple_prompt("Password: ", false); 466 | #elif PG_VERSION_NUM >= 100000 467 | simple_prompt("Password: ", passbuf, sizeof(passbuf), false); 468 | password = passbuf; 469 | #else 470 | password = simple_prompt("Password: ", 100, false); 471 | #endif 472 | new_pass = true; 473 | } 474 | } while (new_pass); 475 | 476 | /* check to see that the backend connection was successfully made */ 477 | if (PQstatus(conn) == CONNECTION_BAD) 478 | pgcc_log(PGCC_FATAL, "could not connect to server: %s", 479 | PQerrorMessage(conn)); 480 | 481 | return conn; 482 | } 483 | 484 | /* 485 | * Attempt to obtain the OID of the database being checked. 486 | */ 487 | char * 488 | get_database_oid(PGconn *conn) 489 | { 490 | PGresult *res; 491 | char *val; 492 | 493 | res = PQexec(conn, 494 | "SELECT oid FROM pg_database WHERE datname = current_database()"); 495 | if (PQresultStatus(res) != PGRES_TUPLES_OK) 496 | { 497 | char *message = PQresultErrorMessage(res); 498 | 499 | if (message != NULL && message[0] != '\0') 500 | pgcc_log(PGCC_ERROR, "could not determine database OID: %s", 501 | message); 502 | else 503 | pgcc_log(PGCC_ERROR, 504 | "could not determine database OID: unexpected status %s\n", 505 | PQresStatus(PQresultStatus(res))); 506 | return NULL; 507 | } 508 | if (PQntuples(res) != 1) 509 | { 510 | pgcc_log(PGCC_ERROR, "query for database OID returned %d values\n", 511 | PQntuples(res)); 512 | return NULL; 513 | } 514 | val = pg_strdup(PQgetvalue(res, 0, 0)); 515 | PQclear(res); 516 | 517 | pgcc_log(PGCC_DEBUG, "database OID is %s\n", val); 518 | 519 | return val; 520 | } 521 | 522 | /* 523 | * Decide which columns to check. 524 | */ 525 | static void 526 | decide_what_to_check(bool selected_columns) 527 | { 528 | pg_catalog_table *tab; 529 | 530 | /* 531 | * First pass: set "checked" flags, and tentatively set "needed" flags. 532 | */ 533 | for (tab = pg_catalog_tables; tab->table_name != NULL; ++tab) 534 | { 535 | pg_catalog_column *tabcol; 536 | 537 | tab->available = false; 538 | 539 | for (tabcol = tab->cols; tabcol->name != NULL; ++tabcol) 540 | { 541 | /* 542 | * If the user explicitly asked us to check a column we don't know 543 | * how to check, that's a usage error, so bail out. 544 | */ 545 | if (tabcol->checked == TRI_YES && tabcol->check == NULL) 546 | pgcc_log(PGCC_FATAL, 547 | "no check defined for column %s.%s\n", 548 | tab->table_name, tabcol->name); 549 | 550 | /* Decide whether this column is available. */ 551 | if (tabcol->is_edb_only && !remote_is_edb) 552 | tabcol->available = false; /* EDB column on non-EDB 553 | * database */ 554 | else if (tabcol->minimum_version && 555 | remote_version < tabcol->minimum_version) 556 | tabcol->available = false; /* DB version too old */ 557 | else if (tabcol->maximum_version && 558 | remote_version > tabcol->maximum_version) 559 | tabcol->available = false; /* DB version too new */ 560 | else 561 | tabcol->available = true; 562 | if (tabcol->available) 563 | tab->available = true; 564 | 565 | /* 566 | * If the column looks like it is not available in this version 567 | * but the user asked explicitly for that particular column, warn 568 | * them that things might not work out well. 569 | */ 570 | if (tabcol->available == false && tabcol->checked == TRI_YES) 571 | { 572 | tabcol->available = true; 573 | pgcc_log(PGCC_WARNING, 574 | "column %s.%s is not supported by this server version\n", 575 | tab->table_name, tabcol->name); 576 | } 577 | 578 | /* 579 | * If the user didn't specify whether to check the column, decide 580 | * whether or not to do so. 581 | */ 582 | if (tabcol->checked == TRI_DEFAULT) 583 | { 584 | if (tabcol->check == NULL) 585 | tabcol->checked = TRI_NO; /* no check defined */ 586 | else if (!tabcol->available) 587 | tabcol->checked = TRI_NO; /* not in this version */ 588 | else if (tab->checked != TRI_DEFAULT) 589 | tabcol->checked = tab->checked; /* use table setting */ 590 | else if (selected_columns) 591 | tabcol->checked = TRI_NO; /* only specified columns */ 592 | else 593 | tabcol->checked = TRI_YES; /* otherwise, check it */ 594 | } 595 | 596 | /* 597 | * Decide whether the column is needed, indicating whether it will 598 | * be selected when we retrieve data from the table. We exclude 599 | * columns not available in this server version, but include other 600 | * columns if they are to be checked, if they are part of the key, 601 | * or if we display them for purposes of row identification. 602 | */ 603 | if (!tabcol->available) 604 | tabcol->needed = false; 605 | else 606 | tabcol->needed = (tabcol->checked == TRI_YES) 607 | || tabcol->is_key_column || tabcol->is_display_column; 608 | } 609 | } 610 | 611 | /* Prepare for select_from_relations, if option been provided. */ 612 | if (select_from_relations) 613 | prepare_to_select_from_relations(); 614 | 615 | /* 616 | * Second pass: allow individual checks to mark additional columns as 617 | * needed, and set ordering dependencies. 618 | */ 619 | for (tab = pg_catalog_tables; tab->table_name != NULL; ++tab) 620 | { 621 | pg_catalog_column *tabcol; 622 | 623 | for (tabcol = tab->cols; tabcol->name != NULL; ++tabcol) 624 | { 625 | pg_catalog_check *check; 626 | 627 | check = tabcol->check; 628 | if (check == NULL || tabcol->checked != TRI_YES) 629 | continue; 630 | 631 | switch (check->type) 632 | { 633 | case CHECK_ATTNUM: 634 | prepare_to_check_relnatts(tab, tabcol); 635 | break; 636 | case CHECK_OID_REFERENCE: 637 | case CHECK_OID_VECTOR_REFERENCE: 638 | case CHECK_OID_ARRAY_REFERENCE: 639 | prepare_to_check_oid_reference(tab, tabcol); 640 | break; 641 | case CHECK_DEPENDENCY_CLASS_ID: 642 | prepare_to_check_dependency_class_id(tab, tabcol); 643 | break; 644 | case CHECK_DEPENDENCY_ID: 645 | prepare_to_check_dependency_id(tab, tabcol); 646 | break; 647 | case CHECK_DEPENDENCY_SUBID: 648 | prepare_to_check_dependency_subid(tab, tabcol); 649 | break; 650 | case CHECK_RELNATTS: 651 | prepare_to_check_relnatts(tab, tabcol); 652 | break; 653 | } 654 | } 655 | } 656 | 657 | } 658 | 659 | /* 660 | * Set up metadata that will be needed to choose an order in which to check 661 | * the tables. 662 | */ 663 | static void 664 | perform_checks(PGconn *conn) 665 | { 666 | pg_catalog_table *tab; 667 | 668 | /* Initialize the table check states. */ 669 | for (tab = pg_catalog_tables; tab->table_name != NULL; ++tab) 670 | { 671 | pg_catalog_column *tabcol; 672 | 673 | if (tab->num_needed_by != 0) 674 | tab->needs_load = true; 675 | 676 | for (tabcol = tab->cols; tabcol->name != NULL; ++tabcol) 677 | { 678 | if (tabcol->needed) 679 | tab->needs_load = true; 680 | if (tabcol->checked == TRI_YES) 681 | { 682 | Assert(tab->needs_load); 683 | tab->needs_check = true; 684 | break; 685 | } 686 | } 687 | } 688 | 689 | /* Loop until all checks are complete. */ 690 | for (;;) 691 | { 692 | pg_catalog_table *best = NULL; 693 | int remaining = 0; 694 | 695 | /* 696 | * Search for tables that can be checked without loading any more data 697 | * from the database. If we find any, check them. Along the way, 698 | * keep a count of the number of tables remaining to be checked. 699 | */ 700 | for (tab = pg_catalog_tables; tab->table_name != NULL; ++tab) 701 | { 702 | if (tab->needs_check && !tab->needs_load && tab->num_needs == 0) 703 | check_table(conn, tab); 704 | if (tab->needs_check) 705 | ++remaining; 706 | } 707 | 708 | /* If no tables remain to be checked, we're done. */ 709 | if (remaining == 0) 710 | break; 711 | 712 | /* 713 | * There are tables that remain to be checked, but none of them can be 714 | * checked without reading data from the database. Choose one which 715 | * requires preloading the fewest tables; in case of a tie, prefer the 716 | * one required by the most yet-to-be-checked tables, in the hopes of 717 | * unblocking as many other checks as possible. 718 | */ 719 | for (tab = pg_catalog_tables; tab->table_name != NULL; ++tab) 720 | { 721 | if (!tab->needs_check) 722 | continue; 723 | if (best == NULL || tab->num_needs < best->num_needs || 724 | (tab->num_needs == best->num_needs 725 | && tab->num_needed_by > best->num_needed_by)) 726 | best = tab; 727 | } 728 | Assert(best != NULL); 729 | 730 | /* If the selected candidate needs other tables preloaded, do that. */ 731 | while (best->num_needs > 0) 732 | { 733 | pg_catalog_table *reftab = best->needs[best->num_needs - 1]; 734 | int old_num_needs PG_USED_FOR_ASSERTS_ONLY = best->num_needs; 735 | 736 | if (!reftab->needs_load) 737 | continue; 738 | pgcc_log(PGCC_VERBOSE, 739 | "preloading table %s because it is required in order to check %s\n", 740 | reftab->table_name, best->table_name); 741 | load_table(conn, reftab); 742 | Assert(old_num_needs > best->num_needs); 743 | } 744 | 745 | /* Load the table itself, if it isn't already. */ 746 | if (best->needs_load) 747 | { 748 | pgcc_log(PGCC_VERBOSE, "loading table %s\n", 749 | best->table_name); 750 | load_table(conn, best); 751 | } 752 | 753 | /* Check the table. */ 754 | if (best->needs_check) 755 | check_table(conn, best); 756 | } 757 | 758 | /* Check select-from-relations */ 759 | if (select_from_relations) 760 | perform_select_from_relations(conn); 761 | } 762 | 763 | #if PG_VERSION_NUM >= 90200 764 | static void 765 | /* 766 | * load_check_by_singlerow() loads one row at a time and perform check_table() 767 | * immediately. This mode does not create hash for the table's key columns and 768 | * hence should be called only for tables that do not have any dependees. 769 | */ 770 | load_check_by_singlerow(PGconn *conn, pg_catalog_table *tab, PQExpBuffer query) 771 | { 772 | PGresult *res = NULL; 773 | int ntups = 0; 774 | 775 | if (PQsendQuery(conn, query->data) != 1) 776 | { 777 | pgcc_log(PGCC_ERROR, "could not send query for table %s: %s\n", 778 | tab->table_name, PQerrorMessage(conn)); 779 | goto exit; 780 | } 781 | 782 | /* Set single-row mode */ 783 | if (PQsetSingleRowMode(conn) != 1) 784 | { 785 | pgcc_log(PGCC_ERROR, "could not set libpq connection to single " 786 | "row mode for table %s\n", tab->table_name); 787 | goto exit; 788 | } 789 | 790 | while ((res = PQgetResult(conn)) != NULL) 791 | { 792 | if (PQresultStatus(res) == PGRES_SINGLE_TUPLE) 793 | { 794 | ntups++; 795 | tab->data = res; 796 | check_table(conn, tab); 797 | } 798 | PQclear(res); 799 | } 800 | 801 | pgcc_log(PGCC_VERBOSE, "checked table %s (%d rows)\n", tab->table_name, 802 | ntups); 803 | 804 | exit: 805 | tab->needs_check = false; 806 | } 807 | #endif 808 | 809 | /* 810 | * Load a table into memory. 811 | */ 812 | static void 813 | load_table(PGconn *conn, pg_catalog_table *tab) 814 | { 815 | PQExpBuffer query; 816 | bool need_load = true; 817 | int i; 818 | 819 | Assert(tab->needs_load); 820 | 821 | /* Load the table data. */ 822 | query = build_query_for_table(tab); 823 | pgcc_log(PGCC_DEBUG, "executing query: %s\n", query->data); 824 | 825 | #if PG_VERSION_NUM >= 90200 826 | /* 827 | * If this table is not needed by any other table, then we won't need to 828 | * refer back any given row after it's processed, so we can load them one 829 | * at a time to reduce memory consumption. However, we build a special 830 | * hash table over the contents of pg_shdepend (duplicate_owner_ht) and 831 | * therefore cannot use row-at-at-time mode for that table. 832 | */ 833 | if (tab->num_needed_by == 0 && strcmp(tab->table_name, "pg_shdepend") != 0) 834 | { 835 | load_check_by_singlerow(conn, tab, query); 836 | need_load = false; 837 | } 838 | #endif 839 | 840 | if (need_load) 841 | { 842 | tab->data = PQexec(conn, query->data); 843 | if (PQresultStatus(tab->data) != PGRES_TUPLES_OK) 844 | { 845 | char *message = PQresultErrorMessage(tab->data); 846 | 847 | if (message != NULL && message[0] != '\0') 848 | pgcc_log(PGCC_ERROR, "could not load table %s: %s", 849 | tab->table_name, message); 850 | else 851 | pgcc_log(PGCC_ERROR, 852 | "could not load table %s: unexpected status %s\n", 853 | tab->table_name, PQresStatus(PQresultStatus(tab->data))); 854 | } 855 | else 856 | build_hash_from_query_results(tab); 857 | } 858 | 859 | destroyPQExpBuffer(query); 860 | 861 | /* This table is now loaded. */ 862 | tab->needs_load = false; 863 | 864 | /* Any other tables that neeed this table no longer do. */ 865 | for (i = 0; i < tab->num_needed_by; ++i) 866 | { 867 | pg_catalog_table *reftab = tab->needed_by[i]; 868 | int j, 869 | k; 870 | 871 | for (j = 0, k = 0; j < reftab->num_needs; ++j) 872 | { 873 | reftab->needs[k] = reftab->needs[j]; 874 | if (tab != reftab->needs[j]) 875 | ++k; 876 | } 877 | Assert(k == reftab->num_needs - 1); 878 | reftab->num_needs = k; 879 | } 880 | 881 | /* We can throw away our side of the dependency information as well. */ 882 | tab->num_needed_by = 0; 883 | if (tab->num_needed_by_allocated > 0) 884 | { 885 | pg_free(tab->needed_by); 886 | tab->num_needed_by_allocated = 0; 887 | } 888 | } 889 | 890 | /* 891 | * Perform integrity checks on a table. 892 | */ 893 | static void 894 | check_table(PGconn *conn, pg_catalog_table *tab) 895 | { 896 | int i; 897 | int ntups; 898 | 899 | /* Once we've tried to check the table, we shouldn't try again. */ 900 | tab->needs_check = false; 901 | Assert(tab->data != NULL); 902 | 903 | /* 904 | * If we weren't able to retrieve the table data, then we can't check the 905 | * table. But there's no real need to log the error message, because 906 | * load_table() will have already done so. 907 | */ 908 | #if PG_VERSION_NUM >= 90200 909 | /* The table could be loaded either in single-row mode or bulk mode */ 910 | if (PQresultStatus(tab->data) != PGRES_TUPLES_OK && 911 | PQresultStatus(tab->data) != PGRES_SINGLE_TUPLE) 912 | return; 913 | #else 914 | if (PQresultStatus(tab->data) != PGRES_TUPLES_OK) 915 | return; 916 | #endif 917 | 918 | /* 919 | * Log a message, if verbose mode is enabled. If the table was loaded in 920 | * single-row mode the caller takes care of logging the total row count. 921 | */ 922 | ntups = PQntuples(tab->data); 923 | 924 | if (PQresultStatus(tab->data) == PGRES_TUPLES_OK) 925 | pgcc_log(PGCC_VERBOSE, "checking table %s (%d rows)\n", tab->table_name, 926 | ntups); 927 | 928 | /* Loop over the rows and check them. */ 929 | for (i = 0; i < ntups; ++i) 930 | { 931 | pg_catalog_column *tabcol; 932 | 933 | for (tabcol = tab->cols; tabcol->name != NULL; ++tabcol) 934 | { 935 | pg_catalog_check *check; 936 | 937 | if (tabcol->checked != TRI_YES || tabcol->check == NULL) 938 | continue; 939 | check = tabcol->check; 940 | 941 | switch (check->type) 942 | { 943 | case CHECK_ATTNUM: 944 | check_attnum(tab, tabcol, i); 945 | break; 946 | case CHECK_OID_REFERENCE: 947 | case CHECK_OID_VECTOR_REFERENCE: 948 | case CHECK_OID_ARRAY_REFERENCE: 949 | check_oid_reference(tab, tabcol, i); 950 | break; 951 | case CHECK_DEPENDENCY_CLASS_ID: 952 | check_dependency_class_id(tab, tabcol, i); 953 | break; 954 | case CHECK_DEPENDENCY_ID: 955 | check_dependency_id(tab, tabcol, i); 956 | break; 957 | case CHECK_DEPENDENCY_SUBID: 958 | check_dependency_subid(tab, tabcol, i); 959 | break; 960 | case CHECK_RELNATTS: 961 | check_relnatts(tab, tabcol, i); 962 | break; 963 | } 964 | } 965 | } 966 | } 967 | 968 | /* 969 | * Build a hash table on the key columns of the catalog table contents. 970 | */ 971 | static void 972 | build_hash_from_query_results(pg_catalog_table *tab) 973 | { 974 | int i; 975 | pg_catalog_column *tabcol; 976 | pgrhash *ht; 977 | int keycols[MAX_KEY_COLS]; 978 | int nkeycols = 0; 979 | int ntups = PQntuples(tab->data); 980 | 981 | for (tabcol = tab->cols; tabcol->name != NULL; ++tabcol) 982 | if (tabcol->available && tabcol->is_key_column) 983 | keycols[nkeycols++] = PQfnumber(tab->data, tabcol->name); 984 | 985 | /* 986 | * Tables like pg_depend get loaded so that we can check them, but they 987 | * don't have a primary key, so we don't build a hash table. 988 | */ 989 | if (nkeycols == 0) 990 | return; 991 | 992 | /* Create the hash table. */ 993 | ht = tab->ht = pgrhash_create(tab->data, nkeycols, keycols); 994 | 995 | for (i = 0; i < ntups; i++) 996 | if (pgrhash_insert(ht, i) != -1) 997 | pgcc_report(tab, NULL, i, "%s row duplicates existing key\n", 998 | tab->table_name); 999 | } 1000 | 1001 | /* 1002 | * Build a query to read the needed columns from a table. 1003 | */ 1004 | static PQExpBuffer 1005 | build_query_for_table(pg_catalog_table *tab) 1006 | { 1007 | PQExpBuffer query; 1008 | pg_catalog_column *tabcol; 1009 | int index = 0; 1010 | 1011 | query = createPQExpBuffer(); 1012 | 1013 | appendPQExpBuffer(query, "SELECT"); 1014 | for (tabcol = tab->cols; tabcol->name != NULL; ++tabcol) 1015 | { 1016 | if (!tabcol->needed) 1017 | continue; 1018 | if (index == 0) 1019 | appendPQExpBuffer(query, " %s", tabcol->name); 1020 | else 1021 | appendPQExpBuffer(query, ", %s", tabcol->name); 1022 | if (tabcol->cast) 1023 | appendPQExpBuffer(query, "::%s", tabcol->cast); 1024 | 1025 | /* Remember where this column is supposed to be in the output. */ 1026 | tabcol->result_column = index; 1027 | index++; 1028 | } 1029 | 1030 | Assert(index > 0); 1031 | 1032 | appendPQExpBuffer(query, " FROM pg_catalog.%s", tab->table_name); 1033 | 1034 | return query; 1035 | } 1036 | 1037 | /* 1038 | * Indicate that one table ("needs") requires that another table ("needed_by") 1039 | * be loaded before it is checked. 1040 | */ 1041 | void 1042 | add_table_dependency(pg_catalog_table *needs, pg_catalog_table *needed_by) 1043 | { 1044 | int i; 1045 | 1046 | if (!needs->available || !needed_by->available) 1047 | return; 1048 | 1049 | /* 1050 | * We necessarily load tables before checking them, so there's no point in 1051 | * a circular dependency. 1052 | */ 1053 | if (needs == needed_by) 1054 | return; 1055 | 1056 | pgcc_log(PGCC_DEBUG, "table %s depends on table %s\n", 1057 | needs->table_name, needed_by->table_name); 1058 | 1059 | /* Check whether the dependency is already present; if so, do nothing. */ 1060 | for (i = 0; i < needs->num_needs; ++i) 1061 | if (needs->needs[i] == needed_by) 1062 | return; 1063 | 1064 | /* Make sure there's enough space to store the new dependency. */ 1065 | if (needs->num_needs >= needs->num_needs_allocated) 1066 | { 1067 | if (needs->num_needs_allocated == 0) 1068 | { 1069 | needs->num_needs_allocated = 4; 1070 | needs->needs = 1071 | pg_malloc(needs->num_needs_allocated * sizeof(pg_catalog_table *)); 1072 | } 1073 | else 1074 | { 1075 | needs->num_needs_allocated *= 2; 1076 | needs->needs = pg_realloc(needs->needs, 1077 | needs->num_needs_allocated * sizeof(pg_catalog_table *)); 1078 | } 1079 | } 1080 | if (needed_by->num_needed_by >= needed_by->num_needed_by_allocated) 1081 | { 1082 | if (needed_by->num_needed_by_allocated == 0) 1083 | { 1084 | needed_by->num_needed_by_allocated = 4; 1085 | needed_by->needed_by = 1086 | pg_malloc(needed_by->num_needed_by_allocated * 1087 | sizeof(pg_catalog_table *)); 1088 | } 1089 | else 1090 | { 1091 | needed_by->num_needed_by_allocated *= 2; 1092 | needed_by->needed_by = pg_realloc(needed_by->needed_by, 1093 | needed_by->num_needed_by_allocated * sizeof(pg_catalog_table *)); 1094 | } 1095 | } 1096 | 1097 | /* Add the dependency. */ 1098 | needs->needs[needs->num_needs] = needed_by; 1099 | ++needs->num_needs; 1100 | needed_by->needed_by[needed_by->num_needed_by] = needs; 1101 | ++needed_by->num_needed_by; 1102 | } 1103 | 1104 | /* 1105 | * Print a usage message and exit. 1106 | */ 1107 | static void 1108 | usage(void) 1109 | { 1110 | printf("%s is catalog table validation tool for PostgreSQL.\n\n", progname); 1111 | printf("Usage:\n %s [OPTION]... [DBNAME]\n\n", progname); 1112 | printf("Options:\n"); 1113 | printf(" -c, --column check only the named columns\n"); 1114 | printf(" -t, --table check only columns in the named tables\n"); 1115 | printf(" -T, --exclude-table do NOT check the named tables\n"); 1116 | printf(" -C, --exclude-column do NOT check the named columns\n"); 1117 | printf(" --select-from-relations execute the SELECT on relations in the database\n"); 1118 | printf(" --target-version=VERSION assume specified target version\n"); 1119 | printf(" --enterprisedb assume EnterpriseDB database\n"); 1120 | printf(" --postgresql assume PostgreSQL database\n"); 1121 | printf(" -h, --host=HOSTNAME database server host or socket directory\n"); 1122 | printf(" -p, --port=PORT database server port number\n"); 1123 | printf(" -q, --quiet do not display progress messages\n"); 1124 | printf(" -U, --username=USERNAME connect as specified database user\n"); 1125 | printf(" -v, --verbose enable verbose internal logging\n"); 1126 | printf(" -V, --version output version information, then exit\n"); 1127 | printf(" -?, --help show this help, then exit\n"); 1128 | printf("\nReport bugs to .\n"); 1129 | } 1130 | -------------------------------------------------------------------------------- /pg_catcheck.h: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * pg_catcheck.h 4 | * 5 | * Functions and data structures for catalog integrity checking. 6 | * 7 | *------------------------------------------------------------------------- 8 | */ 9 | 10 | #ifndef PGCATCHECK_H 11 | #define PGCATCHECK_H 12 | 13 | #include "libpq-fe.h" /* for PGresult */ 14 | #include "compat.h" 15 | 16 | /* Forward declarations. */ 17 | struct pgrhash; 18 | typedef struct pgrhash pgrhash; 19 | struct pg_catalog_table; 20 | typedef struct pg_catalog_table pg_catalog_table; 21 | 22 | /* Tri-value logic for handling table and column selection. */ 23 | enum trivalue 24 | { 25 | TRI_DEFAULT, 26 | TRI_NO, 27 | TRI_YES 28 | }; 29 | 30 | /* Defined check types. */ 31 | typedef enum checktype 32 | { 33 | CHECK_ATTNUM, 34 | CHECK_OID_REFERENCE, 35 | CHECK_OID_VECTOR_REFERENCE, 36 | CHECK_OID_ARRAY_REFERENCE, 37 | CHECK_DEPENDENCY_CLASS_ID, 38 | CHECK_DEPENDENCY_ID, 39 | CHECK_DEPENDENCY_SUBID, 40 | CHECK_RELNATTS, 41 | } checktype; 42 | 43 | /* Generic catalog check structure. */ 44 | typedef struct pg_catalog_check 45 | { 46 | checktype type; 47 | } pg_catalog_check; 48 | 49 | /* Specialization of pg_catalog_check for the various types of OID checks. */ 50 | typedef struct pg_catalog_check_oid 51 | { 52 | checktype type; 53 | bool zero_oid_ok; 54 | char *oid_references_table; 55 | } pg_catalog_check_oid; 56 | 57 | 58 | /* Everything we need to check a catalog column. */ 59 | typedef struct pg_catalog_column 60 | { 61 | /* These columns are listed in definitions.c */ 62 | char *name; 63 | char *cast; 64 | int minimum_version; 65 | int maximum_version; 66 | bool is_edb_only; 67 | bool is_key_column; 68 | bool is_display_column; 69 | void *check; /* some kind of pg_catalog_check object */ 70 | 71 | /* These columns are populated at runtime. */ 72 | bool available; 73 | enum trivalue checked; 74 | bool needed; 75 | void *check_private; /* workspace for individual checks */ 76 | int result_column; /* result column number */ 77 | } pg_catalog_column; 78 | 79 | /* Everything we need to check an entire catalog table. */ 80 | struct pg_catalog_table 81 | { 82 | /* These columns are listed in definitions.c */ 83 | char *table_name; 84 | pg_catalog_column *cols; 85 | 86 | /* These columns are populated at runtime. */ 87 | bool available; /* OK for this version? */ 88 | enum trivalue checked; 89 | bool needs_load; /* Still needs to be loaded? */ 90 | bool needs_check; /* Still needs to be checked? */ 91 | PGresult *data; /* Table data. */ 92 | pgrhash *ht; /* Hash of table data. */ 93 | int num_needs; /* # of tables we depend on. */ 94 | int num_needs_allocated; /* Allocated slots for same. */ 95 | pg_catalog_table **needs; /* Array of tables we depend on. */ 96 | int num_needed_by; /* # of tables depending on us. */ 97 | int num_needed_by_allocated; /* Allocated slots for same. */ 98 | pg_catalog_table **needed_by; /* Array of tables depending on us. */ 99 | }; 100 | 101 | /* Array of tables known to this tool. */ 102 | extern struct pg_catalog_table pg_catalog_tables[]; 103 | 104 | /* Identifying characteristics of the database to be checked. */ 105 | extern int remote_version; /* Version number. */ 106 | extern bool remote_is_edb; /* Is it an EDB database? */ 107 | extern char *database_oid; /* Database OID, if known. */ 108 | 109 | /* pg_catcheck.c */ 110 | extern pg_catalog_table *find_table_by_name(char *table_name); 111 | extern pg_catalog_column *find_column_by_name(pg_catalog_table *, char *); 112 | extern void add_table_dependency(pg_catalog_table *needs, 113 | pg_catalog_table *needed_by); 114 | 115 | /* check_attribute.c */ 116 | extern void prepare_to_check_attnum(pg_catalog_table *tab, 117 | pg_catalog_column *tabcol); 118 | extern void check_attnum(pg_catalog_table *tab, pg_catalog_column *tabcol, 119 | int rownum); 120 | 121 | /* check_class.c */ 122 | extern void prepare_to_check_relnatts(pg_catalog_table *tab, 123 | pg_catalog_column *tabcol); 124 | extern void check_relnatts(pg_catalog_table *tab, pg_catalog_column *tabcol, 125 | int rownum); 126 | 127 | /* check_depend.c */ 128 | extern void prepare_to_check_dependency_class_id(pg_catalog_table *tab, 129 | pg_catalog_column *tabcol); 130 | extern void prepare_to_check_dependency_id(pg_catalog_table *tab, 131 | pg_catalog_column *tabcol); 132 | extern void prepare_to_check_dependency_subid(pg_catalog_table *tab, 133 | pg_catalog_column *tabcol); 134 | extern void check_dependency_class_id(pg_catalog_table *tab, 135 | pg_catalog_column *tabcol, int rownum); 136 | extern void check_dependency_id(pg_catalog_table *tab, 137 | pg_catalog_column *tabcol, int rownum); 138 | extern void check_dependency_subid(pg_catalog_table *tab, 139 | pg_catalog_column *tabcol, int rownum); 140 | 141 | /* check_oids.c */ 142 | extern void prepare_to_check_oid_reference(pg_catalog_table *tab, 143 | pg_catalog_column *tabcol); 144 | extern void check_oid_reference(pg_catalog_table *tab, 145 | pg_catalog_column *tabcol, int rownum); 146 | 147 | /* select_from_relations.c */ 148 | extern void prepare_to_select_from_relations(void); 149 | extern void perform_select_from_relations(PGconn *conn); 150 | 151 | /* log.c */ 152 | typedef enum pgcc_severity 153 | { 154 | PGCC_DEBUG, /* Debugging messages for developers. */ 155 | PGCC_VERBOSE, /* Verbose messages. */ 156 | PGCC_PROGRESS, /* Progress messages. */ 157 | PGCC_NOTICE, /* Database inconsistencies. */ 158 | PGCC_WARNING, /* Warnings other than inconsistencies. */ 159 | PGCC_ERROR, /* Serious but not fatal errors. */ 160 | PGCC_FATAL /* Fatal errors. */ 161 | } pgcc_severity; 162 | 163 | extern bool quiet; 164 | extern int verbose; 165 | 166 | #if PG_VERSION_NUM < 90500 167 | extern void 168 | pgcc_log(pgcc_severity sev, char *fmt,...) 169 | __attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3))); 170 | extern void 171 | pgcc_report(pg_catalog_table *tab, pg_catalog_column *tabcol, 172 | int rownum, char *fmt,...) 173 | __attribute__((format(PG_PRINTF_ATTRIBUTE, 4, 5))); 174 | #else 175 | extern void 176 | pgcc_log(pgcc_severity sev, char *fmt,...) 177 | pg_attribute_printf(2, 3); 178 | extern void 179 | pgcc_report(pg_catalog_table *tab, pg_catalog_column *tabcol, 180 | int rownum, char *fmt,...) 181 | pg_attribute_printf(4, 5); 182 | #endif 183 | extern void pgcc_log_completion(void); 184 | 185 | #ifndef PG_USED_FOR_ASSERTS_ONLY 186 | #define PG_USED_FOR_ASSERTS_ONLY 187 | #endif 188 | 189 | /* pgrhash.c */ 190 | 191 | #define MAX_KEY_COLS 10 192 | extern pgrhash *pgrhash_create(PGresult *result, int nkeycols, int *keycols); 193 | extern int pgrhash_get(pgrhash *ht, char **keyvals); 194 | extern int pgrhash_insert(pgrhash *ht, int rownum); 195 | 196 | #endif /* PGCATCHECK_H */ 197 | -------------------------------------------------------------------------------- /pg_catcheck.proj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | /Od /MDd /Zi /D "DEBUG=1" /D "_DEBUG" 11 | Debug 12 | 13 | 14 | 15 | 16 | /Ox /MD /GF 17 | Release 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | /MACHINE:X64 28 | 29 | 30 | 31 | 32 | /D "_USE_32BIT_TIME_T" 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | /nologo /wd4273 /TC /LD $(XTRA_CFLAGS) /GS /fp:precise /Zc:wchar_t /D "WIN32" /D "__WIN32__" $(XTRA_ARCH_CFLAGS) /D "_CRT_SECURE_NO_DEPRECATE" /D "_CRT_NONSTDC_NO_DEPRECATE" /D "_MBCS" 41 | 42 | 43 | $(XTRA_ARCH_LDFLAGS) /DEBUG /PDB:"pg_catcheck.pdb" /defaultlib:user32 /defaultlib:netapi32 /defaultlib:advapi32 /defaultlib:shell32 /defaultlib:ws2_32 /defaultlib:Secur32.lib /defaultlib:$(PGPATH)\$(BUILD_SUBDIR)\libpq\libpq.lib /defaultlib:$(PGPATH)\$(BUILD_SUBDIR)\libpgport\libpgport.lib /defaultlib:$(PGPATH)\$(BUILD_SUBDIR)\libpgcommon\libpgcommon.lib" 44 | 45 | 46 | pg_catcheck.exe 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /pgrhash.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * pgrhash.c 4 | * 5 | * Simple hash table implementation for text data stored in a PGresult. 6 | * The user can specify which columns are to serve as keys. The code 7 | * is loosely based on the backend's dynahash.c, but is dramatically 8 | * simpler since we need only a small subset of the functionality offered 9 | * by that module. 10 | * 11 | *------------------------------------------------------------------------- 12 | */ 13 | 14 | #include "postgres_fe.h" 15 | 16 | #include "pg_catcheck.h" 17 | 18 | #if PG_VERSION_NUM >= 150000 19 | #include "port/pg_bitutils.h" 20 | #endif 21 | 22 | typedef struct pgrhash_entry 23 | { 24 | struct pgrhash_entry *next; /* link to next entry in same bucket */ 25 | uint32 hashvalue; /* hash function result for this entry */ 26 | int rownum; /* row number of data in PGresult */ 27 | } pgrhash_entry; 28 | 29 | struct pgrhash 30 | { 31 | PGresult *res; /* pointer to PGresult data */ 32 | int nkeycols; /* number of key columns */ 33 | int keycols[MAX_KEY_COLS]; /* array of key column indices */ 34 | unsigned nbuckets; /* number of buckets */ 35 | pgrhash_entry **bucket; /* pointer to hash entries */ 36 | }; 37 | 38 | static uint32 string_hash_sdbm(const char *key); 39 | static bool pgrhash_compare(pgrhash *ht, int rownum, char **keyvals); 40 | 41 | /* 42 | * Create a new hash table for given result set, keyed by the indicate 43 | * column indexes, but do not populate it. pgrhash_insert() should 44 | * be called separately for each row of the result set to actually 45 | * insert the rows. 46 | */ 47 | pgrhash * 48 | pgrhash_create(PGresult *result, int nkeycols, int *keycols) 49 | { 50 | unsigned bucket_shift; 51 | pgrhash *ht; 52 | int ntuples; 53 | 54 | Assert(nkeycols >= 1 && nkeycols <= MAX_KEY_COLS); 55 | 56 | ntuples = PQntuples(result); 57 | 58 | #if PG_VERSION_NUM >= 150000 59 | if (ntuples == 0) 60 | bucket_shift = 0; 61 | else 62 | bucket_shift = pg_leftmost_one_pos32(ntuples) + 1; 63 | #else 64 | bucket_shift = fls(ntuples); 65 | #endif 66 | 67 | if (bucket_shift >= sizeof(unsigned) * BITS_PER_BYTE) 68 | pgcc_log(PGCC_FATAL, "too many tuples"); 69 | 70 | ht = (pgrhash *) pg_malloc(sizeof(pgrhash)); 71 | ht->res = result; 72 | ht->nbuckets = ((unsigned) 1) << bucket_shift; 73 | ht->bucket = (pgrhash_entry **) 74 | pg_malloc0(ht->nbuckets * sizeof(pgrhash_entry *)); 75 | ht->nkeycols = nkeycols; 76 | memcpy(ht->keycols, keycols, sizeof(int) * nkeycols); 77 | 78 | return ht; 79 | } 80 | 81 | /* 82 | * Search a result-set hash table for a row matching a given set of key values. 83 | * 84 | * The return value is the matching row number, or -1 if none. 85 | */ 86 | int 87 | pgrhash_get(pgrhash *ht, char **keyvals) 88 | { 89 | int i; 90 | uint32 hashvalue = 0; 91 | pgrhash_entry *bucket; 92 | 93 | for (i = 0; i < ht->nkeycols; i++) 94 | hashvalue ^= string_hash_sdbm(keyvals[i]); 95 | 96 | for (bucket = ht->bucket[hashvalue & (ht->nbuckets - 1)]; 97 | bucket != NULL; bucket = bucket->next) 98 | if (pgrhash_compare(ht, bucket->rownum, keyvals)) 99 | return bucket->rownum; 100 | 101 | return -1; 102 | } 103 | 104 | /* 105 | * Insert a row into a result-set hash table, provided no such row is already 106 | * present. 107 | * 108 | * The return value is -1 on success, or the row number of an existing row 109 | * with the same key. 110 | * 111 | * The only reason we expose this as a separate function, rather than making 112 | * it part of pgrhash_create, is that it allows callers to insert rows one 113 | * at a time and detect unexpected duplicate key violations. 114 | */ 115 | int 116 | pgrhash_insert(pgrhash *ht, int rownum) 117 | { 118 | unsigned bucket_number; 119 | int i; 120 | unsigned hashvalue = 0; 121 | char *keyvals[MAX_KEY_COLS]; 122 | pgrhash_entry *bucket; 123 | pgrhash_entry *entry; 124 | 125 | for (i = 0; i < ht->nkeycols; i++) 126 | { 127 | keyvals[i] = PQgetvalue(ht->res, rownum, ht->keycols[i]); 128 | hashvalue ^= string_hash_sdbm(keyvals[i]); 129 | } 130 | 131 | /* Check for a conflicting entry already present in the table. */ 132 | bucket_number = hashvalue & (ht->nbuckets - 1); 133 | for (bucket = ht->bucket[bucket_number]; 134 | bucket != NULL; bucket = bucket->next) 135 | if (pgrhash_compare(ht, bucket->rownum, keyvals)) 136 | return bucket->rownum; 137 | 138 | /* Insert the new entry. */ 139 | entry = pg_malloc(sizeof(pgrhash_entry)); 140 | entry->next = ht->bucket[bucket_number]; 141 | entry->rownum = rownum; 142 | ht->bucket[bucket_number] = entry; 143 | 144 | return -1; 145 | } 146 | 147 | /* 148 | * Simple string hash function from http://www.cse.yorku.ca/~oz/hash.html 149 | * 150 | * The backend uses a more sophisticated function for hashing strings, 151 | * but we don't really need that complexity here. Most of the values 152 | * that we're hashing are short integers formatted as text, so there 153 | * shouldn't be much room for pathological input. 154 | */ 155 | static uint32 156 | string_hash_sdbm(const char *key) 157 | { 158 | uint32 hash = 0; 159 | int c; 160 | 161 | while ((c = *key++)) 162 | hash = c + (hash << 6) + (hash << 16) - hash; 163 | 164 | return hash; 165 | } 166 | 167 | /* 168 | * Test whether the given row number is match for the supplied keys. 169 | */ 170 | static bool 171 | pgrhash_compare(pgrhash *ht, int rownum, char **keyvals) 172 | { 173 | int i; 174 | char *keycol; 175 | char *keyval; 176 | 177 | for (i = 0; i < ht->nkeycols; i++) 178 | { 179 | keycol = PQgetvalue(ht->res, rownum, ht->keycols[i]); 180 | keyval = keyvals[i]; 181 | 182 | if (strcmp(keycol, keyval) != 0) 183 | return false; 184 | } 185 | 186 | return true; 187 | } 188 | -------------------------------------------------------------------------------- /select_from_relations.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * select_from_relations.c 4 | * 5 | * Try to select from relations with storage. This will fail if the 6 | * underlying files are absent or inaccessible. This is a little outside 7 | * the general remit of this tool, which is to check the integrity of 8 | * the system catalogs, but it seems like a useful addition. 9 | * 10 | *------------------------------------------------------------------------- 11 | */ 12 | 13 | #include "postgres_fe.h" 14 | #include "pg_catcheck.h" 15 | #include "pqexpbuffer.h" 16 | 17 | /* 18 | * Set up to check SELECT from relations. 19 | */ 20 | void 21 | prepare_to_select_from_relations(void) 22 | { 23 | pg_catalog_table *pg_class = find_table_by_name("pg_class"); 24 | pg_catalog_table *pg_namespace = find_table_by_name("pg_namespace"); 25 | 26 | /* Flag tables that must be loaded for this check. */ 27 | pg_class->needs_check = true; 28 | pg_class->needs_load = true; 29 | pg_namespace->needs_load = true; 30 | pg_namespace->needs_check = true; 31 | 32 | /* Flag columns that must be loaded for this check. */ 33 | find_column_by_name(pg_namespace, "nspname")->needed = true; 34 | find_column_by_name(pg_class, "relname")->needed = true; 35 | find_column_by_name(pg_class, "relnamespace")->needed = true; 36 | find_column_by_name(pg_class, "relkind")->needed = true; 37 | } 38 | 39 | /* 40 | * Try a SELECT from each relation. 41 | * 42 | * We use SELECT 0 here to make it fast; we're just trying to verify 43 | * that selecting data from the relation doesn't fail outright. 44 | */ 45 | void 46 | perform_select_from_relations(PGconn *conn) 47 | { 48 | PQExpBuffer query; 49 | char *tablename, 50 | *nspname, 51 | *nspoid; 52 | int rownum; 53 | int ntups; 54 | int oid_result_column; 55 | int relname_result_column; 56 | int relnamespace_result_column; 57 | int relkind_result_column; 58 | int nspname_result_column; 59 | pg_catalog_table *pg_class = find_table_by_name("pg_class"); 60 | pg_catalog_table *pg_namespace = find_table_by_name("pg_namespace"); 61 | 62 | /* 63 | * If we weren't able to retrieve the table data for either table, then 64 | * we can't run these checks. 65 | */ 66 | if (PQresultStatus(pg_class->data) != PGRES_TUPLES_OK || 67 | PQresultStatus(pg_namespace->data) != PGRES_TUPLES_OK) 68 | return; 69 | 70 | /* Locate the data we need. */ 71 | ntups = PQntuples(pg_class->data); 72 | oid_result_column = PQfnumber(pg_class->data, "oid"); 73 | relname_result_column = PQfnumber(pg_class->data, "relname"); 74 | relnamespace_result_column = PQfnumber(pg_class->data, "relnamespace"); 75 | relkind_result_column = PQfnumber(pg_class->data, "relkind"); 76 | nspname_result_column = PQfnumber(pg_namespace->data, "nspname"); 77 | 78 | query = createPQExpBuffer(); 79 | 80 | /* Loop over the rows and check them. */ 81 | for (rownum = 0; rownum < ntups; ++rownum) 82 | { 83 | char relkind; 84 | int nsp_rownum; 85 | PGresult *qryres; 86 | 87 | /* Check plain tables, toast tables, and materialized views. */ 88 | relkind = *(PQgetvalue(pg_class->data, rownum, relkind_result_column)); 89 | if (relkind != 'r' && relkind != 't' && relkind != 'm') 90 | continue; 91 | 92 | /* Get the table name and namespace OID from the pg_class */ 93 | tablename = PQgetvalue(pg_class->data, rownum, relname_result_column); 94 | nspoid = PQgetvalue(pg_class->data, rownum, relnamespace_result_column); 95 | 96 | /* 97 | * Get the namespace name for the given namespace OID. Any errors here 98 | * have already been reported, so we just emit a debug message here. 99 | */ 100 | nsp_rownum = pgrhash_get(pg_namespace->ht, &nspoid); 101 | if (nsp_rownum == -1) 102 | { 103 | pgcc_log(PGCC_DEBUG, 104 | "can't find schema name for select query for table with OID %s\n", 105 | PQgetvalue(pg_class->data, rownum, oid_result_column)); 106 | continue; 107 | } 108 | nspname = PQgetvalue(pg_namespace->data, nsp_rownum, nspname_result_column); 109 | 110 | /* Debug message. */ 111 | pgcc_log(PGCC_DEBUG, "selecting from \"%s\".\"%s\"\n", nspname, tablename); 112 | 113 | /* Build up a query. */ 114 | resetPQExpBuffer(query); 115 | appendPQExpBuffer(query, "SELECT 1 FROM %s.%s LIMIT 0", 116 | PQescapeIdentifier(conn, nspname, strlen(nspname)), 117 | PQescapeIdentifier(conn, tablename, strlen(tablename))); 118 | 119 | /* Run the query. */ 120 | qryres = PQexec(conn, query->data); 121 | if (PQresultStatus(qryres) != PGRES_TUPLES_OK) 122 | pgcc_log(PGCC_NOTICE, 123 | "unable to query relation \"%s\".\"%s\": %s", 124 | nspname, tablename, PQerrorMessage(conn)); 125 | 126 | /* Clean up. */ 127 | PQclear(qryres); 128 | } 129 | destroyPQExpBuffer(query); 130 | } 131 | -------------------------------------------------------------------------------- /settings.projinc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 0 13 | 14 | 15 | C:\postgresql-9.4.0 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /typedefs.list: -------------------------------------------------------------------------------- 1 | attnum_cache 2 | check_depend_cache 3 | class_id_mapping_type 4 | depend_column_style 5 | exception_list 6 | pg_catalog_column 7 | pg_catalog_table 8 | PGconn 9 | PGresult 10 | pgrhash 11 | pgrhash_entry 12 | relnatts_cache 13 | --------------------------------------------------------------------------------