OmniSQL - Shivam-Jha96/omnisql GitHub Wiki

npm version PyPI version VS Marketplace License: MIT

OmniSQL is a high-performance command-line SQL linter written in Rust. It checks SQL files for style problems and, when given a database schema, for semantic problems such as unknown or ambiguous column references. It also statically analyzes queries for expensive query patterns (cost) and risky statements (security), supports custom rules via WebAssembly (WASM) plugins, and provides editor diagnostics through a Language Server Protocol (LSP) implementation.

Property Value
Current version 1.1.0
License MIT
Default SQL dialect PostgreSQL (postgres)
Source code github.com/Shivam-Jha96/omnisql
Release notes release_notes/

📑 Table of Contents


What OmniSQL Checks

Rule Catalog

Rule ID Category Description Severity Needs Schema? Auto-fixable?
KeywordCasing Style Enforces uniform keyword casing (UPPERCASE by default) Warning No Yes (--fix)
TrailingCommas Style Detects stray trailing commas in SELECT lists Warning No Yes (--fix)
UnknownColumn Semantic Unknown column references not found in the target tables Error Yes No
AmbiguousColumn Semantic Unqualified columns that appear in multiple joined tables Error Yes No
CartesianJoinWarning Cost Constant truthy JOIN ON conditions (e.g. 1=1, ON true) Warning No No
MissingPartitionFilter Cost Missing WHERE filter on a table's declared partition column Error Yes No
UnmaskedPIISelect Security Direct selection of unmasked columns tagged with @PII Error Yes No
UnsafeGrant Security Overly permissive GRANT ALL PRIVILEGES statements Error No No
DangerousMigration Security Destructive DROP TABLE (Error) or ALTER TABLE (Warning) Error / Warning No No

What OmniSQL Does Not Do

  • --fix only repairs style. It does not repair semantic, cost, or security findings. For example, it will not fabricate missing JOIN conditions or synthesize column masking expressions.
  • Cost rules are static checks on query syntax. OmniSQL does not execute queries and does not calculate monetary warehouse expenses or query plan estimates.
  • Semantic checks require a schema. Without --schema or --dbt-manifest, schema-dependent rules cannot run and will be skipped with a warning.

Installation

OmniSQL distributes native binaries built for Windows (x86_64), Linux (x86_64, aarch64), and macOS (x86_64, Apple Silicon aarch64). The npm and PyPI packages are zero-dependency wrappers that download and cache the native binary for your architecture on installation.

Ecosystem Command
npm npm install -g omnisql
PyPI pip install omnisql
VS Code code --install-extension omnisql.omnisql-vscode

The VS Code extension is also available directly on the Visual Studio Marketplace.

Verify your installation:

omnisql --help

Supported Platforms & Environments

  • Node.js: Version 18+ (for npm installations)
  • Python: Version 3.8+ (for pip installations)
  • Air-Gapped / Offline Environments: Download pre-compiled standalone binaries directly from GitHub Releases and place omnisql in your system PATH, or build directly from source:
    cargo install --path .

Upgrading

# npm
npm update -g omnisql

# PyPI
pip install --upgrade omnisql

See release_notes/ for release changelogs.


Quick Start

Lint a single query file:

omnisql lint path/to/query.sql

Lint an entire directory of SQL queries:

omnisql lint ./sql_queries/

Add semantic validation by passing a DDL schema file:

omnisql lint --schema path/to/schema.sql ./sql_queries/

Example Terminal Output

When violations are detected, OmniSQL prints the findings directly to the console:

$ omnisql lint --schema schema.ddl queries.sql
Processing path: queries.sql
Security Error: UnmaskedPIISelect: Selecting PII column 'users.email' without masking.
Cost Error: MissingPartitionFilter: Table 'events' is partitioned by 'dt', but no filter was provided.
Parsing successful.

Command Reference

omnisql lint [OPTIONS] <PATH>

<PATH> is a single .sql file or a directory containing SQL files (recursively processed).

CLI Flags & Options

Option Flag Description Example
--schema <path> -s Path to a DDL schema file for semantic validation omnisql lint -s schema.sql ./queries/
--dbt-manifest <path> Path to a dbt manifest.json file omnisql lint --dbt-manifest target/manifest.json ./models/
--plugin <path> -p Path to a compiled WebAssembly (.wasm) rule plugin omnisql lint -p rules.wasm ./queries/
--dialect <name> Target SQL dialect (postgres, snowflake, generic). Default: postgres omnisql lint --dialect snowflake ./queries/
--fix Automatically fixes style violations in-place omnisql lint --fix ./queries/
--verbose -v Prints detailed debugging and diagnostic info omnisql lint -v ./queries/

Flags can be freely combined:

omnisql lint --fix --schema schema.sql --dialect postgres ./sql_queries/

