github.com/netdata/go.d.plugin@v0.58.1/modules/postgres/queries.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package postgres
     4  
     5  func queryServerVersion() string {
     6  	return "SHOW server_version_num;"
     7  }
     8  
     9  func queryIsSuperUser() string {
    10  	return "SELECT current_setting('is_superuser') = 'on' AS is_superuser;"
    11  }
    12  
    13  func queryPGIsInRecovery() string {
    14  	return "SELECT pg_is_in_recovery();"
    15  }
    16  
    17  func querySettingsMaxConnections() string {
    18  	return "SELECT current_setting('max_connections')::INT - current_setting('superuser_reserved_connections')::INT;"
    19  }
    20  
    21  func querySettingsMaxLocksHeld() string {
    22  	return `
    23  SELECT current_setting('max_locks_per_transaction')::INT *
    24         (current_setting('max_connections')::INT + current_setting('max_prepared_transactions')::INT);
    25  `
    26  }
    27  
    28  // TODO: this is not correct and we should use pg_stat_activity.
    29  // But we need to check what connections (backend_type) count towards 'max_connections'.
    30  // I think python version query doesn't count it correctly.
    31  // https://github.com/netdata/netdata/blob/1782e2d002bc5203128e5a5d2b801010e2822d2d/collectors/python.d.plugin/postgres/postgres.chart.py#L266
    32  func queryServerCurrentConnectionsUsed() string {
    33  	return "SELECT sum(numbackends) FROM pg_stat_database;"
    34  }
    35  
    36  func queryServerConnectionsState() string {
    37  	return `
    38  SELECT state,
    39         COUNT(*)
    40  FROM pg_stat_activity
    41  WHERE state IN
    42        (
    43         'active',
    44         'idle',
    45         'idle in transaction',
    46         'idle in transaction (aborted)',
    47         'fastpath function call',
    48         'disabled'
    49            )
    50  GROUP BY state;
    51  `
    52  }
    53  
    54  func queryCheckpoints() string {
    55  	// definition by version: https://pgpedia.info/p/pg_stat_bgwriter.html
    56  	// docs: https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-BGWRITER-VIEW
    57  	// code: https://github.com/postgres/postgres/blob/366283961ac0ed6d89014444c6090f3fd02fce0a/src/backend/catalog/system_views.sql#L1104
    58  
    59  	return `
    60  SELECT checkpoints_timed,
    61         checkpoints_req,
    62         checkpoint_write_time,
    63         checkpoint_sync_time,
    64         buffers_checkpoint * current_setting('block_size')::numeric AS buffers_checkpoint_bytes,
    65         buffers_clean * current_setting('block_size')::numeric      AS buffers_clean_bytes,
    66         maxwritten_clean,
    67         buffers_backend * current_setting('block_size')::numeric    AS buffers_backend_bytes,
    68         buffers_backend_fsync,
    69         buffers_alloc * current_setting('block_size')::numeric      AS buffers_alloc_bytes
    70  FROM pg_stat_bgwriter;
    71  `
    72  }
    73  
    74  func queryServerUptime() string {
    75  	return `SELECT EXTRACT(epoch FROM CURRENT_TIMESTAMP - pg_postmaster_start_time());`
    76  }
    77  
    78  func queryTXIDWraparound() string {
    79  	// https://www.crunchydata.com/blog/managing-transaction-id-wraparound-in-postgresql
    80  	return `
    81      WITH max_age AS ( SELECT
    82          2000000000 as max_old_xid,
    83          setting AS autovacuum_freeze_max_age 
    84      FROM
    85          pg_catalog.pg_settings 
    86      WHERE
    87          name = 'autovacuum_freeze_max_age'), per_database_stats AS ( SELECT
    88          datname ,
    89          m.max_old_xid::int ,
    90          m.autovacuum_freeze_max_age::int ,
    91          age(d.datfrozenxid) AS oldest_current_xid 
    92      FROM
    93          pg_catalog.pg_database d 
    94      JOIN
    95          max_age m 
    96              ON (true) 
    97      WHERE
    98          d.datallowconn) SELECT
    99          max(oldest_current_xid) AS oldest_current_xid ,
   100          max(ROUND(100*(oldest_current_xid/max_old_xid::float))) AS percent_towards_wraparound ,
   101          max(ROUND(100*(oldest_current_xid/autovacuum_freeze_max_age::float))) AS percent_towards_emergency_autovacuum 
   102      FROM
   103          per_database_stats;
   104  `
   105  }
   106  
   107  func queryWALWrites(version int) string {
   108  	if version < pgVersion10 {
   109  		return `
   110  SELECT
   111      pg_xlog_location_diff( 
   112      CASE
   113          pg_is_in_recovery() 
   114          WHEN
   115              TRUE 
   116          THEN
   117              pg_last_xlog_receive_location() 
   118          ELSE
   119              pg_current_xlog_location() 
   120      END
   121  , '0/0') AS wal_writes ;
   122  `
   123  	}
   124  	return `
   125  SELECT
   126      pg_wal_lsn_diff( 
   127      CASE
   128          pg_is_in_recovery() 
   129          WHEN
   130              TRUE 
   131          THEN
   132              pg_last_wal_receive_lsn() 
   133          ELSE
   134              pg_current_wal_lsn() 
   135      END
   136  , '0/0') AS wal_writes ;
   137  `
   138  }
   139  
   140  func queryWALFiles(version int) string {
   141  	if version < pgVersion10 {
   142  		return `
   143  SELECT count(*) FILTER (WHERE type = 'recycled') AS wal_recycled_files,
   144         count(*) FILTER (WHERE type = 'written')  AS wal_written_files
   145  FROM (SELECT wal.name,
   146               pg_xlogfile_name(
   147                       CASE pg_is_in_recovery()
   148                           WHEN true THEN NULL
   149                           ELSE pg_current_xlog_location()
   150                           END),
   151               CASE
   152                   WHEN wal.name > pg_xlogfile_name(
   153                           CASE pg_is_in_recovery()
   154                               WHEN true THEN NULL
   155                               ELSE pg_current_xlog_location()
   156                               END) THEN 'recycled'
   157                   ELSE 'written'
   158                   END AS type
   159        FROM pg_catalog.pg_ls_dir('pg_xlog') AS wal(name)
   160        WHERE name ~ '^[0-9A-F]{24}$'
   161        ORDER BY (pg_stat_file('pg_xlog/' || name, true)).modification,
   162                 wal.name DESC) sub;
   163  `
   164  	}
   165  	return `
   166  SELECT count(*) FILTER (WHERE type = 'recycled') AS wal_recycled_files,
   167         count(*) FILTER (WHERE type = 'written')  AS wal_written_files
   168  FROM (SELECT wal.name,
   169               pg_walfile_name(
   170                       CASE pg_is_in_recovery()
   171                           WHEN true THEN NULL
   172                           ELSE pg_current_wal_lsn()
   173                           END),
   174               CASE
   175                   WHEN wal.name > pg_walfile_name(
   176                           CASE pg_is_in_recovery()
   177                               WHEN true THEN NULL
   178                               ELSE pg_current_wal_lsn()
   179                               END) THEN 'recycled'
   180                   ELSE 'written'
   181                   END AS type
   182        FROM pg_catalog.pg_ls_dir('pg_wal') AS wal(name)
   183        WHERE name ~ '^[0-9A-F]{24}$'
   184        ORDER BY (pg_stat_file('pg_wal/' || name, true)).modification,
   185                 wal.name DESC) sub;
   186  `
   187  }
   188  
   189  func queryWALArchiveFiles(version int) string {
   190  	if version < pgVersion10 {
   191  		return `
   192      SELECT
   193          CAST(COALESCE(SUM(CAST(archive_file ~ $r$\.ready$$r$ as INT)),
   194          0) AS INT) AS wal_archive_files_ready_count,
   195          CAST(COALESCE(SUM(CAST(archive_file ~ $r$\.done$$r$ AS INT)),
   196          0) AS INT)  AS wal_archive_files_done_count 
   197      FROM
   198          pg_catalog.pg_ls_dir('pg_xlog/archive_status') AS archive_files (archive_file);
   199  `
   200  	}
   201  	return `
   202      SELECT
   203          CAST(COALESCE(SUM(CAST(archive_file ~ $r$\.ready$$r$ as INT)),
   204          0) AS INT) AS wal_archive_files_ready_count,
   205          CAST(COALESCE(SUM(CAST(archive_file ~ $r$\.done$$r$ AS INT)),
   206          0) AS INT)  AS wal_archive_files_done_count 
   207      FROM
   208          pg_catalog.pg_ls_dir('pg_wal/archive_status') AS archive_files (archive_file);
   209  `
   210  }
   211  
   212  func queryCatalogRelations() string {
   213  	// kind of same as
   214  	// https://github.com/netdata/netdata/blob/750810e1798e09cc6210e83594eb9ed4905f8f12/collectors/python.d.plugin/postgres/postgres.chart.py#L336-L354
   215  	// TODO: do we need that? It is optional and disabled by default in py version.
   216  	return `
   217  SELECT relkind,
   218         COUNT(1),
   219         SUM(relpages) * current_setting('block_size')::NUMERIC AS size
   220  FROM pg_class
   221  GROUP BY relkind;
   222  `
   223  }
   224  
   225  func queryAutovacuumWorkers() string {
   226  	// https://github.com/postgres/postgres/blob/9e4f914b5eba3f49ab99bdecdc4f96fac099571f/src/backend/postmaster/autovacuum.c#L3168-L3183
   227  	return `
   228  SELECT count(*) FILTER (
   229      WHERE
   230              query LIKE 'autovacuum: ANALYZE%%'
   231          AND query NOT LIKE '%%to prevent wraparound%%'
   232      )        AS autovacuum_analyze,
   233         count(*) FILTER (
   234             WHERE
   235                     query LIKE 'autovacuum: VACUUM ANALYZE%%'
   236                 AND query NOT LIKE '%%to prevent wraparound%%'
   237             ) AS autovacuum_vacuum_analyze,
   238         count(*) FILTER (
   239             WHERE
   240                     query LIKE 'autovacuum: VACUUM %.%%'
   241                 AND query NOT LIKE '%%to prevent wraparound%%'
   242             ) AS autovacuum_vacuum,
   243         count(*) FILTER (
   244             WHERE
   245             query LIKE '%%to prevent wraparound%%'
   246             ) AS autovacuum_vacuum_freeze,
   247         count(*) FILTER (
   248             WHERE
   249             query LIKE 'autovacuum: BRIN summarize%%'
   250             ) AS autovacuum_brin_summarize
   251  FROM pg_stat_activity
   252  WHERE query NOT LIKE '%%pg_stat_activity%%';
   253  `
   254  }
   255  
   256  func queryXactQueryRunningTime() string {
   257  	return `
   258  SELECT datname,
   259         state,
   260         EXTRACT(epoch from now() - xact_start)  as xact_running_time,
   261         EXTRACT(epoch from now() - query_start) as query_running_time
   262  FROM pg_stat_activity
   263  WHERE datname IS NOT NULL
   264    AND state IN
   265        (
   266         'active',
   267         'idle in transaction',
   268         'idle in transaction (aborted)'
   269            )
   270    AND backend_type = 'client backend';
   271  `
   272  }
   273  
   274  func queryReplicationStandbyAppDelta(version int) string {
   275  	if version < pgVersion10 {
   276  		return `
   277  SELECT application_name,
   278         pg_xlog_location_diff(
   279                 CASE pg_is_in_recovery()
   280                     WHEN true THEN pg_last_xlog_receive_location()
   281                     ELSE pg_current_xlog_location()
   282                     END,
   283                 sent_location)   AS sent_delta,
   284         pg_xlog_location_diff(
   285                 sent_location, write_location)  AS write_delta,
   286         pg_xlog_location_diff(
   287                 write_location, flush_location)  AS flush_delta,
   288         pg_xlog_location_diff(
   289                 flush_location, replay_location) AS replay_delta
   290  FROM pg_stat_replication psr
   291  WHERE application_name IS NOT NULL;
   292  `
   293  	}
   294  	return `
   295  SELECT application_name,
   296         pg_wal_lsn_diff(
   297                 CASE pg_is_in_recovery()
   298                     WHEN true THEN pg_last_wal_receive_lsn()
   299                     ELSE pg_current_wal_lsn()
   300                     END,
   301                 sent_lsn)   AS sent_delta,
   302         pg_wal_lsn_diff(
   303                 sent_lsn, write_lsn)  AS write_delta,
   304         pg_wal_lsn_diff(
   305                 write_lsn, flush_lsn)  AS flush_delta,
   306         pg_wal_lsn_diff(
   307                 flush_lsn, replay_lsn) AS replay_delta
   308  FROM pg_stat_replication
   309  WHERE application_name IS NOT NULL;
   310  `
   311  }
   312  
   313  func queryReplicationStandbyAppLag() string {
   314  	return `
   315  SELECT application_name,
   316         COALESCE(EXTRACT(EPOCH FROM write_lag)::bigint, 0)  AS write_lag,
   317         COALESCE(EXTRACT(EPOCH FROM flush_lag)::bigint, 0)  AS flush_lag,
   318         COALESCE(EXTRACT(EPOCH FROM replay_lag)::bigint, 0) AS replay_lag
   319  FROM pg_stat_replication psr
   320  WHERE application_name IS NOT NULL;
   321  `
   322  }
   323  
   324  func queryReplicationSlotFiles(version int) string {
   325  	if version < pgVersion11 {
   326  		return `
   327  WITH wal_size AS (
   328    SELECT
   329      current_setting('wal_block_size')::INT * setting::INT AS val
   330    FROM pg_settings
   331    WHERE name = 'wal_segment_size'
   332    )
   333  SELECT
   334      slot_name,
   335      slot_type,
   336      replslot_wal_keep,
   337      count(slot_file) AS replslot_files
   338  FROM
   339      (SELECT
   340          slot.slot_name,
   341          CASE
   342              WHEN slot_file <> 'state' THEN 1
   343          END AS slot_file ,
   344          slot_type,
   345          COALESCE (
   346            floor(
   347              CASE WHEN pg_is_in_recovery()
   348              THEN (
   349                pg_wal_lsn_diff(pg_last_wal_receive_lsn(), slot.restart_lsn)
   350                -- this is needed to account for whole WAL retention and
   351                -- not only size retention
   352                + (pg_wal_lsn_diff(restart_lsn, '0/0') % s.val)
   353              ) / s.val
   354              ELSE (
   355                pg_wal_lsn_diff(pg_current_wal_lsn(), slot.restart_lsn)
   356                -- this is needed to account for whole WAL retention and
   357                -- not only size retention
   358                + (pg_walfile_name_offset(restart_lsn)).file_offset
   359              ) / s.val
   360              END
   361            ),0) AS replslot_wal_keep
   362      FROM pg_replication_slots slot
   363      LEFT JOIN (
   364          SELECT
   365              slot2.slot_name,
   366              pg_ls_dir('pg_replslot/' || slot2.slot_name) AS slot_file
   367          FROM pg_replication_slots slot2
   368          ) files (slot_name, slot_file)
   369          ON slot.slot_name = files.slot_name
   370      CROSS JOIN wal_size s
   371      ) AS d
   372  GROUP BY
   373      slot_name,
   374      slot_type,
   375      replslot_wal_keep;
   376  `
   377  	}
   378  
   379  	return `
   380  WITH wal_size AS (
   381    SELECT
   382      setting::int AS val
   383    FROM pg_settings
   384    WHERE name = 'wal_segment_size'
   385    )
   386  SELECT
   387      slot_name,
   388      slot_type,
   389      replslot_wal_keep,
   390      count(slot_file) AS replslot_files
   391  FROM
   392      (SELECT
   393          slot.slot_name,
   394          CASE
   395              WHEN slot_file <> 'state' THEN 1
   396          END AS slot_file ,
   397          slot_type,
   398          COALESCE (
   399            floor(
   400              CASE WHEN pg_is_in_recovery()
   401              THEN (
   402                pg_wal_lsn_diff(pg_last_wal_receive_lsn(), slot.restart_lsn)
   403                -- this is needed to account for whole WAL retention and
   404                -- not only size retention
   405                + (pg_wal_lsn_diff(restart_lsn, '0/0') % s.val)
   406              ) / s.val
   407              ELSE (
   408                pg_wal_lsn_diff(pg_current_wal_lsn(), slot.restart_lsn)
   409                -- this is needed to account for whole WAL retention and
   410                -- not only size retention
   411                + (pg_walfile_name_offset(restart_lsn)).file_offset
   412              ) / s.val
   413              END
   414            ),0) AS replslot_wal_keep
   415      FROM pg_replication_slots slot
   416      LEFT JOIN (
   417          SELECT
   418              slot2.slot_name,
   419              pg_ls_dir('pg_replslot/' || slot2.slot_name) AS slot_file
   420          FROM pg_replication_slots slot2
   421          ) files (slot_name, slot_file)
   422          ON slot.slot_name = files.slot_name
   423      CROSS JOIN wal_size s
   424      ) AS d
   425  GROUP BY
   426      slot_name,
   427      slot_type,
   428      replslot_wal_keep;
   429  `
   430  }
   431  
   432  func queryQueryableDatabaseList() string {
   433  	return `
   434  SELECT datname
   435  FROM pg_database
   436  WHERE datallowconn = true
   437    AND datistemplate = false
   438    AND datname != current_database()
   439    AND has_database_privilege((SELECT CURRENT_USER), datname, 'connect');
   440  `
   441  }
   442  
   443  func queryDatabaseStats() string {
   444  	// definition by version: https://pgpedia.info/p/pg_stat_database.html
   445  	// docs: https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-DATABASE-VIEW
   446  	// code: https://github.com/postgres/postgres/blob/366283961ac0ed6d89014444c6090f3fd02fce0a/src/backend/catalog/system_views.sql#L1018
   447  
   448  	return `
   449  SELECT stat.datname,
   450         numbackends,
   451         pg_database.datconnlimit,
   452         xact_commit,
   453         xact_rollback,
   454         blks_read * current_setting('block_size')::numeric AS blks_read_bytes,
   455         blks_hit * current_setting('block_size')::numeric  AS blks_hit_bytes,
   456         tup_returned,
   457         tup_fetched,
   458         tup_inserted,
   459         tup_updated,
   460         tup_deleted,
   461         conflicts,
   462         temp_files,
   463         temp_bytes,
   464         deadlocks
   465  FROM pg_stat_database stat
   466           INNER JOIN
   467       pg_database
   468       ON pg_database.datname = stat.datname
   469  WHERE pg_database.datistemplate = false;
   470  `
   471  }
   472  
   473  func queryDatabaseSize(version int) string {
   474  	if version < pgVersion10 {
   475  		return `
   476  SELECT datname,
   477         pg_database_size(datname) AS size
   478  FROM pg_database
   479  WHERE pg_database.datistemplate = false
   480    AND has_database_privilege((SELECT CURRENT_USER), pg_database.datname, 'connect');
   481  `
   482  	}
   483  	return `
   484  SELECT datname,
   485         pg_database_size(datname) AS size
   486  FROM pg_database
   487  WHERE pg_database.datistemplate = false
   488    AND (has_database_privilege((SELECT CURRENT_USER), datname, 'connect')
   489         OR pg_has_role((SELECT CURRENT_USER), 'pg_read_all_stats', 'MEMBER'));
   490  `
   491  }
   492  
   493  func queryDatabaseConflicts() string {
   494  	// definition by version: https://pgpedia.info/p/pg_stat_database_conflicts.html
   495  	// docs: https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-DATABASE-CONFLICTS-VIEW
   496  	// code: https://github.com/postgres/postgres/blob/366283961ac0ed6d89014444c6090f3fd02fce0a/src/backend/catalog/system_views.sql#L1058
   497  
   498  	return `
   499  SELECT stat.datname,
   500         confl_tablespace,
   501         confl_lock,
   502         confl_snapshot,
   503         confl_bufferpin,
   504         confl_deadlock
   505  FROM pg_stat_database_conflicts stat
   506           INNER JOIN
   507       pg_database
   508       ON pg_database.datname = stat.datname
   509  WHERE pg_database.datistemplate = false;
   510  `
   511  }
   512  
   513  func queryDatabaseLocks() string {
   514  	// definition by version: https://pgpedia.info/p/pg_locks.html
   515  	// docs: https://www.postgresql.org/docs/current/view-pg-locks.html
   516  
   517  	return `
   518  SELECT pg_database.datname,
   519         mode,
   520         granted,
   521         count(mode) AS locks_count
   522  FROM pg_locks
   523           INNER JOIN
   524       pg_database
   525       ON pg_database.oid = pg_locks.database
   526  WHERE pg_database.datistemplate = false
   527  GROUP BY datname,
   528           mode,
   529           granted
   530  ORDER BY datname,
   531           mode;
   532  `
   533  }
   534  
   535  func queryUserTablesCount() string {
   536  	return "SELECT count(*) from  pg_stat_user_tables;"
   537  }
   538  
   539  func queryStatUserTables() string {
   540  	return `
   541  SELECT current_database()                                   as datname,
   542         schemaname,
   543         relname,
   544         inh.parent_relname,
   545         seq_scan,
   546         seq_tup_read,
   547         idx_scan,
   548         idx_tup_fetch,
   549         n_tup_ins,
   550         n_tup_upd,
   551         n_tup_del,
   552         n_tup_hot_upd,
   553         n_live_tup,
   554         n_dead_tup,
   555         EXTRACT(epoch from now() - last_vacuum)              as last_vacuum,
   556         EXTRACT(epoch from now() - last_autovacuum)          as last_autovacuum,
   557         EXTRACT(epoch from now() - last_analyze)             as last_analyze,
   558         EXTRACT(epoch from now() - last_autoanalyze)         as last_autoanalyze,
   559         vacuum_count,
   560         autovacuum_count,
   561         analyze_count,
   562         autoanalyze_count,
   563         pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(relname)) as total_relation_size
   564  FROM pg_stat_user_tables
   565  LEFT JOIN(
   566      SELECT 
   567        c.oid AS child_oid, 
   568        p.relname AS parent_relname 
   569      FROM 
   570        pg_inherits 
   571        JOIN pg_class AS c ON (inhrelid = c.oid) 
   572        JOIN pg_class AS p ON (inhparent = p.oid)
   573    ) AS inh ON inh.child_oid = relid 
   574  WHERE has_schema_privilege(schemaname, 'USAGE');
   575  `
   576  }
   577  
   578  func queryStatIOUserTables() string {
   579  	return `
   580  SELECT current_database()                                       AS datname,
   581         schemaname,
   582         relname,
   583         inh.parent_relname,
   584         heap_blks_read * current_setting('block_size')::numeric  AS heap_blks_read_bytes,
   585         heap_blks_hit * current_setting('block_size')::numeric   AS heap_blks_hit_bytes,
   586         idx_blks_read * current_setting('block_size')::numeric   AS idx_blks_read_bytes,
   587         idx_blks_hit * current_setting('block_size')::numeric    AS idx_blks_hit_bytes,
   588         toast_blks_read * current_setting('block_size')::numeric AS toast_blks_read_bytes,
   589         toast_blks_hit * current_setting('block_size')::numeric  AS toast_blks_hit_bytes,
   590         tidx_blks_read * current_setting('block_size')::numeric  AS tidx_blks_read_bytes,
   591         tidx_blks_hit * current_setting('block_size')::numeric   AS tidx_blks_hit_bytes
   592  FROM pg_statio_user_tables
   593  LEFT JOIN(
   594      SELECT 
   595        c.oid AS child_oid, 
   596        p.relname AS parent_relname 
   597      FROM 
   598        pg_inherits 
   599        JOIN pg_class AS c ON (inhrelid = c.oid) 
   600        JOIN pg_class AS p ON (inhparent = p.oid)
   601    ) AS inh ON inh.child_oid = relid
   602  WHERE has_schema_privilege(schemaname, 'USAGE');
   603  `
   604  }
   605  
   606  func queryUserIndexesCount() string {
   607  	return "SELECT count(*) from  pg_stat_user_indexes;"
   608  }
   609  
   610  func queryStatUserIndexes() string {
   611  	return `
   612  SELECT current_database()                                as datname,
   613         schemaname,
   614         relname,
   615         indexrelname,
   616         inh.parent_relname,
   617         idx_scan,
   618         idx_tup_read,
   619         idx_tup_fetch,
   620         pg_relation_size(quote_ident(schemaname) || '.' || quote_ident(indexrelname)::text) as size
   621  FROM pg_stat_user_indexes
   622  LEFT JOIN(
   623      SELECT 
   624        c.oid AS child_oid, 
   625        p.relname AS parent_relname 
   626      FROM 
   627        pg_inherits 
   628        JOIN pg_class AS c ON (inhrelid = c.oid) 
   629        JOIN pg_class AS p ON (inhparent = p.oid)
   630    ) AS inh ON inh.child_oid = relid
   631  WHERE has_schema_privilege(schemaname, 'USAGE');
   632  `
   633  }
   634  
   635  // The following query for bloat was taken from the venerable check_postgres
   636  // script (https://bucardo.org/check_postgres/), which is:
   637  //
   638  // Copyright (c) 2007-2017 Greg Sabino Mullane
   639  //------------------------------------------------------------------------------
   640  
   641  func queryBloat() string {
   642  	return `
   643  SELECT
   644    current_database() AS db, schemaname, tablename, reltuples::bigint AS tups, relpages::bigint AS pages, otta,
   645    ROUND(CASE WHEN otta=0 OR sml.relpages=0 OR sml.relpages=otta THEN 0.0 ELSE sml.relpages/otta::numeric END,1) AS tbloat,
   646    CASE WHEN relpages < otta THEN 0 ELSE relpages::bigint - otta END AS wastedpages,
   647    CASE WHEN relpages < otta THEN 0 ELSE bs*(sml.relpages-otta)::bigint END AS wastedbytes,
   648    CASE WHEN relpages < otta THEN '0 bytes'::text ELSE (bs*(relpages-otta))::bigint::text || ' bytes' END AS wastedsize,
   649    iname, ituples::bigint AS itups, ipages::bigint AS ipages, iotta,
   650    ROUND(CASE WHEN iotta=0 OR ipages=0 OR ipages=iotta THEN 0.0 ELSE ipages/iotta::numeric END,1) AS ibloat,
   651    CASE WHEN ipages < iotta THEN 0 ELSE ipages::bigint - iotta END AS wastedipages,
   652    CASE WHEN ipages < iotta THEN 0 ELSE bs*(ipages-iotta) END AS wastedibytes,
   653    CASE WHEN ipages < iotta THEN '0 bytes' ELSE (bs*(ipages-iotta))::bigint::text || ' bytes' END AS wastedisize,
   654    CASE WHEN relpages < otta THEN
   655      CASE WHEN ipages < iotta THEN 0 ELSE bs*(ipages-iotta::bigint) END
   656      ELSE CASE WHEN ipages < iotta THEN bs*(relpages-otta::bigint)
   657        ELSE bs*(relpages-otta::bigint + ipages-iotta::bigint) END
   658    END AS totalwastedbytes
   659  FROM (
   660    SELECT
   661      nn.nspname AS schemaname,
   662      cc.relname AS tablename,
   663      COALESCE(cc.reltuples,0) AS reltuples,
   664      COALESCE(cc.relpages,0) AS relpages,
   665      COALESCE(bs,0) AS bs,
   666      COALESCE(CEIL((cc.reltuples*((datahdr+ma-
   667        (CASE WHEN datahdr%ma=0 THEN ma ELSE datahdr%ma END))+nullhdr2+4))/(bs-20::float)),0) AS otta,
   668      COALESCE(c2.relname,'?') AS iname, COALESCE(c2.reltuples,0) AS ituples, COALESCE(c2.relpages,0) AS ipages,
   669      COALESCE(CEIL((c2.reltuples*(datahdr-12))/(bs-20::float)),0) AS iotta -- very rough approximation, assumes all cols
   670    FROM
   671       pg_class cc
   672    JOIN pg_namespace nn ON cc.relnamespace = nn.oid AND nn.nspname <> 'information_schema'
   673    LEFT JOIN
   674    (
   675      SELECT
   676        ma,bs,foo.nspname,foo.relname,
   677        (datawidth+(hdr+ma-(case when hdr%ma=0 THEN ma ELSE hdr%ma END)))::numeric AS datahdr,
   678        (maxfracsum*(nullhdr+ma-(case when nullhdr%ma=0 THEN ma ELSE nullhdr%ma END))) AS nullhdr2
   679      FROM (
   680        SELECT
   681          ns.nspname, tbl.relname, hdr, ma, bs,
   682          SUM((1-coalesce(null_frac,0))*coalesce(avg_width, 2048)) AS datawidth,
   683          MAX(coalesce(null_frac,0)) AS maxfracsum,
   684          hdr+(
   685            SELECT 1+count(*)/8
   686            FROM pg_stats s2
   687            WHERE null_frac<>0 AND s2.schemaname = ns.nspname AND s2.tablename = tbl.relname
   688          ) AS nullhdr
   689        FROM pg_attribute att
   690        JOIN pg_class tbl ON att.attrelid = tbl.oid
   691        JOIN pg_namespace ns ON ns.oid = tbl.relnamespace
   692        LEFT JOIN pg_stats s ON s.schemaname=ns.nspname
   693        AND s.tablename = tbl.relname
   694        AND s.inherited=false
   695        AND s.attname=att.attname,
   696        (
   697          SELECT
   698            (SELECT current_setting('block_size')::numeric) AS bs,
   699              CASE WHEN SUBSTRING(SPLIT_PART(v, ' ', 2) FROM '#"[0-9]+.[0-9]+#"%' for '#')
   700                IN ('8.0','8.1','8.2') THEN 27 ELSE 23 END AS hdr,
   701            CASE WHEN v ~ 'mingw32' OR v ~ '64-bit' THEN 8 ELSE 4 END AS ma
   702          FROM (SELECT version() AS v) AS foo
   703        ) AS constants
   704        WHERE att.attnum > 0 AND tbl.relkind='r'
   705        GROUP BY 1,2,3,4,5
   706      ) AS foo
   707    ) AS rs
   708    ON cc.relname = rs.relname AND nn.nspname = rs.nspname
   709    LEFT JOIN pg_index i ON indrelid = cc.oid
   710    LEFT JOIN pg_class c2 ON c2.oid = i.indexrelid
   711  ) AS sml
   712  WHERE sml.relpages - otta > 10 OR ipages - iotta > 10;
   713  `
   714  }
   715  
   716  func queryColumnsStats() string {
   717  	return `
   718  SELECT current_database()        AS datname,
   719         nspname                   AS schemaname,
   720         relname,
   721         st.attname,
   722         typname,
   723         (st.null_frac * 100)::int AS null_percent,
   724         case
   725             when st.n_distinct >= 0
   726                 then st.n_distinct
   727             else
   728                 abs(st.n_distinct) * reltuples
   729             end                   AS "distinct"
   730  FROM pg_class c
   731           JOIN
   732       pg_namespace ns
   733       ON
   734           (ns.oid = relnamespace)
   735           JOIN
   736       pg_attribute at
   737       ON
   738           (c.oid = attrelid)
   739           JOIN
   740       pg_type t
   741       ON
   742           (t.oid = atttypid)
   743           JOIN
   744       pg_stats st
   745       ON
   746           (st.tablename = relname AND st.attname = at.attname)
   747  WHERE relkind = 'r'
   748    AND nspname NOT LIKE E'pg\\_%'
   749    AND nspname != 'information_schema'
   750    AND NOT attisdropped
   751    AND attstattarget != 0
   752    AND reltuples >= 100
   753  ORDER BY nspname,
   754           relname,
   755           st.attname;
   756  `
   757  }