This is a conversion of the original Python dblinter to a PostgreSQL extension written in Rust using pgrx.
In recent years, DBAs were more involved with the database engine itselfβcreating instances, configuring backups, and monitoring systemsβwhile also overseeing developers' activities. Today, in the DBRE world where databases are cloud-managed, developers and operations teams often work independently, without a dedicated DBA. So databases objects lives their own life, created by persons that do their best. It can be usefull to be able to detect some wrong design creation (for example foreign keys created accross differents schemas...). That's what pglinter was created for.
pglinter is a PostgreSQL linter that analyzes your database for potential issues, performance problems, and best practice violations. This Rust implementation provides:
- Better Performance: Native Rust performance vs Python
- Deep Integration: Runs directly inside PostgreSQL using pgrx
- SARIF Output: Industry-standard Static Analysis Results Interchange Format
- Extensible Rules: Easy to add new rules using Rust traits
This extension is built with pgrx 0.16.1 and supports:
- PostgreSQL 13
- PostgreSQL 14
- PostgreSQL 15
- PostgreSQL 16
- PostgreSQL 17
- PostgreSQL 18beta2 β¨ (latest with pgrx 0.16.1)
- B (Base/Database): Database-wide checks
- C (Cluster): PostgreSQL cluster configuration checks
- T (Table): Individual table checks
- S (Schema): Schema-level checks
- Rule Engine (
rule_engine.rs
): Core engine that executes rules - Rule Trait: Common interface for all rules
- SARIF Generator: Creates standardized output format
- Configuration: Database-stored rule configurations
- Rust 1.88.0+ (required for pgrx 0.16.1)
- PostgreSQL 13-18 development packages
- cargo-pgrx 0.16.1
Debian/Ubuntu Systems:
# Download and install (replace XX with your PG version: 13, 14, 15, 16, 17, 18)
wget https://github.com/pmpetit/pglinter/releases/download/0.0.17/postgresql_pglinter_XX_0.0.17_amd64.deb
sudo dpkg -i postgresql_pglinter_XX_0.0.17_amd64.deb
# Fix dependencies if needed
sudo apt-get install -f
RHEL/CentOS/Fedora Systems:
# Download and install (replace XX with your PG version: 13, 14, 15, 16, 17, 18)
wget https://github.com/pmpetit/pglinter/releases/download/0.0.17/postgresql_pglinter_XX-0.0.17-1.x86_64.rpm
sudo rpm -i postgresql_pglinter_XX-0.0.17-1.x86_64.rpm
# or
sudo yum localinstall postgresql_pglinter_XX-0.0.17-1.x86_64.rpm
After installation, enable the extension in your PostgreSQL database:
-- Connect to your database
\c your_database
-- Create the extension
CREATE EXTENSION pglinter;
-- Run a basic check
SELECT pglinter.perform_base_check();
-- Check specific rules
SELECT pglinter.check_rule('B001'); -- Tables without primary keys
SELECT pglinter.check_rule('B002'); -- Redundant indexes
- B001-B008: Base database rules (primary keys, indexes, schemas, etc.)
- C002: Cluster security rules
- T001-T010: Individual table rules
- S001: Schema rules
For complete documentation, visit: https://github.com/pmpetit/pglinter/blob/main/docs/functions/README.md
# Install cargo-pgrx if not already installed
cargo install --locked cargo-pgrx
# Initialize pgrx (one time setup)
cargo pgrx init
# Build the extension for your PostgreSQL version
cargo pgrx package
# Install using the Makefile (handles both system and pgrx-managed PostgreSQL)
sudo make install
# Or install manually for a specific PostgreSQL version
sudo PGVER=pg16 make install
# Load in your database
psql -d your_database -c "CREATE EXTENSION pglinter;"
The extension provides comprehensive database analysis functions with optional file output:
-- Quick comprehensive check (output to prompt)
SELECT pglinter.check_all();
-- Individual category checks (output to prompt)
SELECT pglinter.check_base();
SELECT pglinter.check_cluster();
SELECT pglinter.check_table();
SELECT pglinter.check_schema();
-- Generate SARIF reports to files
SELECT pglinter.perform_base_check('/path/to/base_results.sarif');
SELECT pglinter.perform_cluster_check('/path/to/cluster_results.sarif');
SELECT pglinter.perform_table_check('/path/to/table_results.sarif');
SELECT pglinter.perform_schema_check('/path/to/schema_results.sarif');
-- Rule management
SELECT pglinter.show_rules(); -- Show all rules and status
SELECT pglinter.explain_rule('B001'); -- Get rule details and fixes
SELECT pglinter.enable_rule('B001'); -- Enable specific rule
SELECT pglinter.disable_rule('B001'); -- Disable specific rule
SELECT pglinter.is_rule_enabled('B001'); -- Check rule status
- B001: Tables without primary keys
- B002: Redundant indexes
- B003: Tables without indexes on foreign keys
- B004: Unused indexes
- B005: Unsecured public schema
- B006: Tables with uppercase names/columns
- C001: max_connections * work_mem > available RAM
- C002: Insecure pg_hba.conf entries
- T001: Individual tables without primary keys
- T002: Tables without any indexes
- T003: Tables with redundant indexes
- T004: Tables with foreign keys not indexed
- T005: Tables with potential missing indexes (high sequential scan usage)
- T006: Tables with foreign keys referencing other schemas
- T007: Tables with unused indexes
- T008: Tables with foreign key type mismatches
- T009: Tables with no roles granted
- T010: Tables using reserved keywords
- T011: Tables with uppercase names/columns
- T012: Tables with sensitive columns (requires anon extension)
- S001: Schemas without default role grants
- S002: Schemas with environment prefixes/suffixes
Adding a new rule is straightforward:
pub struct B007Rule; // New rule
impl DatabaseRule for B007Rule {
fn execute(&self, params: &[RuleParam]) -> spi::Result<Option<RuleResult>> {
// Your rule logic here
let query = "SELECT count(*) FROM problematic_tables";
let result = Spi::connect(|client| {
// Execute query and analyze results
// Return RuleResult if issues found
})?;
Ok(result)
}
fn rule_id(&self) -> &str { "B007" }
fn scope(&self) -> RuleScope { RuleScope::Base }
fn name(&self) -> &str { "YourRuleName" }
}
// Register in RuleEngine::new()
rules.insert("B007".to_string(), Box::new(B007Rule));
Results are generated in SARIF 2.1.0 format:
{
"version": "2.1.0",
"runs": [{
"tool": {
"driver": {
"name": "pglinter",
"version": "1.0.0"
}
},
"results": [{
"ruleId": "B001",
"level": "warning",
"message": {
"text": "5 tables without primary key exceed threshold: 10%"
},
"locations": [{
"physicalLocation": {
"artifactLocation": {
"uri": "database"
}
}
}]
}]
}]
}
This Rust implementation maintains compatibility with the original Python pglinter while offering:
- Performance: 10-100x faster execution
- Memory Safety: Rust's memory management
- Integration: No external dependencies or connections needed
- Deployment: Single extension installation
- Rule Logic: Direct translation of Python rule logic to Rust
- Configuration: Database-stored instead of YAML files
- Output Format: Same SARIF 2.1.0 format
- API: PostgreSQL functions instead of CLI interface
# Run tests
cargo pgrx test
# Development build
cargo pgrx run
# Package for distribution
cargo pgrx package
Documentation https://pglinter.readthedocs.io/en/latest/
- Fork the repository
- Create a feature branch
- Add new rules following the established pattern
- Add tests for your rules
- Update documentation
- Submit a pull request
Same as original pglinter project.