From 1c9c3a3e4328ed6131aa7efd4785225402bea3ec Mon Sep 17 00:00:00 2001 From: pawurb Date: Tue, 8 Oct 2024 21:50:14 +0200 Subject: [PATCH] Support PG 17 --- .github/workflows/ci.yml | 78 +++++++++++++- Cargo.toml | 2 + src/lib.rs | 156 +++++++++++++++++++--------- src/main.rs | 2 +- src/queries/all_locks.rs | 3 +- src/queries/bloat.rs | 4 +- src/queries/blocking.rs | 3 +- src/queries/buffercache_stats.rs | 7 +- src/queries/buffercache_usage.rs | 7 +- src/queries/cache_hit.rs | 4 +- src/queries/calls.rs | 13 ++- src/queries/connections.rs | 7 +- src/queries/db_settings.rs | 7 +- src/queries/duplicate_indexes.rs | 7 +- src/queries/extensions.rs | 7 +- src/queries/index_cache_hit.rs | 7 +- src/queries/index_scans.rs | 7 +- src/queries/index_size.rs | 7 +- src/queries/index_usage.rs | 7 +- src/queries/indexes.rs | 7 +- src/queries/locks.rs | 3 +- src/queries/long_running_queries.rs | 3 +- src/queries/mandelbrot.rs | 7 +- src/queries/null_indexes.rs | 7 +- src/queries/outliers.rs | 13 ++- src/queries/records_rank.rs | 7 +- src/queries/seq_scans.rs | 7 +- src/queries/shared.rs | 6 +- src/queries/ssl_used.rs | 7 +- src/queries/table_cache_hit.rs | 7 +- src/queries/table_index_scans.rs | 7 +- src/queries/table_indexes_size.rs | 7 +- src/queries/table_size.rs | 7 +- src/queries/tables.rs | 7 +- src/queries/total_index_size.rs | 7 +- src/queries/total_table_size.rs | 7 +- src/queries/unused_indexes.rs | 7 +- src/queries/vacuum_stats.rs | 7 +- src/sql/calls_17.sql | 9 ++ src/sql/outliers_17.sql | 10 ++ src/sql/outliers_legacy.sql | 2 +- 41 files changed, 317 insertions(+), 169 deletions(-) create mode 100644 src/sql/calls_17.sql create mode 100644 src/sql/outliers_17.sql diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2aeb61..8e2ff06 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,14 +10,52 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - name: Run PostgreSQL 12 + run: | + docker run --env POSTGRES_USER=postgres \ + --env POSTGRES_DB=rust-pg-extras-test \ + --env POSTGRES_PASSWORD=secret \ + -d -p 5432:5432 postgres:12.20-alpine \ + postgres -c shared_preload_libraries=pg_stat_statements + - name: Run PostgreSQL 13 + run: | + docker run --env POSTGRES_USER=postgres \ + --env POSTGRES_DB=rust-pg-extras-test \ + --env POSTGRES_PASSWORD=secret \ + -d -p 5433:5432 postgres:13.16-alpine \ + postgres -c shared_preload_libraries=pg_stat_statements - name: Run PostgreSQL 14 run: | docker run --env POSTGRES_USER=postgres \ - --env POSTGRES_DB=rust_pg_extras \ + --env POSTGRES_DB=rust-pg-extras-test \ + --env POSTGRES_PASSWORD=secret \ + -d -p 5434:5432 postgres:14.13-alpine \ + postgres -c shared_preload_libraries=pg_stat_statements + - name: Run PostgreSQL 15 + run: | + docker run --env POSTGRES_USER=postgres \ + --env POSTGRES_DB=rust-pg-extras-test \ --env POSTGRES_PASSWORD=secret \ - -d -p 5432:5432 postgres:14.6-alpine \ + -d -p 5435:5432 postgres:15.8-alpine \ postgres -c shared_preload_libraries=pg_stat_statements + sleep 15 + - name: Run PostgreSQL 16 + run: | + docker run --env POSTGRES_USER=postgres \ + --env POSTGRES_DB=rust-pg-extras-test \ + --env POSTGRES_PASSWORD=secret \ + -d -p 5436:5432 postgres:16.4-alpine \ + postgres -c shared_preload_libraries=pg_stat_statements + sleep 15 + - name: Run PostgreSQL 17 + run: | + docker run --env POSTGRES_USER=postgres \ + --env POSTGRES_DB=rust-pg-extras-test \ + --env POSTGRES_PASSWORD=secret \ + -d -p 5437:5432 postgres:17.0-alpine \ + postgres -c shared_preload_libraries=pg_stat_statements + sleep 15 - name: Setup Rust uses: actions-rs/toolchain@v1 with: @@ -29,9 +67,39 @@ jobs: command: check - name: Lint run: cargo clippy --all --all-features -- -D warnings - - name: Test + - name: Run tests for PG 12 + env: + PG_VERSION: 12 + uses: actions-rs/cargo@v1 + with: + command: test + - name: Run tests for PG 13 + env: + PG_VERSION: 13 + uses: actions-rs/cargo@v1 + with: + command: test + - name: Run tests for PG 14 + env: + PG_VERSION: 14 + uses: actions-rs/cargo@v1 + with: + command: test + - name: Run tests for PG 15 + env: + PG_VERSION: 15 + uses: actions-rs/cargo@v1 + with: + command: test + - name: Run tests for PG 16 + env: + PG_VERSION: 16 + uses: actions-rs/cargo@v1 + with: + command: test + - name: Run tests for PG 17 env: - DATABASE_URL: postgresql://postgres:secret@localhost:5432/rust_pg_extras + PG_VERSION: 17 uses: actions-rs/cargo@v1 with: command: test \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 9b90b70..4474807 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,9 @@ version = "0.4.0" exclude = ["docker-compose.yml.sample", "live_tests.sh"] [dependencies] +lazy_static = "1.5.0" prettytable-rs = "0.10.0" +semver = "1.0.23" sqlx = { version = "0.8", features = [ "runtime-tokio-rustls", "postgres", diff --git a/src/lib.rs b/src/lib.rs index eeb6e9a..934291d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,46 +1,51 @@ -use std::collections::HashMap; -use std::time::Duration; -use std::{env, fmt}; +use std::{ + collections::HashMap, + time::Duration, + {env, fmt}, +}; pub mod queries; -pub use queries::all_locks::AllLocks; -pub use queries::bloat::Bloat; -pub use queries::blocking::Blocking; -pub use queries::buffercache_stats::BuffercacheStats; -pub use queries::buffercache_usage::BuffercacheUsage; -pub use queries::cache_hit::CacheHit; -pub use queries::calls::Calls; -pub use queries::connections::Connections; -pub use queries::db_settings::DbSettings; -pub use queries::duplicate_indexes::DuplicateIndexes; -pub use queries::extensions::Extensions; -pub use queries::index_cache_hit::IndexCacheHit; -pub use queries::index_scans::IndexScans; -pub use queries::index_size::IndexSize; -pub use queries::index_usage::IndexUsage; -pub use queries::indexes::Indexes; -pub use queries::locks::Locks; -pub use queries::long_running_queries::LongRunningQueries; -pub use queries::mandelbrot::Mandelbrot; -pub use queries::null_indexes::NullIndexes; -pub use queries::outliers::Outliers; -pub use queries::records_rank::RecordsRank; -pub use queries::seq_scans::SeqScans; -pub use queries::shared::{get_default_schema, Query}; -pub use queries::ssl_used::SslUsed; -pub use queries::table_cache_hit::TableCacheHit; -pub use queries::table_index_scans::TableIndexScans; -pub use queries::table_indexes_size::TableIndexesSize; -pub use queries::table_size::TableSize; -pub use queries::tables::Tables; -pub use queries::total_index_size::TotalIndexSize; -pub use queries::total_table_size::TotalTableSize; -pub use queries::unused_indexes::UnusedIndexes; -pub use queries::vacuum_stats::VacuumStats; -use sqlx::postgres::PgPoolOptions; +pub use queries::{ + all_locks::AllLocks, + bloat::Bloat, + blocking::Blocking, + buffercache_stats::BuffercacheStats, + buffercache_usage::BuffercacheUsage, + cache_hit::CacheHit, + calls::Calls, + connections::Connections, + db_settings::DbSettings, + duplicate_indexes::DuplicateIndexes, + extensions::Extensions, + index_cache_hit::IndexCacheHit, + index_scans::IndexScans, + index_size::IndexSize, + index_usage::IndexUsage, + indexes::Indexes, + locks::Locks, + long_running_queries::LongRunningQueries, + mandelbrot::Mandelbrot, + null_indexes::NullIndexes, + outliers::Outliers, + records_rank::RecordsRank, + seq_scans::SeqScans, + shared::{get_default_schema, Query}, + ssl_used::SslUsed, + table_cache_hit::TableCacheHit, + table_index_scans::TableIndexScans, + table_indexes_size::TableIndexesSize, + table_size::TableSize, + tables::Tables, + total_index_size::TotalIndexSize, + total_table_size::TotalTableSize, + unused_indexes::UnusedIndexes, + vacuum_stats::VacuumStats, +}; +use semver::Version; +use sqlx::{postgres::PgPoolOptions, Row}; #[macro_use] extern crate prettytable; -use prettytable::{Cell, Row, Table}; +use prettytable::{Cell, Row as TableRow, Table}; pub fn render_table(items: Vec) { let mut table = Table::new(); @@ -51,7 +56,7 @@ pub fn render_table(items: Vec) { for item in items { table.add_row(item.to_row()); } - table.set_titles(Row::new(vec![ + table.set_titles(TableRow::new(vec![ Cell::new(T::description().as_str()).style_spec(format!("H{}", columns_count).as_str()) ])); table.printstd(); @@ -228,17 +233,23 @@ impl fmt::Display for PgExtrasError { impl std::error::Error for PgExtrasError {} +use lazy_static::lazy_static; + +lazy_static! { + pub static ref NEW_PG_STAT_STATEMENTS: Version = Version::parse("1.8.0").unwrap(); + pub static ref PG_STAT_STATEMENTS_17: Version = semver::Version::parse("1.11.0").unwrap(); +} + +#[derive(Debug)] +pub enum PgStatsVersion { + Legacy, + Standard, + Pg17, +} + async fn get_rows( params: Option>, ) -> Result, PgExtrasError> { - let mut query = T::read_file(); - - if let Some(params) = params { - for (key, value) in ¶ms { - query = query.replace(&format!("%{{{}}}", key), value.as_str()); - } - } - let pool = match PgPoolOptions::new() .max_connections(5) .acquire_timeout(Duration::from_secs(10)) @@ -249,6 +260,37 @@ async fn get_rows( Err(e) => return Err(PgExtrasError::DbConnectionError(format!("{}", e))), }; + let pg_statements_query = + "select installed_version from pg_available_extensions where name='pg_stat_statements'"; + + let pg_statements_version = match sqlx::query(pg_statements_query).fetch_one(&pool).await { + Ok(row) => row + .try_get::("installed_version") + .unwrap_or_default(), + Err(_) => "".to_string(), + }; + + let default_version = NEW_PG_STAT_STATEMENTS.clone(); + let pg_statements_version = format!("{}.0", pg_statements_version); + let pg_statements_version = + Version::parse(&pg_statements_version).unwrap_or(default_version.clone()); + + let pg_statements_version = if pg_statements_version < default_version { + PgStatsVersion::Legacy + } else if pg_statements_version >= *PG_STAT_STATEMENTS_17 { + PgStatsVersion::Pg17 + } else { + PgStatsVersion::Standard + }; + + let mut query = T::read_file(Some(pg_statements_version)); + + if let Some(params) = params { + for (key, value) in ¶ms { + query = query.replace(&format!("%{{{}}}", key), value.as_str()); + } + } + Ok(match sqlx::query(&query).fetch_all(&pool).await { Ok(rows) => rows.iter().map(T::new).collect(), Err(e) => return Err(PgExtrasError::Unknown(format!("{}", e))), @@ -282,6 +324,24 @@ mod tests { use super::*; async fn setup() -> Result<(), Box> { + let port = match env::var("PG_VERSION").expect("PG_VERSION not set").as_str() { + "12" => "5432", + "13" => "5433", + "14" => "5434", + "15" => "5435", + "16" => "5436", + "17" => "5437", + _ => "5432", + }; + + env::set_var( + "PG_EXTRAS_DATABASE_URL", + format!( + "postgres://postgres:secret@localhost:{}/rust-pg-extras-test", + port + ), + ); + let pool = PgPoolOptions::new() .max_connections(5) .connect(db_url()?.as_str()) diff --git a/src/main.rs b/src/main.rs index 2eb2e9c..7cd0943 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,7 +17,7 @@ async fn main() { match execute(command).await { Ok(_) => {} Err(error) => { - println!("{}", error); + eprintln!("{}", error); } } } diff --git a/src/queries/all_locks.rs b/src/queries/all_locks.rs index aab4995..a33df49 100644 --- a/src/queries/all_locks.rs +++ b/src/queries/all_locks.rs @@ -1,4 +1,5 @@ use crate::queries::shared::{get_default_interval, Query}; +use crate::PgStatsVersion; use sqlx::postgres::{types::PgInterval, PgRow}; use sqlx::Row; @@ -54,7 +55,7 @@ impl Query for AllLocks { ] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/all_locks.sql").to_string() } } diff --git a/src/queries/bloat.rs b/src/queries/bloat.rs index 4316467..58ead9c 100644 --- a/src/queries/bloat.rs +++ b/src/queries/bloat.rs @@ -1,4 +1,4 @@ -use crate::queries::shared::Query; +use crate::{queries::shared::Query, PgStatsVersion}; use sqlx::postgres::PgRow; use sqlx::types::BigDecimal; use sqlx::Row; @@ -37,7 +37,7 @@ impl Query for Bloat { row!["type", "schemaname", "object_name", "bloat", "waste"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/bloat.sql").to_string() } } diff --git a/src/queries/blocking.rs b/src/queries/blocking.rs index e7a2445..e71167e 100644 --- a/src/queries/blocking.rs +++ b/src/queries/blocking.rs @@ -1,4 +1,5 @@ use crate::queries::shared::{get_default_interval, Query}; +use crate::PgStatsVersion; use sqlx::postgres::{types::PgInterval, PgRow}; use sqlx::Row; @@ -58,7 +59,7 @@ impl Query for Blocking { ] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/blocking.sql").to_string() } } diff --git a/src/queries/buffercache_stats.rs b/src/queries/buffercache_stats.rs index ff77c53..ac68423 100644 --- a/src/queries/buffercache_stats.rs +++ b/src/queries/buffercache_stats.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct BuffercacheStats { @@ -38,7 +37,7 @@ impl Query for BuffercacheStats { ] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/buffercache_stats.sql") .to_string() .to_string() diff --git a/src/queries/buffercache_usage.rs b/src/queries/buffercache_usage.rs index 22e1434..7ca820c 100644 --- a/src/queries/buffercache_usage.rs +++ b/src/queries/buffercache_usage.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct BuffercacheUsage { @@ -24,7 +23,7 @@ impl Query for BuffercacheUsage { row!["relname", "buffers"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/buffercache_usage.sql").to_string() } } diff --git a/src/queries/cache_hit.rs b/src/queries/cache_hit.rs index 3c2bcf1..9aee6c4 100644 --- a/src/queries/cache_hit.rs +++ b/src/queries/cache_hit.rs @@ -1,4 +1,4 @@ -use crate::queries::shared::Query; +use crate::{queries::shared::Query, PgStatsVersion}; use sqlx::postgres::PgRow; use sqlx::types::BigDecimal; use sqlx::Row; @@ -25,7 +25,7 @@ impl Query for CacheHit { row!["name", "ratio"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/cache_hit.sql").to_string() } } diff --git a/src/queries/calls.rs b/src/queries/calls.rs index 78a6753..af63516 100644 --- a/src/queries/calls.rs +++ b/src/queries/calls.rs @@ -1,4 +1,5 @@ use crate::queries::shared::{get_default_interval, Query}; +use crate::PgStatsVersion; use sqlx::postgres::{types::PgInterval, PgRow}; use sqlx::Row; @@ -44,7 +45,15 @@ impl Query for Calls { ] } - fn read_file() -> String { - include_str!("../sql/calls.sql").to_string() + fn read_file(pg_statement_version: Option) -> String { + let default = include_str!("../sql/calls.sql"); + + match pg_statement_version { + Some(PgStatsVersion::Legacy) => include_str!("../sql/calls_legacy.sql"), + Some(PgStatsVersion::Standard) => default, + Some(PgStatsVersion::Pg17) => include_str!("../sql/calls_17.sql"), + None => default, + } + .to_string() } } diff --git a/src/queries/connections.rs b/src/queries/connections.rs index f9dda8b..1526e95 100644 --- a/src/queries/connections.rs +++ b/src/queries/connections.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct Connections { @@ -26,7 +25,7 @@ impl Query for Connections { row!["username", "pid", "client_addr"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/connections.sql").to_string() } } diff --git a/src/queries/db_settings.rs b/src/queries/db_settings.rs index f03d97b..30f6e82 100644 --- a/src/queries/db_settings.rs +++ b/src/queries/db_settings.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct DbSettings { @@ -28,7 +27,7 @@ impl Query for DbSettings { row!["name", "setting", "unit", "short_desc"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/db_settings.sql").to_string() } } diff --git a/src/queries/duplicate_indexes.rs b/src/queries/duplicate_indexes.rs index 743bbd4..24ee4bd 100644 --- a/src/queries/duplicate_indexes.rs +++ b/src/queries/duplicate_indexes.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct DuplicateIndexes { @@ -30,7 +29,7 @@ impl Query for DuplicateIndexes { row!["size", "idx1", "idx2", "idx3", "idx4"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/duplicate_indexes.sql").to_string() } } diff --git a/src/queries/extensions.rs b/src/queries/extensions.rs index 1963aa4..1e15058 100644 --- a/src/queries/extensions.rs +++ b/src/queries/extensions.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct Extensions { @@ -33,7 +32,7 @@ impl Query for Extensions { row!["name", "default_version", "installed_version", "comment"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/extensions.sql").to_string() } } diff --git a/src/queries/index_cache_hit.rs b/src/queries/index_cache_hit.rs index 2b88fbb..30c40ba 100644 --- a/src/queries/index_cache_hit.rs +++ b/src/queries/index_cache_hit.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct IndexCacheHit { @@ -36,7 +35,7 @@ impl Query for IndexCacheHit { row!["name", "buffer_hits", "block_reads", "total_read", "ratio"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/index_cache_hit.sql").to_string() } } diff --git a/src/queries/index_scans.rs b/src/queries/index_scans.rs index 92b8781..b7555b9 100644 --- a/src/queries/index_scans.rs +++ b/src/queries/index_scans.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct IndexScans { @@ -36,7 +35,7 @@ impl Query for IndexScans { row!["schemaname", "table", "index", "index_size", "index_scans"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/index_scans.sql").to_string() } } diff --git a/src/queries/index_size.rs b/src/queries/index_size.rs index 995e698..f983409 100644 --- a/src/queries/index_size.rs +++ b/src/queries/index_size.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct IndexSize { @@ -26,7 +25,7 @@ impl Query for IndexSize { row!["name", "size", "schema"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/index_size.sql").to_string() } } diff --git a/src/queries/index_usage.rs b/src/queries/index_usage.rs index f7ab0e2..d261069 100644 --- a/src/queries/index_usage.rs +++ b/src/queries/index_usage.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct IndexUsage { @@ -32,7 +31,7 @@ impl Query for IndexUsage { row!["relname", "percent_of_times_index_used", "rows_in_table"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/index_usage.sql").to_string() } } diff --git a/src/queries/indexes.rs b/src/queries/indexes.rs index cff8ac2..ffb8038 100644 --- a/src/queries/indexes.rs +++ b/src/queries/indexes.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct Indexes { @@ -33,7 +32,7 @@ impl Query for Indexes { row!["schemaname", "indexname", "tablename", "columns"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/indexes.sql").to_string() } } diff --git a/src/queries/locks.rs b/src/queries/locks.rs index 013641f..6bfe2eb 100644 --- a/src/queries/locks.rs +++ b/src/queries/locks.rs @@ -1,4 +1,5 @@ use crate::queries::shared::{get_default_interval, Query}; +use crate::PgStatsVersion; use sqlx::postgres::{types::PgInterval, PgRow}; use sqlx::Row; @@ -54,7 +55,7 @@ impl Query for Locks { ] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/locks.sql").to_string() } } diff --git a/src/queries/long_running_queries.rs b/src/queries/long_running_queries.rs index daab8af..713ab04 100644 --- a/src/queries/long_running_queries.rs +++ b/src/queries/long_running_queries.rs @@ -1,4 +1,5 @@ use crate::queries::shared::{get_default_interval, Query}; +use crate::PgStatsVersion; use sqlx::postgres::{types::PgInterval, PgRow}; use sqlx::Row; @@ -26,7 +27,7 @@ impl Query for LongRunningQueries { row!["pid", "duration", "query"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/long_running_queries.sql").to_string() } } diff --git a/src/queries/mandelbrot.rs b/src/queries/mandelbrot.rs index d9a795d..2c620c0 100644 --- a/src/queries/mandelbrot.rs +++ b/src/queries/mandelbrot.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct Mandelbrot { @@ -22,7 +21,7 @@ impl Query for Mandelbrot { row!["array_to_string"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/mandelbrot.sql").to_string() } } diff --git a/src/queries/null_indexes.rs b/src/queries/null_indexes.rs index 1a84c97..79bf90c 100644 --- a/src/queries/null_indexes.rs +++ b/src/queries/null_indexes.rs @@ -1,7 +1,6 @@ -use crate::queries::shared::Query; +use crate::{queries::shared::Query, PgStatsVersion}; use sqlx::postgres::types::Oid; -use sqlx::postgres::PgRow; -use sqlx::Row; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct NullIndexes { @@ -59,7 +58,7 @@ impl Query for NullIndexes { ] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/null_indexes.sql").to_string() } } diff --git a/src/queries/outliers.rs b/src/queries/outliers.rs index 872f0c4..a520ad5 100644 --- a/src/queries/outliers.rs +++ b/src/queries/outliers.rs @@ -1,4 +1,5 @@ use crate::queries::shared::{get_default_interval, Query}; +use crate::PgStatsVersion; use sqlx::postgres::{types::PgInterval, PgRow}; use sqlx::Row; @@ -46,7 +47,15 @@ impl Query for Outliers { ] } - fn read_file() -> String { - include_str!("../sql/outliers.sql").to_string() + fn read_file(pg_statement_version: Option) -> String { + let default = include_str!("../sql/outliers.sql"); + + match pg_statement_version { + Some(PgStatsVersion::Legacy) => include_str!("../sql/outliers_legacy.sql"), + Some(PgStatsVersion::Standard) => default, + Some(PgStatsVersion::Pg17) => include_str!("../sql/outliers_17.sql"), + None => default, + } + .to_string() } } diff --git a/src/queries/records_rank.rs b/src/queries/records_rank.rs index c5c4009..03c8848 100644 --- a/src/queries/records_rank.rs +++ b/src/queries/records_rank.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct RecordsRank { @@ -24,7 +23,7 @@ impl Query for RecordsRank { row!["name", "estimated_count"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/records_rank.sql").to_string() } } diff --git a/src/queries/seq_scans.rs b/src/queries/seq_scans.rs index 4db4d88..8134393 100644 --- a/src/queries/seq_scans.rs +++ b/src/queries/seq_scans.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct SeqScans { @@ -24,7 +23,7 @@ impl Query for SeqScans { row!["name", "count"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/seq_scans.sql").to_string() } } diff --git a/src/queries/shared.rs b/src/queries/shared.rs index 54b54fd..c7e9a29 100644 --- a/src/queries/shared.rs +++ b/src/queries/shared.rs @@ -1,13 +1,15 @@ use sqlx::postgres::{types::PgInterval, PgRow}; use std::env; +use crate::PgStatsVersion; + pub trait Query { fn new(row: &PgRow) -> Self; fn to_row(&self) -> prettytable::Row; fn headers() -> prettytable::Row; - fn read_file() -> String; + fn read_file(pg_statement_version: Option) -> String; fn description() -> String { - Self::read_file().lines().take(1).collect() + Self::read_file(None).lines().take(1).collect() } } diff --git a/src/queries/ssl_used.rs b/src/queries/ssl_used.rs index f1e86a1..5317418 100644 --- a/src/queries/ssl_used.rs +++ b/src/queries/ssl_used.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct SslUsed { @@ -22,7 +21,7 @@ impl Query for SslUsed { row!["ssl_used"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/ssl_used.sql").to_string() } } diff --git a/src/queries/table_cache_hit.rs b/src/queries/table_cache_hit.rs index 5f48dc9..f3709ac 100644 --- a/src/queries/table_cache_hit.rs +++ b/src/queries/table_cache_hit.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct TableCacheHit { @@ -36,7 +35,7 @@ impl Query for TableCacheHit { row!["name", "buffer_hits", "block_reads", "total_read", "ratio"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/table_cache_hit.sql").to_string() } } diff --git a/src/queries/table_index_scans.rs b/src/queries/table_index_scans.rs index dd16ec5..c97f39a 100644 --- a/src/queries/table_index_scans.rs +++ b/src/queries/table_index_scans.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct TableIndexScans { @@ -24,7 +23,7 @@ impl Query for TableIndexScans { row!["name", "count"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/table_index_scans.sql").to_string() } } diff --git a/src/queries/table_indexes_size.rs b/src/queries/table_indexes_size.rs index ad224fe..9e08cd1 100644 --- a/src/queries/table_indexes_size.rs +++ b/src/queries/table_indexes_size.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct TableIndexesSize { @@ -24,7 +23,7 @@ impl Query for TableIndexesSize { row!["table", "index_size"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/table_indexes_size.sql").to_string() } } diff --git a/src/queries/table_size.rs b/src/queries/table_size.rs index 1ac5e87..55af67f 100644 --- a/src/queries/table_size.rs +++ b/src/queries/table_size.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct TableSize { @@ -26,7 +25,7 @@ impl Query for TableSize { row!["name", "size", "schema"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/table_size.sql").to_string() } } diff --git a/src/queries/tables.rs b/src/queries/tables.rs index 4b04574..91e451d 100644 --- a/src/queries/tables.rs +++ b/src/queries/tables.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct Tables { @@ -24,7 +23,7 @@ impl Query for Tables { row!["tablename", "schemaname"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/tables.sql").to_string() } } diff --git a/src/queries/total_index_size.rs b/src/queries/total_index_size.rs index bf44136..9639967 100644 --- a/src/queries/total_index_size.rs +++ b/src/queries/total_index_size.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct TotalIndexSize { @@ -22,7 +21,7 @@ impl Query for TotalIndexSize { row!["size"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/total_index_size.sql").to_string() } } diff --git a/src/queries/total_table_size.rs b/src/queries/total_table_size.rs index 61ff6c9..ccf1ae7 100644 --- a/src/queries/total_table_size.rs +++ b/src/queries/total_table_size.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct TotalTableSize { @@ -24,7 +23,7 @@ impl Query for TotalTableSize { row!["name", "size"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/total_table_size.sql").to_string() } } diff --git a/src/queries/unused_indexes.rs b/src/queries/unused_indexes.rs index 79b2bde..b12a9cd 100644 --- a/src/queries/unused_indexes.rs +++ b/src/queries/unused_indexes.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct UnusedIndexes { @@ -28,7 +27,7 @@ impl Query for UnusedIndexes { row!["table", "index", "index_size", "index_scans"] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/unused_indexes.sql").to_string() } } diff --git a/src/queries/vacuum_stats.rs b/src/queries/vacuum_stats.rs index e096be9..b1da5e2 100644 --- a/src/queries/vacuum_stats.rs +++ b/src/queries/vacuum_stats.rs @@ -1,6 +1,5 @@ -use crate::queries::shared::Query; -use sqlx::postgres::PgRow; -use sqlx::Row; +use crate::{queries::shared::Query, PgStatsVersion}; +use sqlx::{postgres::PgRow, Row}; #[derive(Debug, Clone)] pub struct VacuumStats { @@ -54,7 +53,7 @@ impl Query for VacuumStats { ] } - fn read_file() -> String { + fn read_file(_pg_statement_version: Option) -> String { include_str!("../sql/vacuum_stats.sql").to_string() } } diff --git a/src/sql/calls_17.sql b/src/sql/calls_17.sql new file mode 100644 index 0000000..e18cd0f --- /dev/null +++ b/src/sql/calls_17.sql @@ -0,0 +1,9 @@ +/* Queries that have highest frequency of execution */ + +SELECT query AS qry, +interval '1 millisecond' * total_exec_time AS exec_time, +to_char((total_exec_time/sum(total_exec_time) OVER()) * 100, 'FM90D0') || '%%' AS prop_exec_time, +to_char(calls, 'FM999G999G990') AS ncalls, +interval '1 millisecond' * (shared_blk_read_time + shared_blk_write_time) AS sync_io_time +FROM pg_stat_statements WHERE userid = (SELECT usesysid FROM pg_user WHERE usename = current_user LIMIT 1) +ORDER BY calls DESC LIMIT %{limit}; diff --git a/src/sql/outliers_17.sql b/src/sql/outliers_17.sql new file mode 100644 index 0000000..97113fa --- /dev/null +++ b/src/sql/outliers_17.sql @@ -0,0 +1,10 @@ +/* Queries that have longest execution time in aggregate */ + +SELECT interval '1 millisecond' * total_exec_time AS total_exec_time, +to_char((total_exec_time/sum(total_exec_time) OVER()) * 100, 'FM90D0') || '%%' AS prop_exec_time, +to_char(calls, 'FM999G999G999G990') AS ncalls, +interval '1 millisecond' * (shared_blk_read_time + shared_blk_write_time) AS sync_io_time, +query AS query +FROM pg_stat_statements WHERE userid = (SELECT usesysid FROM pg_user WHERE usename = current_user LIMIT 1) +ORDER BY total_exec_time DESC +LIMIT 20; diff --git a/src/sql/outliers_legacy.sql b/src/sql/outliers_legacy.sql index f353455..98d8b3d 100644 --- a/src/sql/outliers_legacy.sql +++ b/src/sql/outliers_legacy.sql @@ -7,4 +7,4 @@ interval '1 millisecond' * (blk_read_time + blk_write_time) AS sync_io_time, query AS query FROM pg_stat_statements WHERE userid = (SELECT usesysid FROM pg_user WHERE usename = current_user LIMIT 1) ORDER BY total_time DESC -LIMIT %{limit}; +LIMIT 20;