Note on Configuration File (--config): Centralized YAML configuration (omnisql.yaml) is planned for a future release. For version 1.1.0, configure runs using the CLI flags above.


Guides

Validate Queries Against a Schema (DDL)

Pass a DDL schema file with --schema to check column references against table definitions.

schema.sql:

CREATE TABLE customers (
    id INT PRIMARY KEY,
    email TEXT,
    created_at TIMESTAMP
);

CREATE TABLE orders (
    id INT PRIMARY KEY,
    customer_id INT,
    created_at TIMESTAMP,
    total NUMERIC
);

query.sql:

-- Typo: "emial" does not exist in customers
SELECT emial FROM customers;

-- Ambiguity: "id" and "created_at" exist in both tables
SELECT id, created_at
FROM customers c
JOIN orders o ON o.customer_id = c.id;

Run:

omnisql lint --schema schema.sql query.sql

Output:

Processing path: query.sql
Semantic Error: Column 'emial' not found in any queried tables (["customers"]).
Semantic Error: Ambiguous column 'id'. Found in tables: ["customers", "orders"]
Semantic Error: Ambiguous column 'created_at'. Found in tables: ["customers", "orders"]
Parsing successful.

To fix the ambiguity, qualify each column (c.id, o.created_at).


Validate dbt Models

OmniSQL natively ingests dbt manifest.json to resolve models, sources, and column definitions.

Because OmniSQL is a SQL parser and does not evaluate Jinja templates ({{ ref(...) }} or Jinja macros), point OmniSQL at your compiled SQL directory after generating your manifest:

# 1. Compile your dbt project to generate manifest.json and compiled SQL
dbt compile

# 2. Lint the compiled models using the generated manifest
omnisql lint --dbt-manifest target/manifest.json ./target/compiled/my_dbt_project/models/

OmniSQL automatically resolves table and column definitions from manifest.nodes and manifest.sources.


Choose a SQL Dialect

OmniSQL parses PostgreSQL syntax by default. When linting Snowflake-specific constructs (such as VARIANT colon JSON extraction or QUALIFY filtering), specify the Snowflake dialect:

omnisql lint --dialect snowflake ./snowflake_queries/

Supported dialects:

  • postgres (default)
  • snowflake
  • generic

Fix Style Issues Automatically

Run OmniSQL with --fix to repair style issues in-place:

omnisql lint --fix ./sql_queries/

Warning

--fix resolves stylistic violations only (such as SQL keyword casing and trailing commas). It does not alter query logic, synthesize missing joins, or mask sensitive columns.

Recommended workflow: Ensure all uncommitted changes are committed in Git, run omnisql lint --fix, and inspect the resulting git diff before committing.


Work with @PII Columns

OmniSQL flags any query selecting unmasked columns designated as personally identifiable information (PII).

1. In DDL Schemas

Mark sensitive columns with an inline SQL comment containing @PII (case-insensitive):

CREATE TABLE users (
    id INT,
    email VARCHAR COMMENT '@PII',
    ssn VARCHAR COMMENT '@PII'
);

2. In dbt Projects

Add meta.pii: true in your dbt schema.yml:

version: 2
models:
  - name: users
    columns:
      - name: email
        meta:
          pii: true

Finding Example

When a query selects email directly without masking, OmniSQL flags it:

SELECT email FROM users;
Security Error: UnmaskedPIISelect: Selecting PII column 'users.email' without masking.

Work with Partitioned Tables

To prevent full-table scans and runaway cloud warehouse compute costs, OmniSQL can require explicit WHERE filters on partitioned tables.

1. In DDL Schemas

Tag the partitioning column with a comment containing @PARTITION (case-insensitive):

CREATE TABLE events (
    event_id INT,
    user_id INT,
    event_date DATE COMMENT '@PARTITION'
);

2. In dbt Projects

Declare the partitioning column in the model metadata in schema.yml:

version: 2
models:
  - name: events
    meta:
      partition_column: "event_date"

Failing vs. Passing Queries

❌ Failing Query (triggers MissingPartitionFilter):

SELECT event_id, user_id FROM events WHERE user_id = 101;
-- Cost Error: MissingPartitionFilter: Table 'events' is partitioned by 'event_date', but no filter was provided.

✅ Passing Query (filter on partition column present):

SELECT event_id, user_id FROM events WHERE event_date >= '2026-01-01' AND user_id = 101;

Custom Rules (WASM Plugins)

OmniSQL embeds a high-performance WebAssembly runtime powered by Wasmtime and WASI Preview 1. You can author custom linters or company-specific compliance checks in Rust, TypeScript, Python, or Go and execute them natively inside OmniSQL.

Run a plugin using the --plugin (-p) flag:

omnisql lint --plugin path/to/rule.wasm ./sql_queries/

Plugin Interface

