Skip to main content
Integrating pgvet into your CI/CD pipeline helps catch SQL issues before they reach production.

Exit Codes

pgvet uses standard exit codes to communicate results:
  • 0: No issues found - all SQL passed analysis
  • 1: Issues found (warnings or errors)
  • 2: Fatal error (parse failure, file not found, etc.)
Use these codes to control build behavior:
# Fail build if any issues found
pgvet sql/
if [ $? -ne 0 ]; then
    echo "SQL issues detected!"
    exit 1
fi

# Fail only on fatal errors (allow warnings)
pgvet sql/
if [ $? -eq 2 ]; then
    echo "pgvet encountered an error"
    exit 1
fi

JSON Output Format

Use --format json to get machine-readable output for parsing in CI systems:
pgvet --format json sql/ > results.json
The JSON output is an array of diagnostics:
[
  {
    "rule": "select-star",
    "message": "SELECT * in outermost query is fragile — list columns explicitly",
    "file": "queries/users.sql",
    "line": 5,
    "col": 8,
    "severity": "warning"
  },
  {
    "rule": "not-in-subquery",
    "message": "NOT IN (SELECT ...) is broken when the subquery can return NULLs — use NOT EXISTS instead",
    "file": "queries/orders.sql",
    "line": 12,
    "col": 3,
    "severity": "error"
  }
]

Filtering by Severity

To fail the build only on errors (not warnings), parse the JSON:
pgvet --format json sql/ > results.json
errors=$(jq '[.[] | select(.severity == "error")] | length' results.json)

if [ "$errors" -gt 0 ]; then
    echo "Found $errors SQL errors"
    jq '.[] | select(.severity == "error")' results.json
    exit 1
fi

warnings=$(jq '[.[] | select(.severity == "warning")] | length' results.json)
if [ "$warnings" -gt 0 ]; then
    echo "Found $warnings SQL warnings (not failing build)"
    jq '.[] | select(.severity == "warning")' results.json
fi

GitHub Actions

Basic Integration

Add a workflow file at .github/workflows/sql-lint.yml:
name: SQL Lint

on:
  pull_request:
    paths:
      - '**.sql'
  push:
    branches:
      - main

jobs:
  pgvet:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Install pgvet
        run: |
          curl -fsSL https://github.com/mnafees/pgvet/releases/latest/download/pgvet-linux-amd64 -o /usr/local/bin/pgvet
          chmod +x /usr/local/bin/pgvet
      
      - name: Run pgvet
        run: pgvet sql/

With Annotations

Create inline annotations on your PR using the JSON output:
- name: Run pgvet
  id: pgvet
  run: |
    pgvet --format json sql/ > results.json || true
  
- name: Annotate PR
  if: always()
  run: |
    jq -r '.[] | "::" + (if .severity == "error" then "error" else "warning" end) + " file=" + .file + ",line=" + (.line | tostring) + ",col=" + (.col | tostring) + "::" + .rule + ": " + .message' results.json

- name: Check for errors
  run: |
    errors=$(jq '[.[] | select(.severity == "error")] | length' results.json)
    if [ "$errors" -gt 0 ]; then
      echo "Found $errors SQL errors"
      exit 1
    fi

Fail on Errors Only

- name: Run pgvet (errors only)
  run: |
    pgvet --format json sql/ > results.json
    exit_code=$?
    
    # Exit 2 means parse/runtime error - always fail
    if [ $exit_code -eq 2 ]; then
      cat results.json
      exit 1
    fi
    
    # Exit 1 means issues found - check if any are errors
    if [ $exit_code -eq 1 ]; then
      error_count=$(jq '[.[] | select(.severity == "error")] | length' results.json)
      if [ "$error_count" -gt 0 ]; then
        echo "::error::Found $error_count SQL errors"
        jq '.[] | select(.severity == "error")' results.json
        exit 1
      else
        echo "::notice::Found warnings but no errors"
      fi
    fi

GitLab CI

Add to .gitlab-ci.yml:
sql-lint:
  stage: test
  image: alpine:latest
  before_script:
    - apk add --no-cache curl jq
    - curl -fsSL https://github.com/mnafees/pgvet/releases/latest/download/pgvet-linux-amd64 -o /usr/local/bin/pgvet
    - chmod +x /usr/local/bin/pgvet
  script:
    - pgvet --format json sql/ > results.json
    - |
      error_count=$(jq '[.[] | select(.severity == "error")] | length' results.json)
      if [ "$error_count" -gt 0 ]; then
        echo "Found $error_count SQL errors:"
        jq '.[] | select(.severity == "error")' results.json
        exit 1
      fi
  artifacts:
    reports:
      codequality: results.json
    when: always
  only:
    changes:
      - "**/*.sql"

