diff --git a/docs/generated/sql/functions.md b/docs/generated/sql/functions.md
index 0acd6f8c16d7..52e8df4072a8 100644
--- a/docs/generated/sql/functions.md
+++ b/docs/generated/sql/functions.md
@@ -3789,13 +3789,15 @@ may increase either contention or retry errors, or both.
pg_table_size(relation_oid: oid) → int | Returns the on-disk size, in bytes, of the table, including TOAST and visibility map (where applicable) with the given OID. The size is read from a periodically-refreshed cache and may lag behind the true value by minutes. Returns NULL if no such relation exists.
+pg_table_size(relation_oid: oid) → int | Returns the on-disk size, in bytes, of the table with the given OID, excluding indexes. In CockroachDB this is the primary index size, since the primary index is the row data. The size is read from a periodically-refreshed cache and may lag behind the true value by minutes.
| Volatile |
-pg_total_relation_size(relation_oid: oid) → int | Returns the on-disk size, in bytes, of the relation, including all indexes with the given OID. The size is read from a periodically-refreshed cache and may lag behind the true value by minutes. Returns NULL if no such relation exists.
+pg_total_relation_size(relation_oid: oid) → int | Returns the on-disk size, in bytes, of the relation with the given OID, including all indexes and any data still occupying the table’s keyspace (such as dropped-index data awaiting garbage collection). The size is read from a periodically-refreshed cache and may lag behind the true value by minutes.
| Volatile |
pg_trigger_depth() → int | Returns the current nesting level of PostgreSQL triggers (0 if not called, directly or indirectly, from inside a trigger).
| Volatile |
diff --git a/pkg/cli/clisqlshell/describe.go b/pkg/cli/clisqlshell/describe.go
index d3154ae814e9..fd565a1f8fac 100644
--- a/pkg/cli/clisqlshell/describe.go
+++ b/pkg/cli/clisqlshell/describe.go
@@ -100,14 +100,21 @@ func listAllDbs(hasPattern bool, verbose bool) (string, string) {
printACLColumn(&buf, "d.datacl")
if verbose {
// TODO(sql-sessions): "Tablespace" is omitted.
- // TODO(sql-sessions): "Size" is omited.
- // (pg_database_size is not yet supported.)
+ // "Size" is read from the periodically-refreshed system.table_metadata
+ // cache and may lag the truth by minutes; the column is omitted entirely
+ // (rather than reported as zero) for databases the caller cannot CONNECT
+ // to, matching the upstream psql behavior.
buf.WriteString(`,
CASE
WHEN pg_catalog.has_database_privilege(d.datname, 'CONNECT')
THEN IF(d.datconnlimit < 0, 'Unlimited', d.datconnlimit::STRING)
ELSE 'No Access'
END AS "Connections",
+ CASE
+ WHEN pg_catalog.has_database_privilege(d.datname, 'CONNECT')
+ THEN pg_catalog.pg_size_pretty(pg_catalog.pg_database_size(d.datname))
+ ELSE 'No Access'
+ END AS "Size",
COALESCE(pg_catalog.shobj_description(d.oid, 'pg_database'), '') AS "Description"`)
}
buf.WriteString(`
@@ -403,9 +410,10 @@ func listTables(tabTypes string, hasPattern bool, verbose, showSystem bool) (str
am.amname AS "Access Method"`)
}
- // TODO(sql-sessions): Column "Size" omitted here.
- // This is because pg_table_size() is not supported yet.
+ // "Size" is read from the periodically-refreshed
+ // system.table_metadata cache and may lag the truth by minutes.
buf.WriteString(`,
+ pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) AS "Size",
COALESCE(pg_catalog.obj_description(c.oid, 'pg_class'),'') as "Description"`)
}
diff --git a/pkg/cli/clisqlshell/testdata/describe b/pkg/cli/clisqlshell/testdata/describe
index fd72c4cdc57e..f264521250bd 100644
--- a/pkg/cli/clisqlshell/testdata/describe
+++ b/pkg/cli/clisqlshell/testdata/describe
@@ -29,6 +29,63 @@ create function myfunc(val int) returns int language sql as $$ select val $$;
----
ok
+# Seed system.table_metadata so \l+ and \dt+ show deterministic sizes.
+# The details JSONB mirrors what tableMetadataUpdater writes in production
+# (replica_count + primary_index_id + per-index sizes), so the size
+# builtins go through their normal lookup path rather than the
+# mixed-version replication_size_bytes fallback.
+sql
+WITH per_table(name, primary_size, secondary_size) AS (
+ VALUES
+ ('mytable', 16384, 4096),
+ ('mymview', 8192, 0),
+ ('ftable1', 1024, 256),
+ ('ftable2', 2048, 0)
+),
+idx AS (
+ SELECT t.parent_id AS db_id,
+ t.table_id,
+ t.database_name,
+ t.schema_name,
+ t.name,
+ ti.index_id,
+ ti.index_type,
+ CASE ti.index_type
+ WHEN 'primary' THEN p.primary_size
+ WHEN 'secondary' THEN p.secondary_size
+ END AS index_size
+ FROM crdb_internal.tables t
+ JOIN per_table p ON p.name = t.name
+ JOIN crdb_internal.table_indexes ti ON ti.descriptor_id = t.table_id
+ WHERE t.database_name = 'defaultdb'
+)
+INSERT INTO system.table_metadata (
+ db_id, table_id, db_name, schema_name, table_name,
+ total_columns, total_indexes, store_ids,
+ replication_size_bytes, total_ranges,
+ total_live_data_bytes, total_data_bytes, perc_live_data,
+ table_type, details
+)
+SELECT db_id, table_id, database_name, schema_name, name,
+ 0,
+ count(*)::INT,
+ ARRAY[1]::INT[],
+ sum(index_size)::INT,
+ 1,
+ sum(index_size)::INT, sum(index_size)::INT, 1.0,
+ 'TABLE',
+ jsonb_build_object(
+ 'replica_count', 1,
+ 'primary_index_id',
+ (array_agg(index_id) FILTER (WHERE index_type = 'primary'))[1],
+ 'index_sizes',
+ jsonb_object_agg(index_id::TEXT, index_size)
+ )
+ FROM idx
+ GROUP BY db_id, table_id, database_name, schema_name, name;
+----
+ok
+
subtest list_dbs
cli
@@ -67,15 +124,20 @@ List of databases:
THEN IF(d.datconnlimit < 0, 'Unlimited', d.datconnlimit::STRING)
ELSE 'No Access'
END AS "Connections",
+ CASE
+ WHEN pg_catalog.has_database_privilege(d.datname, 'CONNECT')
+ THEN pg_catalog.pg_size_pretty(pg_catalog.pg_database_size(d.datname))
+ ELSE 'No Access'
+ END AS "Size",
COALESCE(pg_catalog.shobj_description(d.oid, 'pg_database'), '') AS "Description"
FROM pg_catalog.pg_database d
ORDER BY 1
-Name,Owner,Encoding,Collate,Ctype,Access privileges,Connections,Description
-defaultdb,root,UTF8,en_US.utf8,en_US.utf8,,Unlimited,
-mydb,root,UTF8,en_US.utf8,en_US.utf8,,Unlimited,my awesome db comment
-postgres,root,UTF8,en_US.utf8,en_US.utf8,,Unlimited,
+Name,Owner,Encoding,Collate,Ctype,Access privileges,Connections,Size,Description
+defaultdb,root,UTF8,en_US.utf8,en_US.utf8,,Unlimited,31 kB,
+mydb,root,UTF8,en_US.utf8,en_US.utf8,,Unlimited,0 bytes,my awesome db comment
+postgres,root,UTF8,en_US.utf8,en_US.utf8,,Unlimited,0 bytes,
system,node,UTF8,en_US.utf8,en_US.utf8,"admin=c*/node
-root=c*/node",Unlimited,
+root=c*/node",Unlimited,0 bytes,
cli
\l my%
@@ -453,6 +515,7 @@ List of relations:
WHEN 't' THEN 'temporary'
WHEN 'u' THEN 'unlogged' END AS "Persistence",
am.amname AS "Access Method",
+ pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) AS "Size",
COALESCE(pg_catalog.obj_description(c.oid, 'pg_class'),'') as "Description"
FROM pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_namespace n on n.oid = c.relnamespace
@@ -463,13 +526,13 @@ LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam
AND n.nspname <> 'crdb_internal'
AND pg_catalog.pg_table_is_visible(c.oid)
ORDER BY 1,2
-Schema,Name,Type,Owner,Persistence,Access Method,Description
-public,ftable1,table,root,permanent,prefix,
-public,ftable2,table,root,permanent,prefix,
-public,mymview,materialized view,root,permanent,NULL,
-public,myseq,sequence,root,permanent,NULL,
-public,mytable,table,root,permanent,prefix,my awesome tb comment
-public,myview,view,root,permanent,NULL,
+Schema,Name,Type,Owner,Persistence,Access Method,Size,Description
+public,ftable1,table,root,permanent,prefix,1024 bytes,
+public,ftable2,table,root,permanent,prefix,2048 bytes,
+public,mymview,materialized view,root,permanent,NULL,8192 bytes,
+public,myseq,sequence,root,permanent,NULL,0 bytes,
+public,mytable,table,root,permanent,prefix,16 kB,my awesome tb comment
+public,myview,view,root,permanent,NULL,0 bytes,
cli
\dS+
@@ -496,6 +559,7 @@ List of relations:
WHEN 't' THEN 'temporary'
WHEN 'u' THEN 'unlogged' END AS "Persistence",
am.amname AS "Access Method",
+ pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) AS "Size",
COALESCE(pg_catalog.obj_description(c.oid, 'pg_class'),'') as "Description"
FROM pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_namespace n on n.oid = c.relnamespace
@@ -503,221 +567,221 @@ LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam
WHERE c.relkind IN ('r','p','t','v','m','S','s','f','')
AND pg_catalog.pg_table_is_visible(c.oid)
ORDER BY 1,2
-Schema,Name,Type,Owner,Persistence,Access Method,Description
-pg_catalog,pg_aggregate,table,node,permanent,prefix,"aggregated built-in functions (incomplete)
+Schema,Name,Type,Owner,Persistence,Access Method,Size,Description
+pg_catalog,pg_aggregate,table,node,permanent,prefix,NULL,"aggregated built-in functions (incomplete)
https://www.postgresql.org/docs/9.6/catalog-pg-aggregate.html"
-pg_catalog,pg_aios,table,node,permanent,prefix,pg_aios was created for compatibility and is currently unimplemented
-pg_catalog,pg_am,table,node,permanent,prefix,"index access methods (incomplete)
+pg_catalog,pg_aios,table,node,permanent,prefix,NULL,pg_aios was created for compatibility and is currently unimplemented
+pg_catalog,pg_am,table,node,permanent,prefix,NULL,"index access methods (incomplete)
https://www.postgresql.org/docs/9.5/catalog-pg-am.html"
-pg_catalog,pg_amop,table,node,permanent,prefix,pg_amop was created for compatibility and is currently unimplemented
-pg_catalog,pg_amproc,table,node,permanent,prefix,pg_amproc was created for compatibility and is currently unimplemented
-pg_catalog,pg_attrdef,table,node,permanent,prefix,"column default values
+pg_catalog,pg_amop,table,node,permanent,prefix,NULL,pg_amop was created for compatibility and is currently unimplemented
+pg_catalog,pg_amproc,table,node,permanent,prefix,NULL,pg_amproc was created for compatibility and is currently unimplemented
+pg_catalog,pg_attrdef,table,node,permanent,prefix,NULL,"column default values
https://www.postgresql.org/docs/9.5/catalog-pg-attrdef.html"
-pg_catalog,pg_attribute,table,node,permanent,prefix,"table columns (incomplete - see also information_schema.columns)
+pg_catalog,pg_attribute,table,node,permanent,prefix,NULL,"table columns (incomplete - see also information_schema.columns)
https://www.postgresql.org/docs/18/catalog-pg-attribute.html"
-pg_catalog,pg_auth_members,table,node,permanent,prefix,"role membership
+pg_catalog,pg_auth_members,table,node,permanent,prefix,NULL,"role membership
https://www.postgresql.org/docs/18/catalog-pg-auth-members.html"
-pg_catalog,pg_authid,table,node,permanent,prefix,"authorization identifiers - differs from postgres as we do not display passwords,
+pg_catalog,pg_authid,table,node,permanent,prefix,NULL,"authorization identifiers - differs from postgres as we do not display passwords,
and thus do not require admin privileges for access.
https://www.postgresql.org/docs/9.5/catalog-pg-authid.html"
-pg_catalog,pg_available_extension_versions,table,node,permanent,prefix,pg_available_extension_versions was created for compatibility and is currently unimplemented
-pg_catalog,pg_available_extensions,table,node,permanent,prefix,"available extensions
+pg_catalog,pg_available_extension_versions,table,node,permanent,prefix,NULL,pg_available_extension_versions was created for compatibility and is currently unimplemented
+pg_catalog,pg_available_extensions,table,node,permanent,prefix,NULL,"available extensions
https://www.postgresql.org/docs/9.6/view-pg-available-extensions.html"
-pg_catalog,pg_backend_memory_contexts,table,node,permanent,prefix,pg_backend_memory_contexts was created for compatibility and is currently unimplemented
-pg_catalog,pg_cast,table,node,permanent,prefix,"casts (empty - needs filling out)
+pg_catalog,pg_backend_memory_contexts,table,node,permanent,prefix,NULL,pg_backend_memory_contexts was created for compatibility and is currently unimplemented
+pg_catalog,pg_cast,table,node,permanent,prefix,NULL,"casts (empty - needs filling out)
https://www.postgresql.org/docs/9.6/catalog-pg-cast.html"
-pg_catalog,pg_class,table,node,permanent,prefix,"tables and relation-like objects (incomplete - see also information_schema.tables/sequences/views)
+pg_catalog,pg_class,table,node,permanent,prefix,NULL,"tables and relation-like objects (incomplete - see also information_schema.tables/sequences/views)
https://www.postgresql.org/docs/9.5/catalog-pg-class.html"
-pg_catalog,pg_collation,table,node,permanent,prefix,"available collations (incomplete)
+pg_catalog,pg_collation,table,node,permanent,prefix,NULL,"available collations (incomplete)
https://www.postgresql.org/docs/18/catalog-pg-collation.html"
-pg_catalog,pg_config,table,node,permanent,prefix,pg_config was created for compatibility and is currently unimplemented
-pg_catalog,pg_constraint,table,node,permanent,prefix,"table constraints (incomplete - see also information_schema.table_constraints)
+pg_catalog,pg_config,table,node,permanent,prefix,NULL,pg_config was created for compatibility and is currently unimplemented
+pg_catalog,pg_constraint,table,node,permanent,prefix,NULL,"table constraints (incomplete - see also information_schema.table_constraints)
https://www.postgresql.org/docs/18/catalog-pg-constraint.html"
-pg_catalog,pg_conversion,table,node,permanent,prefix,"encoding conversions (empty - unimplemented)
+pg_catalog,pg_conversion,table,node,permanent,prefix,NULL,"encoding conversions (empty - unimplemented)
https://www.postgresql.org/docs/9.6/catalog-pg-conversion.html"
-pg_catalog,pg_cursors,table,node,permanent,prefix,"contains currently active SQL cursors created with DECLARE
+pg_catalog,pg_cursors,table,node,permanent,prefix,NULL,"contains currently active SQL cursors created with DECLARE
https://www.postgresql.org/docs/14/view-pg-cursors.html"
-pg_catalog,pg_database,table,node,permanent,prefix,"available databases (incomplete)
+pg_catalog,pg_database,table,node,permanent,prefix,NULL,"available databases (incomplete)
https://www.postgresql.org/docs/18/catalog-pg-database.html"
-pg_catalog,pg_db_role_setting,table,node,permanent,prefix,"contains the default values that have been configured for session variables
+pg_catalog,pg_db_role_setting,table,node,permanent,prefix,NULL,"contains the default values that have been configured for session variables
https://www.postgresql.org/docs/13/catalog-pg-db-role-setting.html"
-pg_catalog,pg_default_acl,table,node,permanent,prefix,"default ACLs; these are the privileges that will be assigned to newly created objects
+pg_catalog,pg_default_acl,table,node,permanent,prefix,NULL,"default ACLs; these are the privileges that will be assigned to newly created objects
https://www.postgresql.org/docs/13/catalog-pg-default-acl.html"
-pg_catalog,pg_depend,table,node,permanent,prefix,"dependency relationships (incomplete)
+pg_catalog,pg_depend,table,node,permanent,prefix,NULL,"dependency relationships (incomplete)
https://www.postgresql.org/docs/9.5/catalog-pg-depend.html"
-pg_catalog,pg_description,view,node,permanent,NULL,"object comments
+pg_catalog,pg_description,view,node,permanent,NULL,NULL,"object comments
https://www.postgresql.org/docs/9.5/catalog-pg-description.html"
-pg_catalog,pg_enum,table,node,permanent,prefix,"enum types and labels (empty - feature does not exist)
+pg_catalog,pg_enum,table,node,permanent,prefix,NULL,"enum types and labels (empty - feature does not exist)
https://www.postgresql.org/docs/9.5/catalog-pg-enum.html"
-pg_catalog,pg_event_trigger,table,node,permanent,prefix,"event triggers (empty - feature does not exist)
+pg_catalog,pg_event_trigger,table,node,permanent,prefix,NULL,"event triggers (empty - feature does not exist)
https://www.postgresql.org/docs/9.6/catalog-pg-event-trigger.html"
-pg_catalog,pg_extension,table,node,permanent,prefix,"installed extensions (empty - feature does not exist)
+pg_catalog,pg_extension,table,node,permanent,prefix,NULL,"installed extensions (empty - feature does not exist)
https://www.postgresql.org/docs/9.5/catalog-pg-extension.html"
-pg_catalog,pg_file_settings,table,node,permanent,prefix,pg_file_settings was created for compatibility and is currently unimplemented
-pg_catalog,pg_foreign_data_wrapper,table,node,permanent,prefix,"foreign data wrappers (empty - feature does not exist)
+pg_catalog,pg_file_settings,table,node,permanent,prefix,NULL,pg_file_settings was created for compatibility and is currently unimplemented
+pg_catalog,pg_foreign_data_wrapper,table,node,permanent,prefix,NULL,"foreign data wrappers (empty - feature does not exist)
https://www.postgresql.org/docs/9.5/catalog-pg-foreign-data-wrapper.html"
-pg_catalog,pg_foreign_server,table,node,permanent,prefix,"foreign servers (empty - feature does not exist)
+pg_catalog,pg_foreign_server,table,node,permanent,prefix,NULL,"foreign servers (empty - feature does not exist)
https://www.postgresql.org/docs/9.5/catalog-pg-foreign-server.html"
-pg_catalog,pg_foreign_table,table,node,permanent,prefix,"foreign tables (empty - feature does not exist)
+pg_catalog,pg_foreign_table,table,node,permanent,prefix,NULL,"foreign tables (empty - feature does not exist)
https://www.postgresql.org/docs/9.5/catalog-pg-foreign-table.html"
-pg_catalog,pg_group,table,node,permanent,prefix,pg_group was created for compatibility and is currently unimplemented
-pg_catalog,pg_hba_file_rules,table,node,permanent,prefix,pg_hba_file_rules was created for compatibility and is currently unimplemented
-pg_catalog,pg_ident_file_mappings,table,node,permanent,prefix,pg_ident_file_mappings was created for compatibility and is currently unimplemented
-pg_catalog,pg_index,table,node,permanent,prefix,"indexes (incomplete)
+pg_catalog,pg_group,table,node,permanent,prefix,NULL,pg_group was created for compatibility and is currently unimplemented
+pg_catalog,pg_hba_file_rules,table,node,permanent,prefix,NULL,pg_hba_file_rules was created for compatibility and is currently unimplemented
+pg_catalog,pg_ident_file_mappings,table,node,permanent,prefix,NULL,pg_ident_file_mappings was created for compatibility and is currently unimplemented
+pg_catalog,pg_index,table,node,permanent,prefix,NULL,"indexes (incomplete)
https://www.postgresql.org/docs/9.5/catalog-pg-index.html"
-pg_catalog,pg_indexes,table,node,permanent,prefix,"index creation statements
+pg_catalog,pg_indexes,table,node,permanent,prefix,NULL,"index creation statements
https://www.postgresql.org/docs/9.5/view-pg-indexes.html"
-pg_catalog,pg_inherits,table,node,permanent,prefix,"table inheritance hierarchy (empty - feature does not exist)
+pg_catalog,pg_inherits,table,node,permanent,prefix,NULL,"table inheritance hierarchy (empty - feature does not exist)
https://www.postgresql.org/docs/9.5/catalog-pg-inherits.html"
-pg_catalog,pg_init_privs,table,node,permanent,prefix,pg_init_privs was created for compatibility and is currently unimplemented
-pg_catalog,pg_language,table,node,permanent,prefix,"available languages
+pg_catalog,pg_init_privs,table,node,permanent,prefix,NULL,pg_init_privs was created for compatibility and is currently unimplemented
+pg_catalog,pg_language,table,node,permanent,prefix,NULL,"available languages
https://www.postgresql.org/docs/9.5/catalog-pg-language.html"
-pg_catalog,pg_largeobject,table,node,permanent,prefix,pg_largeobject was created for compatibility and is currently unimplemented
-pg_catalog,pg_largeobject_metadata,table,node,permanent,prefix,pg_largeobject_metadata was created for compatibility and is currently unimplemented
-pg_catalog,pg_locks,table,node,permanent,prefix,"advisory locks: granted and waiting advisory locks
+pg_catalog,pg_largeobject,table,node,permanent,prefix,NULL,pg_largeobject was created for compatibility and is currently unimplemented
+pg_catalog,pg_largeobject_metadata,table,node,permanent,prefix,NULL,pg_largeobject_metadata was created for compatibility and is currently unimplemented
+pg_catalog,pg_locks,table,node,permanent,prefix,NULL,"advisory locks: granted and waiting advisory locks
https://www.postgresql.org/docs/18/view-pg-locks.html"
-pg_catalog,pg_matviews,table,node,permanent,prefix,"available materialized views
+pg_catalog,pg_matviews,table,node,permanent,prefix,NULL,"available materialized views
https://www.postgresql.org/docs/9.6/view-pg-matviews.html"
-pg_catalog,pg_namespace,table,node,permanent,prefix,"available namespaces
+pg_catalog,pg_namespace,table,node,permanent,prefix,NULL,"available namespaces
https://www.postgresql.org/docs/9.5/catalog-pg-namespace.html"
-pg_catalog,pg_opclass,table,node,permanent,prefix,"opclass (empty - Operator classes not supported yet)
+pg_catalog,pg_opclass,table,node,permanent,prefix,NULL,"opclass (empty - Operator classes not supported yet)
https://www.postgresql.org/docs/12/catalog-pg-opclass.html"
-pg_catalog,pg_operator,table,node,permanent,prefix,"operators (incomplete)
+pg_catalog,pg_operator,table,node,permanent,prefix,NULL,"operators (incomplete)
https://www.postgresql.org/docs/9.5/catalog-pg-operator.html"
-pg_catalog,pg_opfamily,table,node,permanent,prefix,pg_opfamily was created for compatibility and is currently unimplemented
-pg_catalog,pg_parameter_acl,table,node,permanent,prefix,pg_parameter_acl was created for compatibility and is currently unimplemented
-pg_catalog,pg_partitioned_table,table,node,permanent,prefix,pg_partitioned_table was created for compatibility and is currently unimplemented
-pg_catalog,pg_policies,table,node,permanent,prefix,"pg_policies provides a user-friendly view of row-level security policies
+pg_catalog,pg_opfamily,table,node,permanent,prefix,NULL,pg_opfamily was created for compatibility and is currently unimplemented
+pg_catalog,pg_parameter_acl,table,node,permanent,prefix,NULL,pg_parameter_acl was created for compatibility and is currently unimplemented
+pg_catalog,pg_partitioned_table,table,node,permanent,prefix,NULL,pg_partitioned_table was created for compatibility and is currently unimplemented
+pg_catalog,pg_policies,table,node,permanent,prefix,NULL,"pg_policies provides a user-friendly view of row-level security policies
https://www.postgresql.org/docs/17/view-pg-policies.html"
-pg_catalog,pg_policy,table,node,permanent,prefix,"stores row-level security policies for tables
+pg_catalog,pg_policy,table,node,permanent,prefix,NULL,"stores row-level security policies for tables
https://www.postgresql.org/docs/17/catalog-pg-policy.html"
-pg_catalog,pg_prepared_statements,table,node,permanent,prefix,"prepared statements
+pg_catalog,pg_prepared_statements,table,node,permanent,prefix,NULL,"prepared statements
https://www.postgresql.org/docs/18/view-pg-prepared-statements.html"
-pg_catalog,pg_prepared_xacts,table,node,permanent,prefix,"prepared transactions
+pg_catalog,pg_prepared_xacts,table,node,permanent,prefix,NULL,"prepared transactions
https://www.postgresql.org/docs/9.6/view-pg-prepared-xacts.html"
-pg_catalog,pg_proc,table,node,permanent,prefix,"built-in functions (incomplete)
+pg_catalog,pg_proc,table,node,permanent,prefix,NULL,"built-in functions (incomplete)
https://www.postgresql.org/docs/16/catalog-pg-proc.html"
-pg_catalog,pg_publication,table,node,permanent,prefix,pg_publication was created for compatibility and is currently unimplemented
-pg_catalog,pg_publication_namespace,table,node,permanent,prefix,pg_publication_namespace was created for compatibility and is currently unimplemented
-pg_catalog,pg_publication_rel,table,node,permanent,prefix,pg_publication_rel was created for compatibility and is currently unimplemented
-pg_catalog,pg_publication_tables,table,node,permanent,prefix,pg_publication_tables was created for compatibility and is currently unimplemented
-pg_catalog,pg_range,table,node,permanent,prefix,"range types (empty - feature does not exist)
+pg_catalog,pg_publication,table,node,permanent,prefix,NULL,pg_publication was created for compatibility and is currently unimplemented
+pg_catalog,pg_publication_namespace,table,node,permanent,prefix,NULL,pg_publication_namespace was created for compatibility and is currently unimplemented
+pg_catalog,pg_publication_rel,table,node,permanent,prefix,NULL,pg_publication_rel was created for compatibility and is currently unimplemented
+pg_catalog,pg_publication_tables,table,node,permanent,prefix,NULL,pg_publication_tables was created for compatibility and is currently unimplemented
+pg_catalog,pg_range,table,node,permanent,prefix,NULL,"range types (empty - feature does not exist)
https://www.postgresql.org/docs/9.5/catalog-pg-range.html"
-pg_catalog,pg_replication_origin,table,node,permanent,prefix,pg_replication_origin was created for compatibility and is currently unimplemented
-pg_catalog,pg_replication_origin_status,table,node,permanent,prefix,pg_replication_origin_status was created for compatibility and is currently unimplemented
-pg_catalog,pg_replication_slots,table,node,permanent,prefix,pg_replication_slots was created for compatibility and is currently unimplemented
-pg_catalog,pg_rewrite,table,node,permanent,prefix,"rewrite rules (only for referencing on pg_depend for table-view dependencies)
+pg_catalog,pg_replication_origin,table,node,permanent,prefix,NULL,pg_replication_origin was created for compatibility and is currently unimplemented
+pg_catalog,pg_replication_origin_status,table,node,permanent,prefix,NULL,pg_replication_origin_status was created for compatibility and is currently unimplemented
+pg_catalog,pg_replication_slots,table,node,permanent,prefix,NULL,pg_replication_slots was created for compatibility and is currently unimplemented
+pg_catalog,pg_rewrite,table,node,permanent,prefix,NULL,"rewrite rules (only for referencing on pg_depend for table-view dependencies)
https://www.postgresql.org/docs/9.5/catalog-pg-rewrite.html"
-pg_catalog,pg_roles,table,node,permanent,prefix,"database roles
+pg_catalog,pg_roles,table,node,permanent,prefix,NULL,"database roles
https://www.postgresql.org/docs/9.5/view-pg-roles.html"
-pg_catalog,pg_rules,table,node,permanent,prefix,pg_rules was created for compatibility and is currently unimplemented
-pg_catalog,pg_seclabel,table,node,permanent,prefix,"security labels (empty - feature does not exist)
+pg_catalog,pg_rules,table,node,permanent,prefix,NULL,pg_rules was created for compatibility and is currently unimplemented
+pg_catalog,pg_seclabel,table,node,permanent,prefix,NULL,"security labels (empty - feature does not exist)
https://www.postgresql.org/docs/9.5/catalog-pg-seclabel.html"
-pg_catalog,pg_seclabels,table,node,permanent,prefix,"security labels (empty)
+pg_catalog,pg_seclabels,table,node,permanent,prefix,NULL,"security labels (empty)
https://www.postgresql.org/docs/9.6/view-pg-seclabels.html"
-pg_catalog,pg_sequence,table,node,permanent,prefix,"sequences (see also information_schema.sequences)
+pg_catalog,pg_sequence,table,node,permanent,prefix,NULL,"sequences (see also information_schema.sequences)
https://www.postgresql.org/docs/9.5/catalog-pg-sequence.html"
-pg_catalog,pg_sequences,table,node,permanent,prefix,"pg_sequences is very similar as pg_sequence.
+pg_catalog,pg_sequences,table,node,permanent,prefix,NULL,"pg_sequences is very similar as pg_sequence.
https://www.postgresql.org/docs/13/view-pg-sequences.html
"
-pg_catalog,pg_settings,table,node,permanent,prefix,"session variables (incomplete)
+pg_catalog,pg_settings,table,node,permanent,prefix,NULL,"session variables (incomplete)
https://www.postgresql.org/docs/9.5/catalog-pg-settings.html"
-pg_catalog,pg_shadow,table,node,permanent,prefix,"pg_shadow lists properties for roles that are marked as rolcanlogin in pg_authid
+pg_catalog,pg_shadow,table,node,permanent,prefix,NULL,"pg_shadow lists properties for roles that are marked as rolcanlogin in pg_authid
https://www.postgresql.org/docs/13/view-pg-shadow.html"
-pg_catalog,pg_shdepend,table,node,permanent,prefix,"Shared Dependencies (Roles depending on objects).
+pg_catalog,pg_shdepend,table,node,permanent,prefix,NULL,"Shared Dependencies (Roles depending on objects).
https://www.postgresql.org/docs/9.6/catalog-pg-shdepend.html"
-pg_catalog,pg_shdescription,view,node,permanent,NULL,"shared object comments
+pg_catalog,pg_shdescription,view,node,permanent,NULL,NULL,"shared object comments
https://www.postgresql.org/docs/9.5/catalog-pg-shdescription.html"
-pg_catalog,pg_shmem_allocations,table,node,permanent,prefix,pg_shmem_allocations was created for compatibility and is currently unimplemented
-pg_catalog,pg_shmem_allocations_numa,table,node,permanent,prefix,pg_shmem_allocations_numa was created for compatibility and is currently unimplemented
-pg_catalog,pg_shseclabel,table,node,permanent,prefix,"shared security labels (empty - feature not supported)
+pg_catalog,pg_shmem_allocations,table,node,permanent,prefix,NULL,pg_shmem_allocations was created for compatibility and is currently unimplemented
+pg_catalog,pg_shmem_allocations_numa,table,node,permanent,prefix,NULL,pg_shmem_allocations_numa was created for compatibility and is currently unimplemented
+pg_catalog,pg_shseclabel,table,node,permanent,prefix,NULL,"shared security labels (empty - feature not supported)
https://www.postgresql.org/docs/9.5/catalog-pg-shseclabel.html"
-pg_catalog,pg_stat_activity,table,node,permanent,prefix,"backend access statistics (empty - monitoring works differently in CockroachDB)
+pg_catalog,pg_stat_activity,table,node,permanent,prefix,NULL,"backend access statistics (empty - monitoring works differently in CockroachDB)
https://www.postgresql.org/docs/9.6/monitoring-stats.html#PG-STAT-ACTIVITY-VIEW"
-pg_catalog,pg_stat_all_indexes,table,node,permanent,prefix,pg_stat_all_indexes was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_all_tables,table,node,permanent,prefix,pg_stat_all_tables was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_archiver,table,node,permanent,prefix,pg_stat_archiver was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_bgwriter,table,node,permanent,prefix,pg_stat_bgwriter was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_checkpointer,table,node,permanent,prefix,pg_stat_checkpointer was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_database,table,node,permanent,prefix,pg_stat_database was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_database_conflicts,table,node,permanent,prefix,pg_stat_database_conflicts was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_gssapi,table,node,permanent,prefix,pg_stat_gssapi was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_io,table,node,permanent,prefix,pg_stat_io was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_progress_analyze,table,node,permanent,prefix,pg_stat_progress_analyze was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_progress_basebackup,table,node,permanent,prefix,pg_stat_progress_basebackup was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_progress_cluster,table,node,permanent,prefix,pg_stat_progress_cluster was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_progress_copy,table,node,permanent,prefix,pg_stat_progress_copy was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_progress_create_index,table,node,permanent,prefix,pg_stat_progress_create_index was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_progress_vacuum,table,node,permanent,prefix,pg_stat_progress_vacuum was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_recovery_prefetch,table,node,permanent,prefix,pg_stat_recovery_prefetch was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_replication,table,node,permanent,prefix,pg_stat_replication was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_replication_slots,table,node,permanent,prefix,pg_stat_replication_slots was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_slru,table,node,permanent,prefix,pg_stat_slru was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_ssl,table,node,permanent,prefix,pg_stat_ssl was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_subscription,table,node,permanent,prefix,pg_stat_subscription was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_subscription_stats,table,node,permanent,prefix,pg_stat_subscription_stats was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_sys_indexes,table,node,permanent,prefix,pg_stat_sys_indexes was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_sys_tables,table,node,permanent,prefix,pg_stat_sys_tables was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_user_functions,table,node,permanent,prefix,pg_stat_user_functions was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_user_indexes,table,node,permanent,prefix,pg_stat_user_indexes was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_user_tables,table,node,permanent,prefix,pg_stat_user_tables was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_wal,table,node,permanent,prefix,pg_stat_wal was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_wal_receiver,table,node,permanent,prefix,pg_stat_wal_receiver was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_xact_all_tables,table,node,permanent,prefix,pg_stat_xact_all_tables was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_xact_sys_tables,table,node,permanent,prefix,pg_stat_xact_sys_tables was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_xact_user_functions,table,node,permanent,prefix,pg_stat_xact_user_functions was created for compatibility and is currently unimplemented
-pg_catalog,pg_stat_xact_user_tables,table,node,permanent,prefix,pg_stat_xact_user_tables was created for compatibility and is currently unimplemented
-pg_catalog,pg_statio_all_indexes,table,node,permanent,prefix,pg_statio_all_indexes was created for compatibility and is currently unimplemented
-pg_catalog,pg_statio_all_sequences,table,node,permanent,prefix,pg_statio_all_sequences was created for compatibility and is currently unimplemented
-pg_catalog,pg_statio_all_tables,table,node,permanent,prefix,pg_statio_all_tables was created for compatibility and is currently unimplemented
-pg_catalog,pg_statio_sys_indexes,table,node,permanent,prefix,pg_statio_sys_indexes was created for compatibility and is currently unimplemented
-pg_catalog,pg_statio_sys_sequences,table,node,permanent,prefix,pg_statio_sys_sequences was created for compatibility and is currently unimplemented
-pg_catalog,pg_statio_sys_tables,table,node,permanent,prefix,pg_statio_sys_tables was created for compatibility and is currently unimplemented
-pg_catalog,pg_statio_user_indexes,table,node,permanent,prefix,pg_statio_user_indexes was created for compatibility and is currently unimplemented
-pg_catalog,pg_statio_user_sequences,table,node,permanent,prefix,pg_statio_user_sequences was created for compatibility and is currently unimplemented
-pg_catalog,pg_statio_user_tables,table,node,permanent,prefix,pg_statio_user_tables was created for compatibility and is currently unimplemented
-pg_catalog,pg_statistic,table,node,permanent,prefix,pg_statistic was created for compatibility and is currently unimplemented
-pg_catalog,pg_statistic_ext,table,node,permanent,prefix,"pg_statistic_ext has the statistics objects created with CREATE STATISTICS
+pg_catalog,pg_stat_all_indexes,table,node,permanent,prefix,NULL,pg_stat_all_indexes was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_all_tables,table,node,permanent,prefix,NULL,pg_stat_all_tables was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_archiver,table,node,permanent,prefix,NULL,pg_stat_archiver was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_bgwriter,table,node,permanent,prefix,NULL,pg_stat_bgwriter was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_checkpointer,table,node,permanent,prefix,NULL,pg_stat_checkpointer was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_database,table,node,permanent,prefix,NULL,pg_stat_database was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_database_conflicts,table,node,permanent,prefix,NULL,pg_stat_database_conflicts was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_gssapi,table,node,permanent,prefix,NULL,pg_stat_gssapi was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_io,table,node,permanent,prefix,NULL,pg_stat_io was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_progress_analyze,table,node,permanent,prefix,NULL,pg_stat_progress_analyze was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_progress_basebackup,table,node,permanent,prefix,NULL,pg_stat_progress_basebackup was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_progress_cluster,table,node,permanent,prefix,NULL,pg_stat_progress_cluster was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_progress_copy,table,node,permanent,prefix,NULL,pg_stat_progress_copy was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_progress_create_index,table,node,permanent,prefix,NULL,pg_stat_progress_create_index was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_progress_vacuum,table,node,permanent,prefix,NULL,pg_stat_progress_vacuum was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_recovery_prefetch,table,node,permanent,prefix,NULL,pg_stat_recovery_prefetch was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_replication,table,node,permanent,prefix,NULL,pg_stat_replication was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_replication_slots,table,node,permanent,prefix,NULL,pg_stat_replication_slots was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_slru,table,node,permanent,prefix,NULL,pg_stat_slru was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_ssl,table,node,permanent,prefix,NULL,pg_stat_ssl was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_subscription,table,node,permanent,prefix,NULL,pg_stat_subscription was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_subscription_stats,table,node,permanent,prefix,NULL,pg_stat_subscription_stats was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_sys_indexes,table,node,permanent,prefix,NULL,pg_stat_sys_indexes was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_sys_tables,table,node,permanent,prefix,NULL,pg_stat_sys_tables was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_user_functions,table,node,permanent,prefix,NULL,pg_stat_user_functions was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_user_indexes,table,node,permanent,prefix,NULL,pg_stat_user_indexes was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_user_tables,table,node,permanent,prefix,NULL,pg_stat_user_tables was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_wal,table,node,permanent,prefix,NULL,pg_stat_wal was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_wal_receiver,table,node,permanent,prefix,NULL,pg_stat_wal_receiver was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_xact_all_tables,table,node,permanent,prefix,NULL,pg_stat_xact_all_tables was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_xact_sys_tables,table,node,permanent,prefix,NULL,pg_stat_xact_sys_tables was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_xact_user_functions,table,node,permanent,prefix,NULL,pg_stat_xact_user_functions was created for compatibility and is currently unimplemented
+pg_catalog,pg_stat_xact_user_tables,table,node,permanent,prefix,NULL,pg_stat_xact_user_tables was created for compatibility and is currently unimplemented
+pg_catalog,pg_statio_all_indexes,table,node,permanent,prefix,NULL,pg_statio_all_indexes was created for compatibility and is currently unimplemented
+pg_catalog,pg_statio_all_sequences,table,node,permanent,prefix,NULL,pg_statio_all_sequences was created for compatibility and is currently unimplemented
+pg_catalog,pg_statio_all_tables,table,node,permanent,prefix,NULL,pg_statio_all_tables was created for compatibility and is currently unimplemented
+pg_catalog,pg_statio_sys_indexes,table,node,permanent,prefix,NULL,pg_statio_sys_indexes was created for compatibility and is currently unimplemented
+pg_catalog,pg_statio_sys_sequences,table,node,permanent,prefix,NULL,pg_statio_sys_sequences was created for compatibility and is currently unimplemented
+pg_catalog,pg_statio_sys_tables,table,node,permanent,prefix,NULL,pg_statio_sys_tables was created for compatibility and is currently unimplemented
+pg_catalog,pg_statio_user_indexes,table,node,permanent,prefix,NULL,pg_statio_user_indexes was created for compatibility and is currently unimplemented
+pg_catalog,pg_statio_user_sequences,table,node,permanent,prefix,NULL,pg_statio_user_sequences was created for compatibility and is currently unimplemented
+pg_catalog,pg_statio_user_tables,table,node,permanent,prefix,NULL,pg_statio_user_tables was created for compatibility and is currently unimplemented
+pg_catalog,pg_statistic,table,node,permanent,prefix,NULL,pg_statistic was created for compatibility and is currently unimplemented
+pg_catalog,pg_statistic_ext,table,node,permanent,prefix,NULL,"pg_statistic_ext has the statistics objects created with CREATE STATISTICS
https://www.postgresql.org/docs/18/catalog-pg-statistic-ext.html"
-pg_catalog,pg_statistic_ext_data,table,node,permanent,prefix,pg_statistic_ext_data was created for compatibility and is currently unimplemented
-pg_catalog,pg_stats,table,node,permanent,prefix,pg_stats was created for compatibility and is currently unimplemented
-pg_catalog,pg_stats_ext,table,node,permanent,prefix,pg_stats_ext was created for compatibility and is currently unimplemented
-pg_catalog,pg_stats_ext_exprs,table,node,permanent,prefix,pg_stats_ext_exprs was created for compatibility and is currently unimplemented
-pg_catalog,pg_subscription,table,node,permanent,prefix,pg_subscription was created for compatibility and is currently unimplemented
-pg_catalog,pg_subscription_rel,table,node,permanent,prefix,pg_subscription_rel was created for compatibility and is currently unimplemented
-pg_catalog,pg_tables,table,node,permanent,prefix,"tables summary (see also information_schema.tables, pg_catalog.pg_class)
+pg_catalog,pg_statistic_ext_data,table,node,permanent,prefix,NULL,pg_statistic_ext_data was created for compatibility and is currently unimplemented
+pg_catalog,pg_stats,table,node,permanent,prefix,NULL,pg_stats was created for compatibility and is currently unimplemented
+pg_catalog,pg_stats_ext,table,node,permanent,prefix,NULL,pg_stats_ext was created for compatibility and is currently unimplemented
+pg_catalog,pg_stats_ext_exprs,table,node,permanent,prefix,NULL,pg_stats_ext_exprs was created for compatibility and is currently unimplemented
+pg_catalog,pg_subscription,table,node,permanent,prefix,NULL,pg_subscription was created for compatibility and is currently unimplemented
+pg_catalog,pg_subscription_rel,table,node,permanent,prefix,NULL,pg_subscription_rel was created for compatibility and is currently unimplemented
+pg_catalog,pg_tables,table,node,permanent,prefix,NULL,"tables summary (see also information_schema.tables, pg_catalog.pg_class)
https://www.postgresql.org/docs/9.5/view-pg-tables.html"
-pg_catalog,pg_tablespace,table,node,permanent,prefix,"available tablespaces (incomplete; concept inapplicable to CockroachDB)
+pg_catalog,pg_tablespace,table,node,permanent,prefix,NULL,"available tablespaces (incomplete; concept inapplicable to CockroachDB)
https://www.postgresql.org/docs/9.5/catalog-pg-tablespace.html"
-pg_catalog,pg_timezone_abbrevs,table,node,permanent,prefix,pg_timezone_abbrevs lists the timezone abbreviations recognized by date/time literal parsing
-pg_catalog,pg_timezone_names,table,node,permanent,prefix,pg_timezone_names lists all the timezones that are supported by SET timezone
-pg_catalog,pg_transform,table,node,permanent,prefix,pg_transform was created for compatibility and is currently unimplemented
-pg_catalog,pg_trigger,table,node,permanent,prefix,"trigger definitions
+pg_catalog,pg_timezone_abbrevs,table,node,permanent,prefix,NULL,pg_timezone_abbrevs lists the timezone abbreviations recognized by date/time literal parsing
+pg_catalog,pg_timezone_names,table,node,permanent,prefix,NULL,pg_timezone_names lists all the timezones that are supported by SET timezone
+pg_catalog,pg_transform,table,node,permanent,prefix,NULL,pg_transform was created for compatibility and is currently unimplemented
+pg_catalog,pg_trigger,table,node,permanent,prefix,NULL,"trigger definitions
https://www.postgresql.org/docs/16/catalog-pg-trigger.html"
-pg_catalog,pg_ts_config,table,node,permanent,prefix,pg_ts_config was created for compatibility and is currently unimplemented
-pg_catalog,pg_ts_config_map,table,node,permanent,prefix,pg_ts_config_map was created for compatibility and is currently unimplemented
-pg_catalog,pg_ts_dict,table,node,permanent,prefix,pg_ts_dict was created for compatibility and is currently unimplemented
-pg_catalog,pg_ts_parser,table,node,permanent,prefix,pg_ts_parser was created for compatibility and is currently unimplemented
-pg_catalog,pg_ts_template,table,node,permanent,prefix,pg_ts_template was created for compatibility and is currently unimplemented
-pg_catalog,pg_type,table,node,permanent,prefix,"scalar types (incomplete)
+pg_catalog,pg_ts_config,table,node,permanent,prefix,NULL,pg_ts_config was created for compatibility and is currently unimplemented
+pg_catalog,pg_ts_config_map,table,node,permanent,prefix,NULL,pg_ts_config_map was created for compatibility and is currently unimplemented
+pg_catalog,pg_ts_dict,table,node,permanent,prefix,NULL,pg_ts_dict was created for compatibility and is currently unimplemented
+pg_catalog,pg_ts_parser,table,node,permanent,prefix,NULL,pg_ts_parser was created for compatibility and is currently unimplemented
+pg_catalog,pg_ts_template,table,node,permanent,prefix,NULL,pg_ts_template was created for compatibility and is currently unimplemented
+pg_catalog,pg_type,table,node,permanent,prefix,NULL,"scalar types (incomplete)
https://www.postgresql.org/docs/9.5/catalog-pg-type.html"
-pg_catalog,pg_user,table,node,permanent,prefix,"database users
+pg_catalog,pg_user,table,node,permanent,prefix,NULL,"database users
https://www.postgresql.org/docs/9.5/view-pg-user.html"
-pg_catalog,pg_user_mapping,table,node,permanent,prefix,"local to remote user mapping (empty - feature does not exist)
+pg_catalog,pg_user_mapping,table,node,permanent,prefix,NULL,"local to remote user mapping (empty - feature does not exist)
https://www.postgresql.org/docs/9.5/catalog-pg-user-mapping.html"
-pg_catalog,pg_user_mappings,table,node,permanent,prefix,pg_user_mappings was created for compatibility and is currently unimplemented
-pg_catalog,pg_views,table,node,permanent,prefix,"view definitions (incomplete - see also information_schema.views)
+pg_catalog,pg_user_mappings,table,node,permanent,prefix,NULL,pg_user_mappings was created for compatibility and is currently unimplemented
+pg_catalog,pg_views,table,node,permanent,prefix,NULL,"view definitions (incomplete - see also information_schema.views)
https://www.postgresql.org/docs/9.5/view-pg-views.html"
-pg_catalog,pg_wait_events,table,node,permanent,prefix,pg_wait_events was created for compatibility and is currently unimplemented
-pg_extension,geography_columns,table,node,permanent,prefix,Shows all defined geography columns. Matches PostGIS' geography_columns functionality.
-pg_extension,geometry_columns,table,node,permanent,prefix,Shows all defined geometry columns. Matches PostGIS' geometry_columns functionality.
-pg_extension,spatial_ref_sys,table,node,permanent,prefix,Shows all defined Spatial Reference Identifiers (SRIDs). Matches PostGIS' spatial_ref_sys table.
-public,ftable1,table,root,permanent,prefix,
-public,ftable2,table,root,permanent,prefix,
-public,mymview,materialized view,root,permanent,NULL,
-public,myseq,sequence,root,permanent,NULL,
-public,mytable,table,root,permanent,prefix,my awesome tb comment
-public,myview,view,root,permanent,NULL,
+pg_catalog,pg_wait_events,table,node,permanent,prefix,NULL,pg_wait_events was created for compatibility and is currently unimplemented
+pg_extension,geography_columns,table,node,permanent,prefix,NULL,Shows all defined geography columns. Matches PostGIS' geography_columns functionality.
+pg_extension,geometry_columns,table,node,permanent,prefix,NULL,Shows all defined geometry columns. Matches PostGIS' geometry_columns functionality.
+pg_extension,spatial_ref_sys,table,node,permanent,prefix,NULL,Shows all defined Spatial Reference Identifiers (SRIDs). Matches PostGIS' spatial_ref_sys table.
+public,ftable1,table,root,permanent,prefix,1024 bytes,
+public,ftable2,table,root,permanent,prefix,2048 bytes,
+public,mymview,materialized view,root,permanent,NULL,8192 bytes,
+public,myseq,sequence,root,permanent,NULL,0 bytes,
+public,mytable,table,root,permanent,prefix,16 kB,my awesome tb comment
+public,myview,view,root,permanent,NULL,0 bytes,
subtest end
@@ -757,6 +821,47 @@ public,ftable1,table,root
public,ftable2,table,root
public,mytable,table,root
+cli
+\dt+
+----
+sql -e \set echo -e \set display_format csv -e \dt+
+List of relations:
+> SELECT n.nspname as "Schema",
+ c.relname as "Name",
+ CASE c.relkind
+ WHEN 'r' THEN 'table'
+ WHEN 'v' THEN 'view'
+ WHEN 'm' THEN 'materialized view'
+ WHEN 'i' THEN 'index'
+ WHEN 'S' THEN 'sequence'
+ WHEN 's' THEN 'special'
+ WHEN 't' THEN 'TOAST table'
+ WHEN 'f' THEN 'foreign table'
+ WHEN 'p' THEN 'partitioned table'
+ WHEN 'I' THEN 'partitioned index'
+ END as "Type",
+ pg_catalog.pg_get_userbyid(c.relowner) as "Owner",
+ CASE c.relpersistence
+ WHEN 'p' THEN 'permanent'
+ WHEN 't' THEN 'temporary'
+ WHEN 'u' THEN 'unlogged' END AS "Persistence",
+ am.amname AS "Access Method",
+ pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) AS "Size",
+ COALESCE(pg_catalog.obj_description(c.oid, 'pg_class'),'') as "Description"
+ FROM pg_catalog.pg_class c
+LEFT JOIN pg_catalog.pg_namespace n on n.oid = c.relnamespace
+LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam
+ WHERE c.relkind IN ('r','p','')
+ AND n.nspname !~ '^pg_'
+ AND n.nspname <> 'information_schema'
+ AND n.nspname <> 'crdb_internal'
+ AND pg_catalog.pg_table_is_visible(c.oid)
+ ORDER BY 1,2
+Schema,Name,Type,Owner,Persistence,Access Method,Size,Description
+public,ftable1,table,root,permanent,prefix,1024 bytes,
+public,ftable2,table,root,permanent,prefix,2048 bytes,
+public,mytable,table,root,permanent,prefix,16 kB,my awesome tb comment
+
subtest end
subtest list_indexes
@@ -826,6 +931,7 @@ List of relations:
WHEN 't' THEN 'temporary'
WHEN 'u' THEN 'unlogged' END AS "Persistence",
am.amname AS "Access Method",
+ pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) AS "Size",
COALESCE(pg_catalog.obj_description(c.oid, 'pg_class'),'') as "Description"
FROM pg_catalog.pg_class@primary c
LEFT JOIN pg_catalog.pg_namespace n on n.oid = c.relnamespace
@@ -838,12 +944,12 @@ LEFT JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid
AND n.nspname <> 'crdb_internal'
AND pg_catalog.pg_table_is_visible(c.oid)
ORDER BY 1,2
-Schema,Name,Type,Owner,Table,Persistence,Access Method,Description
-public,ftable1_pkey,index,root,ftable1,permanent,prefix,
-public,ftable1_x_key,index,root,ftable1,permanent,prefix,
-public,ftable2_pkey,index,root,ftable2,permanent,prefix,
-public,myidx,index,root,mytable,permanent,prefix,my awesome idx comment
-public,mytable_pkey,index,root,mytable,permanent,prefix,
+Schema,Name,Type,Owner,Table,Persistence,Access Method,Size,Description
+public,ftable1_pkey,index,root,ftable1,permanent,prefix,NULL,
+public,ftable1_x_key,index,root,ftable1,permanent,prefix,NULL,
+public,ftable2_pkey,index,root,ftable2,permanent,prefix,NULL,
+public,myidx,index,root,mytable,permanent,prefix,NULL,my awesome idx comment
+public,mytable_pkey,index,root,mytable,permanent,prefix,NULL,
cli
\di myidx
@@ -903,6 +1009,7 @@ List of relations:
WHEN 't' THEN 'temporary'
WHEN 'u' THEN 'unlogged' END AS "Persistence",
am.amname AS "Access Method",
+ pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) AS "Size",
COALESCE(pg_catalog.obj_description(c.oid, 'pg_class'),'') as "Description"
FROM pg_catalog.pg_class@primary c
LEFT JOIN pg_catalog.pg_namespace n on n.oid = c.relnamespace
@@ -912,8 +1019,8 @@ LEFT JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid
WHERE c.relkind IN ('i','s','')
AND c.relname LIKE 'myidx'
ORDER BY 1,2
-Schema,Name,Type,Owner,Table,Persistence,Access Method,Description
-public,myidx,index,root,mytable,permanent,prefix,my awesome idx comment
+Schema,Name,Type,Owner,Table,Persistence,Access Method,Size,Description
+public,myidx,index,root,mytable,permanent,prefix,NULL,my awesome idx comment
subtest end
@@ -1005,6 +1112,7 @@ List of relations:
WHEN 't' THEN 'temporary'
WHEN 'u' THEN 'unlogged' END AS "Persistence",
am.amname AS "Access Method",
+ pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) AS "Size",
COALESCE(pg_catalog.obj_description(c.oid, 'pg_class'),'') as "Description"
FROM pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_namespace n on n.oid = c.relnamespace
@@ -1015,8 +1123,8 @@ LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam
AND n.nspname <> 'crdb_internal'
AND pg_catalog.pg_table_is_visible(c.oid)
ORDER BY 1,2
-Schema,Name,Type,Owner,Persistence,Access Method,Description
-public,mymview,materialized view,root,permanent,NULL,
+Schema,Name,Type,Owner,Persistence,Access Method,Size,Description
+public,mymview,materialized view,root,permanent,NULL,8192 bytes,
cli
\dm+ mymview
@@ -1043,6 +1151,7 @@ List of relations:
WHEN 't' THEN 'temporary'
WHEN 'u' THEN 'unlogged' END AS "Persistence",
am.amname AS "Access Method",
+ pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) AS "Size",
COALESCE(pg_catalog.obj_description(c.oid, 'pg_class'),'') as "Description"
FROM pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_namespace n on n.oid = c.relnamespace
@@ -1050,8 +1159,8 @@ LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam
WHERE c.relkind IN ('m','s','')
AND c.relname LIKE 'mymview'
ORDER BY 1,2
-Schema,Name,Type,Owner,Persistence,Access Method,Description
-public,mymview,materialized view,root,permanent,NULL,
+Schema,Name,Type,Owner,Persistence,Access Method,Size,Description
+public,mymview,materialized view,root,permanent,NULL,8192 bytes,
subtest end
@@ -1141,6 +1250,7 @@ List of relations:
WHEN 'p' THEN 'permanent'
WHEN 't' THEN 'temporary'
WHEN 'u' THEN 'unlogged' END AS "Persistence",
+ pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) AS "Size",
COALESCE(pg_catalog.obj_description(c.oid, 'pg_class'),'') as "Description"
FROM pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_namespace n on n.oid = c.relnamespace
@@ -1150,8 +1260,8 @@ LEFT JOIN pg_catalog.pg_namespace n on n.oid = c.relnamespace
AND n.nspname <> 'crdb_internal'
AND pg_catalog.pg_table_is_visible(c.oid)
ORDER BY 1,2
-Schema,Name,Type,Owner,Persistence,Description
-public,myview,view,root,permanent,
+Schema,Name,Type,Owner,Persistence,Size,Description
+public,myview,view,root,permanent,0 bytes,
cli
\dv+ myview
@@ -1177,14 +1287,15 @@ List of relations:
WHEN 'p' THEN 'permanent'
WHEN 't' THEN 'temporary'
WHEN 'u' THEN 'unlogged' END AS "Persistence",
+ pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) AS "Size",
COALESCE(pg_catalog.obj_description(c.oid, 'pg_class'),'') as "Description"
FROM pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_namespace n on n.oid = c.relnamespace
WHERE c.relkind IN ('v','s','')
AND c.relname LIKE 'myview'
ORDER BY 1,2
-Schema,Name,Type,Owner,Persistence,Description
-public,myview,view,root,permanent,
+Schema,Name,Type,Owner,Persistence,Size,Description
+public,myview,view,root,permanent,0 bytes,
subtest end
@@ -1245,6 +1356,7 @@ List of relations:
WHEN 'p' THEN 'permanent'
WHEN 't' THEN 'temporary'
WHEN 'u' THEN 'unlogged' END AS "Persistence",
+ pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) AS "Size",
COALESCE(pg_catalog.obj_description(c.oid, 'pg_class'),'') as "Description"
FROM pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_namespace n on n.oid = c.relnamespace
@@ -1254,8 +1366,8 @@ LEFT JOIN pg_catalog.pg_namespace n on n.oid = c.relnamespace
AND n.nspname <> 'crdb_internal'
AND pg_catalog.pg_table_is_visible(c.oid)
ORDER BY 1,2
-Schema,Name,Type,Owner,Persistence,Description
-public,myseq,sequence,root,permanent,
+Schema,Name,Type,Owner,Persistence,Size,Description
+public,myseq,sequence,root,permanent,0 bytes,
cli
\ds mys%
@@ -1309,14 +1421,15 @@ List of relations:
WHEN 'p' THEN 'permanent'
WHEN 't' THEN 'temporary'
WHEN 'u' THEN 'unlogged' END AS "Persistence",
+ pg_catalog.pg_size_pretty(pg_catalog.pg_table_size(c.oid)) AS "Size",
COALESCE(pg_catalog.obj_description(c.oid, 'pg_class'),'') as "Description"
FROM pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_namespace n on n.oid = c.relnamespace
WHERE c.relkind IN ('S','s','')
AND c.relname LIKE 'mys%'
ORDER BY 1,2
-Schema,Name,Type,Owner,Persistence,Description
-public,myseq,sequence,root,permanent,
+Schema,Name,Type,Owner,Persistence,Size,Description
+public,myseq,sequence,root,permanent,0 bytes,
subtest end
diff --git a/pkg/sql/logictest/testdata/logic_test/pg_builtins b/pkg/sql/logictest/testdata/logic_test/pg_builtins
index b7fe1e8cecbb..8016e358ae9d 100644
--- a/pkg/sql/logictest/testdata/logic_test/pg_builtins
+++ b/pkg/sql/logictest/testdata/logic_test/pg_builtins
@@ -1988,6 +1988,13 @@ SELECT pg_relation_size(c.oid)
----
4096
+# pg_indexes_size on a row with empty details returns 0, since there are
+# no per-index entries to sum.
+query I
+SELECT pg_indexes_size($relsize_oid::OID)
+----
+0
+
statement count 1
DELETE FROM system.table_metadata WHERE table_id = $relsize_oid
@@ -1995,3 +2002,110 @@ statement ok
DROP TABLE pg_relsize_t
subtest end
+
+subtest pg_size_per_index
+
+# Build a table with two secondary indexes and populate a synthetic cache
+# row whose details JSONB has per-index sizes. Then verify the math:
+# pg_relation_size = pg_table_size = primary index size only
+# pg_indexes_size = sum of secondary index sizes
+# pg_total_relation_size = whole table prefix (including any garbage)
+# pg_table_size + pg_indexes_size <= pg_total_relation_size
+statement ok
+CREATE TABLE pg_idxsize_t (id INT PRIMARY KEY, a STRING, b STRING, INDEX (a), INDEX (b))
+
+let $idxsize_oid
+SELECT 'pg_idxsize_t'::regclass::oid::INT
+
+let $idxsize_db_oid
+SELECT oid::INT FROM pg_database WHERE datname = current_database()
+
+# Determine the primary index_id and the two secondary index_ids from
+# crdb_internal.table_indexes so the synthetic details JSONB matches what
+# the real cache job would produce.
+let $idxsize_primary_id
+SELECT index_id FROM crdb_internal.table_indexes
+WHERE descriptor_id = $idxsize_oid AND index_type = 'primary'
+
+# Insert a synthetic cache row. replication_size_bytes is intentionally
+# 1000 larger than the sum of per-index sizes — that 1000-byte gap stands
+# in for dropped-index garbage.
+statement ok
+INSERT INTO system.table_metadata (
+ db_id, table_id, db_name, schema_name, table_name, total_columns,
+ total_indexes, store_ids, replication_size_bytes, total_ranges,
+ total_live_data_bytes, total_data_bytes, perc_live_data, table_type, details
+) VALUES (
+ $idxsize_db_oid, $idxsize_oid, current_database(), 'public', 'pg_idxsize_t',
+ 4, 3, ARRAY[1], 7000, 1, 7000, 7000, 1.0, 'TABLE',
+ json_build_object(
+ 'primary_index_id', $idxsize_primary_id,
+ 'index_sizes', json_build_object(
+ $idxsize_primary_id::TEXT, 4000,
+ (SELECT min(index_id) FROM crdb_internal.table_indexes
+ WHERE descriptor_id = $idxsize_oid AND index_type = 'secondary')::TEXT, 1000,
+ (SELECT max(index_id) FROM crdb_internal.table_indexes
+ WHERE descriptor_id = $idxsize_oid AND index_type = 'secondary')::TEXT, 1000
+ )
+ )
+)
+
+# Primary index size only.
+query I
+SELECT pg_relation_size($idxsize_oid::OID)
+----
+4000
+
+query I
+SELECT pg_table_size($idxsize_oid::OID)
+----
+4000
+
+# Sum of secondary index sizes.
+query I
+SELECT pg_indexes_size($idxsize_oid::OID)
+----
+2000
+
+# Whole table prefix, including the 1000-byte garbage gap.
+query I
+SELECT pg_total_relation_size($idxsize_oid::OID)
+----
+7000
+
+# Identity holds: pg_table_size + pg_indexes_size <= pg_total_relation_size.
+# The gap (1000 bytes here) is the GC-pressure proxy.
+query II
+SELECT pg_table_size($idxsize_oid::OID) + pg_indexes_size($idxsize_oid::OID),
+ pg_total_relation_size($idxsize_oid::OID)
+ - (pg_table_size($idxsize_oid::OID) + pg_indexes_size($idxsize_oid::OID))
+----
+6000 1000
+
+# pg_relation_size on a secondary index OID returns just that index's size.
+query TI rowsort
+SELECT c.relname, pg_relation_size(c.oid)
+ FROM pg_catalog.pg_class c
+ JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid
+ WHERE i.indrelid = $idxsize_oid AND c.relkind = 'i'
+ AND NOT i.indisprimary
+----
+pg_idxsize_t_a_idx 1000
+pg_idxsize_t_b_idx 1000
+
+# pg_relation_size on the primary index OID also returns its cached size.
+query I
+SELECT pg_relation_size(c.oid)
+ FROM pg_catalog.pg_class c
+ JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid
+ WHERE i.indrelid = $idxsize_oid AND i.indisprimary
+----
+4000
+
+statement count 1
+DELETE FROM system.table_metadata WHERE table_id = $idxsize_oid
+
+statement ok
+DROP TABLE pg_idxsize_t
+
+subtest end
diff --git a/pkg/sql/sem/builtins/fixed_oids.go b/pkg/sql/sem/builtins/fixed_oids.go
index 3cc72da19c1e..8fe742a2f4d0 100644
--- a/pkg/sql/sem/builtins/fixed_oids.go
+++ b/pkg/sql/sem/builtins/fixed_oids.go
@@ -2958,6 +2958,7 @@ var builtinOidsArray = []string{
3003: `pg_relation_size(relation_oid: oid) -> int`,
3004: `pg_table_size(relation_oid: oid) -> int`,
3005: `pg_total_relation_size(relation_oid: oid) -> int`,
+ 3006: `pg_indexes_size(relation_oid: oid) -> int`,
}
var builtinOidsBySignature map[string]oid.Oid
diff --git a/pkg/sql/sem/builtins/pg_builtins.go b/pkg/sql/sem/builtins/pg_builtins.go
index 8218d105a249..351144d62b45 100644
--- a/pkg/sql/sem/builtins/pg_builtins.go
+++ b/pkg/sql/sem/builtins/pg_builtins.go
@@ -35,6 +35,7 @@ import (
"github.com/cockroachdb/cockroach/pkg/sql/types"
"github.com/cockroachdb/cockroach/pkg/util/ipaddr"
"github.com/cockroachdb/errors"
+ "github.com/cockroachdb/redact"
"github.com/lib/pq/oid"
)
@@ -2635,27 +2636,94 @@ FROM defaults_parsed
Volatility: volatility.Volatile,
}),
- // pg_relation_size, pg_table_size, and pg_total_relation_size all return
- // the same cached value today: the total on-disk size of the entire table
- // keyspace (primary index data plus all secondary indexes). This matches
- // PG's pg_total_relation_size exactly. The other two are an over-count
- // vs PG by the size of secondary indexes; they are intentionally distinct
- // symbols so a future per-index extension to the metadata cache can
- // tighten pg_relation_size and pg_table_size downward without touching
- // pg_total_relation_size.
+ // pg_relation_size and pg_table_size return the size of the table's
+ // primary index — equivalent to PG's "heap only" since CRDB's primary
+ // index *is* the row data. pg_total_relation_size keeps reading the
+ // whole-table-prefix size from system.table_metadata, so any
+ // dropped-index garbage that hasn't yet been GC'd shows up as the
+ // difference between pg_total_relation_size and (pg_table_size +
+ // pg_indexes_size).
+ //
+ // During a mixed-version upgrade, cache rows written by the previous
+ // version may not yet have a primary_index_id field in details. Both
+ // pg_relation_size and pg_table_size fall back to replication_size_bytes
+ // in that case so callers see the v1 (over-counted) value rather than
+ // NULL or 0. Once the cluster is fully upgraded and the cache cycles
+ // once, all rows have the per-index data and the fallback is dormant.
"pg_relation_size": makeBuiltin(
tree.FunctionProperties{Category: builtinconstants.CategorySystemInfo, DistsqlBlocklist: true},
- relationSizeOverload("the relation"),
+ tree.Overload{
+ Types: tree.ParamTypes{{Name: "relation_oid", Typ: types.Oid}},
+ ReturnType: tree.FixedReturnType(types.Int),
+ // pg_relation_size accepts either a table OID or an index OID. The
+ // SQL dispatches between the two cases based on whether the OID
+ // resolves to a system.descriptor row (table) or a pg_class row
+ // of relkind 'i' (index). The table case bypasses pg_class
+ // visibility (matching the other size builtins); the index case
+ // goes through pg_class and respects its visibility filter.
+ Fn: func(ctx context.Context, evalCtx *eval.Context, args tree.Datums) (tree.Datum, error) {
+ return runRelationSizeLookup(ctx, evalCtx, "pg-relation-size",
+ relationSizeQuery, int64(tree.MustBeDOid(args[0]).Oid))
+ },
+ Info: "Returns the on-disk size, in bytes, of the relation with the given OID. " +
+ "For a table-class relation this is the primary index size (matching PG's " +
+ "\"heap only\" semantics, since CockroachDB's primary index is the row data). " +
+ "For an index this is the size of just that index. The size is read from a " +
+ "periodically-refreshed cache and may lag behind the true value by minutes. " +
+ "Returns NULL if no such relation is visible to the caller.",
+ Volatility: volatility.Volatile,
+ },
),
"pg_table_size": makeBuiltin(
tree.FunctionProperties{Category: builtinconstants.CategorySystemInfo, DistsqlBlocklist: true},
- relationSizeOverload("the table, including TOAST and visibility map (where applicable)"),
+ tableRelationSizeOverload(
+ "Returns the on-disk size, in bytes, of the table with the given OID, excluding "+
+ "indexes. In CockroachDB this is the primary index size, since the primary "+
+ "index is the row data. The size is read from a periodically-refreshed cache "+
+ "and may lag behind the true value by minutes.",
+ ),
),
"pg_total_relation_size": makeBuiltin(
tree.FunctionProperties{Category: builtinconstants.CategorySystemInfo, DistsqlBlocklist: true},
- relationSizeOverload("the relation, including all indexes"),
+ tree.Overload{
+ Types: tree.ParamTypes{{Name: "relation_oid", Typ: types.Oid}},
+ ReturnType: tree.FixedReturnType(types.Int),
+ Fn: func(ctx context.Context, evalCtx *eval.Context, args tree.Datums) (tree.Datum, error) {
+ return runRelationSizeLookup(ctx, evalCtx, "pg-total-relation-size",
+ totalRelationSizeQuery, int64(tree.MustBeDOid(args[0]).Oid))
+ },
+ Info: "Returns the on-disk size, in bytes, of the relation with the given OID, " +
+ "including all indexes and any data still occupying the table's keyspace " +
+ "(such as dropped-index data awaiting garbage collection). The size is read " +
+ "from a periodically-refreshed cache and may lag behind the true value by " +
+ "minutes.",
+ Volatility: volatility.Volatile,
+ },
+ ),
+
+ "pg_indexes_size": makeBuiltin(
+ tree.FunctionProperties{Category: builtinconstants.CategorySystemInfo, DistsqlBlocklist: true},
+ tree.Overload{
+ Types: tree.ParamTypes{{Name: "relation_oid", Typ: types.Oid}},
+ ReturnType: tree.FixedReturnType(types.Int),
+ Fn: func(ctx context.Context, evalCtx *eval.Context, args tree.Datums) (tree.Datum, error) {
+ return runRelationSizeLookup(ctx, evalCtx, "pg-indexes-size",
+ indexesSizeQuery, int64(tree.MustBeDOid(args[0]).Oid))
+ },
+ // CRDB diverges from PG slightly: PostgreSQL's pg_indexes_size
+ // includes the primary index, but in CRDB the primary index is the
+ // row data (it has no separate heap), so including it would
+ // double-count against pg_table_size and break the identity
+ // pg_table_size + pg_indexes_size <= pg_total_relation_size.
+ Info: "Returns the total on-disk size, in bytes, of the secondary indexes attached " +
+ "to the relation with the given OID. The primary index is excluded (it is " +
+ "reported by pg_relation_size and pg_table_size, since CockroachDB stores " +
+ "row data in the primary index). The size is read from a " +
+ "periodically-refreshed cache and may lag behind the true value by minutes.",
+ Volatility: volatility.Volatile,
+ },
),
// NOTE: these two builtins could be defined as user-defined functions, like
@@ -3718,12 +3786,12 @@ func databaseSizeByOid(
return row[1], nil
}
-// relationSizeQuery returns the cached on-disk size of the relation identified
-// by the supplied OID, or NULL if no such relation exists. The descriptor join
-// distinguishes "no descriptor" (NULL) from "descriptor exists but the cache
-// has not yet ingested it" (0). The query uses the NodeUser session-data
-// override at the call site to read the admin-only system.table_metadata.
-const relationSizeQuery = `
+// totalRelationSizeQuery returns the entire-table-prefix size of the relation
+// with the supplied OID. NULL if no descriptor exists; 0 if descriptor exists
+// but the cache hasn't ingested it. Includes any data sitting in the table's
+// keyspace that isn't in a live index (e.g., dropped-index garbage awaiting
+// GC). Used by pg_total_relation_size.
+const totalRelationSizeQuery = `
SELECT CASE WHEN d.id IS NOT NULL
THEN COALESCE(tm.replication_size_bytes, 0)::INT
ELSE NULL
@@ -3734,35 +3802,123 @@ SELECT CASE WHEN d.id IS NOT NULL
LEFT JOIN system.table_metadata tm
ON tm.table_id = input.id`
-// relationSizeOverload constructs a single-OID-argument overload that returns
-// the cached on-disk size for a relation. The three pg_*_size functions all
-// share this overload today; see the comment on pg_relation_size above for
-// the intentional separation between symbols.
-func relationSizeOverload(infoSubject string) tree.Overload {
+// tableRelationSizeQuery returns the size of the relation's primary index —
+// matching PG's "heap only" semantics for pg_relation_size and pg_table_size,
+// since CRDB's primary index *is* the row data.
+//
+// The mixed-version COALESCE fallback to replication_size_bytes covers cache
+// rows written before the per-index extension landed: those rows lack the
+// primary_index_id field in details. Falling back keeps the function
+// returning the v1 (over-counted) value rather than NULL or 0 for the brief
+// window between cluster upgrade and the next cache refresh.
+const tableRelationSizeQuery = `
+SELECT CASE WHEN d.id IS NOT NULL
+ THEN COALESCE(
+ (tm.details->'index_sizes'->>(tm.details->>'primary_index_id'))::INT,
+ tm.replication_size_bytes,
+ 0
+ )
+ ELSE NULL
+ END
+ FROM (SELECT $1::INT AS id) input
+ LEFT JOIN system.descriptor d
+ ON d.id = input.id
+ LEFT JOIN system.table_metadata tm
+ ON tm.table_id = input.id`
+
+// indexesSizeQuery returns the sum of secondary-index sizes for the relation
+// with the supplied OID. Excludes the primary index (which is reported by
+// pg_relation_size / pg_table_size). NULL if no descriptor exists; 0 if
+// descriptor exists but the cache lacks per-index data.
+const indexesSizeQuery = `
+SELECT CASE WHEN d.id IS NOT NULL
+ THEN COALESCE((
+ SELECT sum(value::INT)::INT
+ FROM jsonb_each_text(tm.details->'index_sizes')
+ WHERE key != (tm.details->>'primary_index_id')
+ ), 0)
+ ELSE NULL
+ END
+ FROM (SELECT $1::INT AS id) input
+ LEFT JOIN system.descriptor d
+ ON d.id = input.id
+ LEFT JOIN system.table_metadata tm
+ ON tm.table_id = input.id`
+
+// relationSizeQuery is the dispatch query used by pg_relation_size. It
+// returns the cached size for either a table-class relation (primary index
+// size, with the same mixed-version COALESCE fallback as
+// tableRelationSizeQuery) or an index (the entry in
+// details->'index_sizes' keyed by index_id, resolved via pg_class).
+//
+// Resolution priority:
+// - If the OID resolves to a system.descriptor row (table-class),
+// return the table primary-index size. This branch bypasses pg_class
+// visibility, matching the other table-size builtins.
+// - Else if the OID resolves to a pg_class row of relkind 'i' (index),
+// return that index's cached size. This branch respects pg_class
+// visibility — slight inconsistency with the table branch, but PG
+// itself routes index lookups through pg_class so this is closer to
+// PG's natural visibility model for indexes.
+// - Else return NULL.
+const relationSizeQuery = `
+SELECT CASE
+ WHEN d.id IS NOT NULL THEN COALESCE(
+ (tm.details->'index_sizes'->>(tm.details->>'primary_index_id'))::INT,
+ tm.replication_size_bytes,
+ 0
+ )
+ WHEN c.relkind = 'i' THEN COALESCE(
+ (tm_idx.details->'index_sizes'->>ti.index_id::TEXT)::INT,
+ 0
+ )
+ ELSE NULL
+ END
+ FROM (SELECT $1::INT AS id) input
+ LEFT JOIN system.descriptor d ON d.id = input.id
+ LEFT JOIN system.table_metadata tm
+ ON tm.table_id = input.id
+ LEFT JOIN pg_catalog.pg_class c ON c.oid = input.id
+ LEFT JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid
+ LEFT JOIN crdb_internal.table_indexes ti
+ ON ti.descriptor_id = i.indrelid AND ti.index_name = c.relname
+ LEFT JOIN system.table_metadata tm_idx
+ ON tm_idx.table_id = i.indrelid`
+
+// runRelationSizeLookup runs one of the *RelationSizeQuery statements above
+// with the given OID, using the NodeUser session-data override to bypass the
+// admin-only privileges on system.table_metadata. Returns NULL when the OID
+// does not resolve to a relation (or, for indexRelationSizeQuery, to a
+// pg_class row of relkind 'i').
+func runRelationSizeLookup(
+ ctx context.Context, evalCtx *eval.Context, opName, query string, oidArg int64,
+) (tree.Datum, error) {
+ row, err := evalCtx.Planner.QueryRowEx(
+ ctx, redact.RedactableString(opName),
+ sessiondata.NodeUserSessionDataOverride,
+ query, oidArg,
+ )
+ if err != nil {
+ return nil, err
+ }
+ if row == nil {
+ return tree.DNull, nil
+ }
+ return row[0], nil
+}
+
+// tableRelationSizeOverload constructs the single-OID overload that returns
+// the primary-index size for a table-class relation, used by pg_table_size.
+// Unlike pg_relation_size, pg_table_size does not also handle index OIDs.
+func tableRelationSizeOverload(info string) tree.Overload {
return tree.Overload{
Types: tree.ParamTypes{{Name: "relation_oid", Typ: types.Oid}},
ReturnType: tree.FixedReturnType(types.Int),
Fn: func(ctx context.Context, evalCtx *eval.Context, args tree.Datums) (tree.Datum, error) {
- oidArg := tree.MustBeDOid(args[0])
- row, err := evalCtx.Planner.QueryRowEx(
- ctx, "pg-relation-size",
- sessiondata.NodeUserSessionDataOverride,
- relationSizeQuery, int64(oidArg.Oid),
- )
- if err != nil {
- return nil, err
- }
- if row == nil {
- return tree.DNull, nil
- }
- return row[0], nil
+ return runRelationSizeLookup(ctx, evalCtx, "pg-table-size",
+ tableRelationSizeQuery, int64(tree.MustBeDOid(args[0]).Oid))
},
- Info: fmt.Sprintf(
- "Returns the on-disk size, in bytes, of %s with the given OID. The size is read "+
- "from a periodically-refreshed cache and may lag behind the true value by "+
- "minutes. Returns NULL if no such relation exists.",
- infoSubject,
- ),
+ Info: info,
// Volatile to match PostgreSQL's pg_proc.provolatile for these functions.
Volatility: volatility.Volatile,
}
diff --git a/pkg/sql/tablemetadatacache/BUILD.bazel b/pkg/sql/tablemetadatacache/BUILD.bazel
index 0b88b5b4ca52..0e4bbcff02b3 100644
--- a/pkg/sql/tablemetadatacache/BUILD.bazel
+++ b/pkg/sql/tablemetadatacache/BUILD.bazel
@@ -14,11 +14,14 @@ go_library(
"//pkg/base",
"//pkg/jobs",
"//pkg/jobs/jobspb",
+ "//pkg/keys",
"//pkg/roachpb",
"//pkg/settings",
"//pkg/settings/cluster",
"//pkg/sql",
+ "//pkg/sql/catalog/descpb",
"//pkg/sql/isql",
+ "//pkg/sql/rowenc",
"//pkg/sql/sem/tree",
"//pkg/sql/sessiondata",
"//pkg/sql/tablemetadatacache/util",
diff --git a/pkg/sql/tablemetadatacache/table_metadata_batch_iterator.go b/pkg/sql/tablemetadatacache/table_metadata_batch_iterator.go
index dbe89194e23d..caa44f6129cc 100644
--- a/pkg/sql/tablemetadatacache/table_metadata_batch_iterator.go
+++ b/pkg/sql/tablemetadatacache/table_metadata_batch_iterator.go
@@ -10,8 +10,11 @@ import (
"fmt"
"time"
+ "github.com/cockroachdb/cockroach/pkg/keys"
"github.com/cockroachdb/cockroach/pkg/roachpb"
+ "github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb"
"github.com/cockroachdb/cockroach/pkg/sql/isql"
+ "github.com/cockroachdb/cockroach/pkg/sql/rowenc"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/sql/sessiondata"
"github.com/cockroachdb/cockroach/pkg/util/log"
@@ -31,6 +34,8 @@ const (
autoStatsEnabledIdx
statsLastUpdatedIdx
spanIdx
+ primaryIndexIDIdx
+ secondaryIndexIDsIdx
// iterCols is the number of columns returned by the batch iterator.
iterCols
@@ -57,10 +62,19 @@ type tableMetadataIterRow struct {
tableType string
autoStatsEnabled *bool
statsLastUpdated *time.Time
+ // primaryIndexID is the ID of the table's primary index, or 0 if the
+ // relation has no primary index (e.g., views).
+ primaryIndexID descpb.IndexID
+ // indexSizes maps each live index ID to its replication size in bytes,
+ // derived from per-index spans in the same SpanStats batch as the
+ // table-level span. Empty for relations with no indexes.
+ indexSizes map[descpb.IndexID]int64
}
type tableMetadataBatchIterator struct {
ie isql.Executor
+ // codec is used to compute per-index span prefixes.
+ codec keys.SQLCodec
// The last ID that was read from the iterator.
lastID paginationKey
// The current batch of rows.
@@ -72,11 +86,16 @@ type tableMetadataBatchIterator struct {
}
func newTableMetadataBatchIterator(
- ie isql.Executor, spanStatsFetcher spanStatsFetcher, aostClause string, batchSize int64,
+ ie isql.Executor,
+ codec keys.SQLCodec,
+ spanStatsFetcher spanStatsFetcher,
+ aostClause string,
+ batchSize int64,
) *tableMetadataBatchIterator {
return &tableMetadataBatchIterator{
batchSize: batchSize,
ie: ie,
+ codec: codec,
spanStatsFetcher: spanStatsFetcher,
batchRows: make([]tableMetadataIterRow, 0, batchSize),
lastID: paginationKey{
@@ -110,10 +129,20 @@ func (batchIter *tableMetadataBatchIterator) fetchNextBatch(
// Rows are then joined with the system.descirptor table to get the
// table descriptor metadata.
//
- // We collect the table spans for the tables in the batch and issue
- // a single SpanStats rpc via the spanStatsFetcher to get the span
- // stats for all the tables in the batch.
- var spans roachpb.Spans
+ // We collect the table spans (one per row) and per-index spans (zero
+ // or more per row) and issue a single SpanStats rpc via the
+ // spanStatsFetcher. The response is keyed by span.String(), so the
+ // table and per-index sizes are demultiplexed back to each row after
+ // the RPC returns. tableSpans is kept parallel to batchRows;
+ // indexSpanRefs records the (row, index) attribution for each
+ // element of indexSpans.
+ var tableSpans roachpb.Spans
+ var indexSpans roachpb.Spans
+ type indexSpanRef struct {
+ rowIdx int
+ indexID descpb.IndexID
+ }
+ var indexSpanRefs []indexSpanRef
var itErr error
var exhausted bool
for {
@@ -192,11 +221,31 @@ func (batchIter *tableMetadataBatchIterator) fetchNextBatch(
}
dSpan := tree.MustBeDArray(row[spanIdx]).Array
- spans = append(spans, roachpb.Span{
+ tableSpans = append(tableSpans, roachpb.Span{
Key: []byte(tree.MustBeDBytes(dSpan[0])),
EndKey: []byte(tree.MustBeDBytes(dSpan[1])),
})
+ // Collect per-index spans for this row. Each becomes its own
+ // entry in the SpanStats request, paired with an indexSpanRef
+ // so the per-index size can be matched back to the right
+ // (row, index) after the RPC returns. Views have no primary
+ // index and no secondary indexes; sequences have a primary
+ // only; tables and matviews have both.
+ rowIdx := len(batchIter.batchRows)
+ if row[primaryIndexIDIdx] != tree.DNull {
+ iterRow.primaryIndexID = descpb.IndexID(tree.MustBeDInt(row[primaryIndexIDIdx]))
+ indexSpans = append(indexSpans, indexSpan(batchIter.codec, descpb.ID(iterRow.tableID), iterRow.primaryIndexID))
+ indexSpanRefs = append(indexSpanRefs, indexSpanRef{rowIdx: rowIdx, indexID: iterRow.primaryIndexID})
+ }
+ if row[secondaryIndexIDsIdx] != tree.DNull {
+ for _, d := range tree.MustBeDArray(row[secondaryIndexIDsIdx]).Array {
+ idxID := descpb.IndexID(tree.MustBeDInt(d))
+ indexSpans = append(indexSpans, indexSpan(batchIter.codec, descpb.ID(iterRow.tableID), idxID))
+ indexSpanRefs = append(indexSpanRefs, indexSpanRef{rowIdx: rowIdx, indexID: idxID})
+ }
+ }
+
batchIter.batchRows = append(batchIter.batchRows, iterRow)
}
}()
@@ -214,9 +263,15 @@ func (batchIter *tableMetadataBatchIterator) fetchNextBatch(
}
}
- // Collect the span stats for the tables in the batch.
+ // Collect the span stats for the tables and indexes in the batch in
+ // one round trip. Table-level spans come first, then per-index spans;
+ // the response is keyed by span.String() so order doesn't matter for
+ // demultiplexing.
+ allSpans := make(roachpb.Spans, 0, len(tableSpans)+len(indexSpans))
+ allSpans = append(allSpans, tableSpans...)
+ allSpans = append(allSpans, indexSpans...)
res, err := batchIter.spanStatsFetcher.SpanStats(ctx, &roachpb.SpanStatsRequest{
- Spans: spans,
+ Spans: allSpans,
NodeID: "0", // Fan out.
// Tablemetadata does not use the ApproximateTotalStats field, so we can skip the
// nodes making additional RangeStats RPCs to collect the MVCC stats across all replicas.
@@ -236,18 +291,35 @@ func (batchIter *tableMetadataBatchIterator) fetchNextBatch(
if res.SpanToStats != nil {
for i, row := range batchIter.batchRows {
- spanStats := res.SpanToStats[spans[i].String()]
+ spanStats := res.SpanToStats[tableSpans[i].String()]
if spanStats == nil {
continue
}
row.spanStats = *spanStats
batchIter.batchRows[i] = row
}
+ for i, ref := range indexSpanRefs {
+ spanStats := res.SpanToStats[indexSpans[i].String()]
+ if spanStats == nil {
+ continue
+ }
+ row := &batchIter.batchRows[ref.rowIdx]
+ if row.indexSizes == nil {
+ row.indexSizes = make(map[descpb.IndexID]int64)
+ }
+ row.indexSizes[ref.indexID] = int64(spanStats.ApproximateDiskBytes)
+ }
}
return true, nil
}
+// indexSpan returns the span containing the keys of the given index.
+func indexSpan(codec keys.SQLCodec, tableID descpb.ID, indexID descpb.IndexID) roachpb.Span {
+ start := roachpb.Key(rowenc.MakeIndexKeyPrefix(codec, tableID, indexID))
+ return roachpb.Span{Key: start, EndKey: start.PrefixEnd()}
+}
+
// newBatchQueryStatement creates a query statement to fetch batches of table metadata to insert into
// system.table_metadata.
func newBatchQueryStatement(aostClause string) string {
@@ -292,7 +364,12 @@ SELECT
WHERE "tableID" = n_id
GROUP BY "tableID"
),
- crdb_internal.table_span(n_id) as span
+ crdb_internal.table_span(n_id) as span,
+ (d->'table'->'primaryIndex'->>'id')::INT as primary_index_id,
+ (
+ SELECT array_agg((idx->>'id')::INT)
+ FROM jsonb_array_elements(d->'table'->'indexes') idx
+ ) as secondary_index_ids
FROM
cte
%[1]s
diff --git a/pkg/sql/tablemetadatacache/table_metadata_updater.go b/pkg/sql/tablemetadatacache/table_metadata_updater.go
index 1d298e0aef53..6d5b85137f4a 100644
--- a/pkg/sql/tablemetadatacache/table_metadata_updater.go
+++ b/pkg/sql/tablemetadatacache/table_metadata_updater.go
@@ -13,7 +13,9 @@ import (
"math"
"time"
+ "github.com/cockroachdb/cockroach/pkg/keys"
"github.com/cockroachdb/cockroach/pkg/roachpb"
+ "github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb"
"github.com/cockroachdb/cockroach/pkg/sql/isql"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/sql/sessiondata"
@@ -31,6 +33,7 @@ type spanStatsFetcher interface {
// tableMetadataUpdater encapsulates the logic for updating the table metadata cache.
type tableMetadataUpdater struct {
ie isql.Executor
+ codec keys.SQLCodec
timeSrc timeutil.TimeSource
metrics *TableMetadataUpdateJobMetrics
updateProgress func(ctx context.Context, progress float32)
@@ -52,6 +55,17 @@ type tableMetadataDetails struct {
// ReplicaCount is the number of voter and non-voter replicas
// containing data for the table.
ReplicaCount int32 `json:"replica_count"`
+ // PrimaryIndexID is the index ID of the table's primary index, or 0 if
+ // the relation has no primary index (e.g., views). Stored alongside
+ // IndexSizes so size functions can identify the primary entry without
+ // an additional catalog lookup.
+ PrimaryIndexID descpb.IndexID `json:"primary_index_id,omitempty"`
+ // IndexSizes maps each live index ID (primary and secondary) to its
+ // replication size in bytes. Empty for relations without indexes.
+ // Note: integer map keys are JSON-encoded as strings, so callers
+ // reading this from SQL should access entries via
+ // `details->'index_sizes'->>''`.
+ IndexSizes map[descpb.IndexID]int64 `json:"index_sizes,omitempty"`
}
var _ tablemetadatacacheutil.ITableMetadataUpdater = &tableMetadataUpdater{}
@@ -62,12 +76,14 @@ func newTableMetadataUpdater(
metrics *TableMetadataUpdateJobMetrics,
spanStatsFetcher spanStatsFetcher,
ie isql.Executor,
+ codec keys.SQLCodec,
timeSrc timeutil.TimeSource,
batchSize int64,
testKnobs *tablemetadatacacheutil.TestingKnobs,
) *tableMetadataUpdater {
return &tableMetadataUpdater{
ie: ie,
+ codec: codec,
metrics: metrics,
updateProgress: onProgressUpdated,
spanStatsFetcher: spanStatsFetcher,
@@ -98,7 +114,7 @@ func (u *tableMetadataUpdater) updateCache(ctx context.Context) (updated int, er
// upsertQuery is the query used to upsert table metadata rows,
// it is reused for each batch to avoid allocations between batches.
upsert := newTableMetadataBatchUpsertQuery(u.batchSize)
- it := newTableMetadataBatchIterator(u.ie, u.spanStatsFetcher, u.testKnobs.GetAOSTClause(), u.batchSize)
+ it := newTableMetadataBatchIterator(u.ie, u.codec, u.spanStatsFetcher, u.testKnobs.GetAOSTClause(), u.batchSize)
estimatedRowsToUpdate, err := u.getRowsToUpdateCount(ctx)
if err != nil {
log.Dev.Errorf(ctx, "failed to get estimated row count. err=%s", err.Error())
@@ -296,6 +312,8 @@ func (q *tableMetadataBatchUpsertQuery) addRow(
AutoStatsEnabled: row.autoStatsEnabled,
StatsLastUpdated: row.statsLastUpdated,
ReplicaCount: stats.ReplicaCount,
+ PrimaryIndexID: row.primaryIndexID,
+ IndexSizes: row.indexSizes,
}
detailsStr, err := gojson.Marshal(details)
if err != nil {
diff --git a/pkg/sql/tablemetadatacache/table_metadata_updater_test.go b/pkg/sql/tablemetadatacache/table_metadata_updater_test.go
index 3c4759a5dacc..6ba426b803ba 100644
--- a/pkg/sql/tablemetadatacache/table_metadata_updater_test.go
+++ b/pkg/sql/tablemetadatacache/table_metadata_updater_test.go
@@ -96,7 +96,7 @@ func TestDataDrivenTableMetadataCacheUpdater(t *testing.T) {
},
}
}
- updater := newTableMetadataUpdater(mockUpdateProgress, &metrics, spanStatsSrv, s.InternalExecutor().(isql.Executor), mockTimeSrc, batchSize, knobs)
+ updater := newTableMetadataUpdater(mockUpdateProgress, &metrics, spanStatsSrv, s.InternalExecutor().(isql.Executor), s.Codec(), mockTimeSrc, batchSize, knobs)
prevUpdatedTables := metrics.UpdatedTables.Count()
prevErrs := metrics.Errors.Count()
err := updater.RunUpdater(ctx)
@@ -109,7 +109,7 @@ func TestDataDrivenTableMetadataCacheUpdater(t *testing.T) {
metrics.NumRuns.Count(),
metrics.Duration.CumulativeSnapshot().Mean() > 0)
case "prune-cache":
- updater := newTableMetadataUpdater(mockUpdateProgress, &metrics, spanStatsServer, s.InternalExecutor().(isql.Executor), mockTimeSrc, batchSize, knobs)
+ updater := newTableMetadataUpdater(mockUpdateProgress, &metrics, spanStatsServer, s.InternalExecutor().(isql.Executor), s.Codec(), mockTimeSrc, batchSize, knobs)
pruned, err := updater.pruneCache(ctx)
if err != nil {
return err.Error()
@@ -173,6 +173,7 @@ func TestTableMetadataUpdateJobProgressAndMetrics(t *testing.T) {
&metrics,
s.TenantStatusServer().(serverpb.TenantStatusServer),
s.ExecutorConfig().(sql.ExecutorConfig).InternalDB.Executor(),
+ s.Codec(),
timeutil.DefaultTimeSource{},
defaultTableBatchSize,
knobs,
diff --git a/pkg/sql/tablemetadatacache/testdata/explain_tmj_select b/pkg/sql/tablemetadatacache/testdata/explain_tmj_select
index f0f511cb4ad9..36826ff6c07e 100644
--- a/pkg/sql/tablemetadatacache/testdata/explain_tmj_select
+++ b/pkg/sql/tablemetadatacache/testdata/explain_tmj_select
@@ -42,7 +42,12 @@ SELECT
WHERE "tableID" = n_id
GROUP BY "tableID"
),
- crdb_internal.table_span(n_id) as span
+ crdb_internal.table_span(n_id) as span,
+ (d->'table'->'primaryIndex'->>'id')::INT as primary_index_id,
+ (
+ SELECT array_agg((idx->>'id')::INT)
+ FROM jsonb_array_elements(d->'table'->'indexes') idx
+ ) as secondary_index_ids
FROM
cte
AS OF SYSTEM TIME '-1us'
@@ -52,51 +57,70 @@ distribution: local
• render
│
-└── • apply join (left outer)
- │
- ├── • render
- │ │
- │ └── • limit
- │ │ count: ‹×›
- │ │
- │ └── • lookup join
- │ │ table: descriptor@primary
- │ │ equality: (id) = (id)
- │ │ equality cols are key
- │ │
- │ └── • sort
- │ │ order: +"parentID",+"parentSchemaID",+name
- │ │
- │ └── • hash join
- │ │ equality: (parentID, parentSchemaID) = (id, id)
- │ │
- │ ├── • filter
- │ │ │ filter: "parentSchemaID" != ‹×›
- │ │ │
- │ │ └── • scan
- │ │ missing stats
- │ │ table: namespace@primary
- │ │ spans: 1 span
- │ │
- │ └── • lookup join
- │ │ table: namespace@primary
- │ │ equality: (id) = (parentID)
- │ │ pred: id != ‹×›
- │ │
- │ └── • scan
- │ missing stats
- │ table: namespace@primary
- │ spans: 1 span
+└── • group (hash)
+ │ group by: rownum
│
- └── • inner loop (unoptimized)
+ └── • apply join (left outer)
+ │
+ ├── • ordinality
+ │ │
+ │ └── • apply join (left outer)
+ │ │
+ │ ├── • render
+ │ │ │
+ │ │ └── • limit
+ │ │ │ count: ‹×›
+ │ │ │
+ │ │ └── • lookup join
+ │ │ │ table: descriptor@primary
+ │ │ │ equality: (id) = (id)
+ │ │ │ equality cols are key
+ │ │ │
+ │ │ └── • sort
+ │ │ │ order: +"parentID",+"parentSchemaID",+name
+ │ │ │
+ │ │ └── • hash join
+ │ │ │ equality: (parentID, parentSchemaID) = (id, id)
+ │ │ │
+ │ │ ├── • filter
+ │ │ │ │ filter: "parentSchemaID" != ‹×›
+ │ │ │ │
+ │ │ │ └── • scan
+ │ │ │ missing stats
+ │ │ │ table: namespace@primary
+ │ │ │ spans: 1 span
+ │ │ │
+ │ │ └── • lookup join
+ │ │ │ table: namespace@primary
+ │ │ │ equality: (id) = (parentID)
+ │ │ │ pred: id != ‹×›
+ │ │ │
+ │ │ └── • scan
+ │ │ missing stats
+ │ │ table: namespace@primary
+ │ │ spans: 1 span
+ │ │
+ │ └── • inner loop (unoptimized)
+ │ │
+ │ └── • group-by (streaming)
+ │ ├── select
+ │ │ ├── scan table_statistics
+ │ │ └── filters
+ │ │ └── tableID = n_id
+ │ └── aggregations
+ │ └── max
+ │ └── createdAt
│
- └── • group-by (streaming)
- ├── select
- │ ├── scan table_statistics
- │ └── filters
- │ └── tableID = n_id
- └── aggregations
- └── max
- └── createdAt
+ └── • inner loop (unoptimized)
+ │
+ └── • project
+ ├── project-set
+ │ ├── values
+ │ │ └── ()
+ │ └── zip
+ │ └── jsonb_array_elements((d->‹×›)->‹×›)
+ └── projections
+ ├── ‹×›
+ └── (value->>‹×›)::INT8
----
----
diff --git a/pkg/sql/tablemetadatacache/testdata/update_table_metadata b/pkg/sql/tablemetadatacache/testdata/update_table_metadata
index b93daca60ff8..ab6d587b7ba7 100644
--- a/pkg/sql/tablemetadatacache/testdata/update_table_metadata
+++ b/pkg/sql/tablemetadatacache/testdata/update_table_metadata
@@ -99,6 +99,44 @@ SELECT count(*) FROM system.table_metadata WHERE total_live_data_bytes > total_d
----
0
+# Every TABLE/SEQUENCE/MATERIALIZED_VIEW should have a primary_index_id
+# recorded. Plain views don't have a primary index.
+query
+SELECT count(*) FROM system.table_metadata
+WHERE table_type IN ('TABLE', 'SEQUENCE', 'MATERIALIZED_VIEW')
+ AND (details->>'primary_index_id')::INT IS NULL
+----
+0
+
+# And those same relations should have an index_sizes entry for the
+# primary.
+query
+SELECT count(*) FROM system.table_metadata
+WHERE table_type IN ('TABLE', 'SEQUENCE', 'MATERIALIZED_VIEW')
+ AND details->'index_sizes'->>(details->>'primary_index_id') IS NULL
+----
+0
+
+# Plain views have no live indexes, so index_sizes is empty/absent.
+query
+SELECT details->'index_sizes' FROM system.table_metadata
+WHERE table_name = 'myview'
+----
+
+
+# mytable2 has a single index (primary). The sum of its index_sizes
+# entries should equal its replication_size_bytes (modulo dropped-index
+# garbage, which there isn't any of for a freshly created table).
+query
+SELECT replication_size_bytes - (
+ SELECT COALESCE(sum(value::INT), 0)
+ FROM jsonb_each_text(details->'index_sizes')
+ )
+FROM system.table_metadata
+WHERE table_name = 'mytable2'
+----
+0
+
query
DROP TABLE mytable
----
diff --git a/pkg/sql/tablemetadatacache/testdata/update_table_metadata_errors b/pkg/sql/tablemetadatacache/testdata/update_table_metadata_errors
index 73357b373ef0..1bd23ba6c080 100644
--- a/pkg/sql/tablemetadatacache/testdata/update_table_metadata_errors
+++ b/pkg/sql/tablemetadatacache/testdata/update_table_metadata_errors
@@ -12,9 +12,9 @@ SELECT * FROM system.table_metadata
WHERE db_name <> 'system' OR table_name IN ('comments', 'locations', 'descriptor_id_seq')
ORDER BY (db_name, table_name)
----
-1 24 system public comments 4 1 {} 0 0 0 0 0 An error has occurred while fetching span stats. 2021-01-07 06:13:20 +0000 UTC TABLE {"auto_stats_enabled": null, "replica_count": 0, "stats_last_updated": null}
-1 7 system public descriptor_id_seq 1 1 {} 0 0 0 0 0 An error has occurred while fetching span stats. 2021-01-07 06:13:20 +0000 UTC SEQUENCE {"auto_stats_enabled": null, "replica_count": 0, "stats_last_updated": null}
-1 21 system public locations 4 1 {} 0 0 0 0 0 An error has occurred while fetching span stats. 2021-01-07 06:13:20 +0000 UTC TABLE {"auto_stats_enabled": null, "replica_count": 0, "stats_last_updated": null}
+1 24 system public comments 4 1 {} 0 0 0 0 0 An error has occurred while fetching span stats. 2021-01-07 06:13:20 +0000 UTC TABLE {"auto_stats_enabled": null, "primary_index_id": 1, "replica_count": 0, "stats_last_updated": null}
+1 7 system public descriptor_id_seq 1 1 {} 0 0 0 0 0 An error has occurred while fetching span stats. 2021-01-07 06:13:20 +0000 UTC SEQUENCE {"auto_stats_enabled": null, "primary_index_id": 1, "replica_count": 0, "stats_last_updated": null}
+1 21 system public locations 4 1 {} 0 0 0 0 0 An error has occurred while fetching span stats. 2021-01-07 06:13:20 +0000 UTC TABLE {"auto_stats_enabled": null, "primary_index_id": 1, "replica_count": 0, "stats_last_updated": null}
set-time unixSecs=1710000000
diff --git a/pkg/sql/tablemetadatacache/update_table_metadata_cache_job.go b/pkg/sql/tablemetadatacache/update_table_metadata_cache_job.go
index 77afb1de7641..9c45e964d9b0 100644
--- a/pkg/sql/tablemetadatacache/update_table_metadata_cache_job.go
+++ b/pkg/sql/tablemetadatacache/update_table_metadata_cache_job.go
@@ -65,6 +65,7 @@ func (j *tableMetadataUpdateJobResumer) Resume(ctx context.Context, execCtxI int
&metrics,
execCtx.ExecCfg().TenantStatusServer,
execCtx.ExecCfg().InternalDB.Executor(),
+ execCtx.ExecCfg().Codec,
timeutil.DefaultTimeSource{},
updateJobBatchSizeSetting.Get(&execCtx.ExecCfg().Settings.SV),
testKnobs)
| |