Plugins execute as a standard WASI binary (_start entry point):

  1. Input: OmniSQL sends the JSON-serialized Abstract Syntax Tree (AST) of each SQL statement via standard input (stdin).
  2. Output: The plugin prints a JSON array of LintIssue objects to standard output (stdout):
    [
      {
        "rule_name": "NoSelectStar",
        "severity": "Warning",
        "message": "Avoid using SELECT *, specify columns explicitly."
      }
    ]

Authoring a Plugin in Rust

Cargo.toml:

[package]
name = "custom_rule"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

src/lib.rs:

use serde::Serialize;
use std::io::{self, Read};

#[derive(Serialize)]
pub struct LintIssue {
    pub rule_name: String,
    pub severity: String,
    pub message: String,
}

#[no_mangle]
pub extern "C" fn _start() {
    let mut input = String::new();
    if io::stdin().read_to_string(&mut input).is_err() {
        return;
    }
    
    let ast: serde_json::Value = match serde_json::from_str(&input) {
        Ok(v) => v,
        Err(_) => return,
    };
    
    let mut issues = Vec::new();
    
    // Custom check: Disallow SELECT *
    if let Some(select) = ast.get("Select") {
        if let Some(columns) = select.get("columns").and_then(|c| c.as_array()) {
            for col in columns {
                if col.get("name").and_then(|n| n.as_str()) == Some("*") {
                    issues.push(LintIssue {
                        rule_name: "NoSelectStar".to_string(),
                        severity: "Warning".to_string(),
                        message: "Avoid using SELECT *, specify columns explicitly.".to_string(),
                    });
                    break;
                }
            }
        }
    }
    
    if !issues.is_empty() {
        if let Ok(json) = serde_json::to_string(&issues) {
            println!("{}", json);
        }
    }
}

Build the .wasm module:

cargo build --target wasm32-wasip1 --release
# Output: target/wasm32-wasip1/release/custom_rule.wasm

Authoring a Plugin in TypeScript

Using standard Node.js/WASI toolchains (or AssemblyScript):

import * as fs from 'fs';

// Read JSON AST from stdin
const input = fs.readFileSync(0, 'utf-8');
const ast = JSON.parse(input);

const issues = [];

if (ast.Select?.columns) {
  for (const col of ast.Select.columns) {
    if (col.name === '*') {
      issues.push({
        rule_name: 'NoSelectStar',
        severity: 'Warning',
        message: 'Avoid using SELECT *. Explicitly list columns for schema stability.'
      });
    }
  }
}

// Print findings as JSON to stdout
if (issues.length > 0) {
  console.log(JSON.stringify(issues));
}

Editor Integration (LSP)

OmniSQL includes a native Language Server Protocol (LSP) server via the omnisql lsp subcommand.

VS Code

Install the official extension:

code --install-extension omnisql.omnisql-vscode

Settings

In your workspace .vscode/settings.json:

{
  "omnisql.schemaPath": "path/to/schema.sql",
  "omnisql.dialect": "postgres"
}

Neovim, Helix, and Other Editors

OmniSQL's LSP server can be integrated with any editor supporting the Language Server Protocol.

Command:

omnisql lsp --schema path/to/schema.sql --dialect postgres

Neovim (nvim-lspconfig Example)

local lspconfig = require('lspconfig')
local configs = require('lspconfig.configs')

if not configs.omnisql then
  configs.omnisql = {
    default_config = {
      cmd = { 'omnisql', 'lsp', '--schema', 'schema.sql' },
      filetypes = { 'sql' },
      root_dir = lspconfig.util.root_pattern('.git', 'schema.sql'),
    },
  }
end
lspconfig.omnisql.setup({})

Continuous Integration

Run OmniSQL on every pull request to enforce SQL standards in CI.

GitHub Actions Workflow

name: SQL Lint

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

jobs:
  lint:
    name: Lint SQL Queries
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install OmniSQL
        run: npm install -g [email protected]

      - name: Run OmniSQL Linter
        run: omnisql lint --schema schema.sql ./sql_queries/

Known Limitations & Roadmap

  • --fix Scope: Currently limited to stylistic formatting (keyword casing and trailing commas).
  • Static Cost Analysis: Cost warnings evaluate query syntax statically without connecting to live databases or fetching runtime query execution plans.
  • Pre-Compiled dbt Models: Jinja syntax ({{ ... }}) is not evaluated; models should be compiled with dbt compile prior to running semantic linting.
  • Upcoming Features:
    • Centralized YAML configuration file (omnisql.yaml).
    • Configurable CI exit codes (--fail-on-error, --fail-on-warning).
    • Additional SQL dialect support (BigQuery, Trino).

Getting Help and Contributing

  • Report Issues or Request Features: GitHub Issues
  • Contribution Guide: Read CONTRIBUTING.md for architecture guidelines and setup instructions
  • License: MIT
⚠️ **GitHub.com Fallback** ⚠️