Code Quality Report

Convert pgvet output to GitLab’s Code Quality format:
script:
  - pgvet --format json sql/ > pgvet-results.json || true
  - |
    jq '[.[] | {
      description: .message,
      check_name: .rule,
      fingerprint: (.file + ":" + (.line | tostring) + ":" + .rule),
      severity: (if .severity == "error" then "major" else "minor" end),
      location: {
        path: .file,
        lines: {
          begin: .line
        }
      }
    }]' pgvet-results.json > gl-code-quality-report.json
artifacts:
  reports:
    codequality: gl-code-quality-report.json

Jenkins

Add to your Jenkinsfile:
pipeline {
    agent any
    
    stages {
        stage('SQL Lint') {
            steps {
                sh '''
                    # Install pgvet if not already available
                    if ! command -v pgvet &> /dev/null; then
                        curl -fsSL https://github.com/mnafees/pgvet/releases/latest/download/pgvet-linux-amd64 -o /usr/local/bin/pgvet
                        chmod +x /usr/local/bin/pgvet
                    fi
                    
                    # Run analysis
                    pgvet --format json sql/ > results.json || true
                '''
                
                script {
                    def results = readJSON file: 'results.json'
                    def errors = results.findAll { it.severity == 'error' }
                    def warnings = results.findAll { it.severity == 'warning' }
                    
                    if (warnings.size() > 0) {
                        echo "Found ${warnings.size()} SQL warnings"
                        warnings.each { warning ->
                            echo "  ${warning.file}:${warning.line}:${warning.col} - ${warning.message}"
                        }
                    }
                    
                    if (errors.size() > 0) {
                        echo "Found ${errors.size()} SQL errors"
                        errors.each { error ->
                            echo "  ${error.file}:${error.line}:${error.col} - ${error.message}"
                        }
                        error("SQL analysis failed with ${errors.size()} errors")
                    }
                }
            }
        }
    }
    
    post {
        always {
            archiveArtifacts artifacts: 'results.json', allowEmptyArchive: true
        }
    }
}

CircleCI

Add to .circleci/config.yml:
version: 2.1

jobs:
  sql-lint:
    docker:
      - image: cimg/base:stable
    steps:
      - checkout
      
      - run:
          name: Install pgvet
          command: |
            curl -fsSL https://github.com/mnafees/pgvet/releases/latest/download/pgvet-linux-amd64 -o /tmp/pgvet
            chmod +x /tmp/pgvet
            sudo mv /tmp/pgvet /usr/local/bin/
      
      - run:
          name: Run SQL analysis
          command: |
            pgvet --format json sql/ > results.json
            error_count=$(jq '[.[] | select(.severity == "error")] | length' results.json)
            if [ "$error_count" -gt 0 ]; then
              echo "Found $error_count SQL errors"
              jq '.[] | select(.severity == "error")' results.json
              exit 1
            fi
      
      - store_artifacts:
          path: results.json

workflows:
  version: 2
  build:
    jobs:
      - sql-lint

Pre-commit Hook

For local enforcement before pushing, add .pre-commit-config.yaml:
repos:
  - repo: local
    hooks:
      - id: pgvet
        name: pgvet SQL linter
        entry: pgvet
        language: system
        files: \.sql$
        pass_filenames: true
Or create .git/hooks/pre-commit:
#!/bin/bash

# Get staged SQL files
FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.sql$')

if [ -n "$FILES" ]; then
    echo "Running pgvet on staged SQL files..."
    echo "$FILES" | xargs pgvet
    
    if [ $? -ne 0 ]; then
        echo "pgvet found issues. Commit aborted."
        echo "Fix the issues or use 'git commit --no-verify' to bypass."
        exit 1
    fi
fi

Tips

  • Cache pgvet binary in CI to speed up builds
  • Run only on changed files using git diff to filter paths
  • Use rule selection (--rules or --exclude) to customize checks per project
  • Store JSON artifacts for trend analysis over time
  • Set up notifications (Slack, email) when errors are found in main branch

Build docs developers (and LLMs) love