OmniSQL - Shivam-Jha96/omnisql GitHub Wiki
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/ |
- What OmniSQL Checks
- Installation
- Quick Start
- Command Reference
- Guides
- Custom Rules (WASM Plugins)
- Editor Integration (LSP)
- Continuous Integration
- Known Limitations & Roadmap
- Getting Help and Contributing
| 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 |
-
--fixonly repairs style. It does not repair semantic, cost, or security findings. For example, it will not fabricate missingJOINconditions 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
--schemaor--dbt-manifest, schema-dependent rules cannot run and will be skipped with a warning.
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- 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
omnisqlin your systemPATH, or build directly from source:cargo install --path .
# npm
npm update -g omnisql
# PyPI
pip install --upgrade omnisqlSee release_notes/ for release changelogs.
Lint a single query file:
omnisql lint path/to/query.sqlLint 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/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.
omnisql lint [OPTIONS] <PATH>
<PATH> is a single .sql file or a directory containing SQL files (recursively processed).
| 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.
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.sqlOutput:
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).
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.
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) snowflakegeneric
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.
OmniSQL flags any query selecting unmasked columns designated as personally identifiable information (PII).
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'
);Add meta.pii: true in your dbt schema.yml:
version: 2
models:
- name: users
columns:
- name: email
meta:
pii: trueWhen a query selects email directly without masking, OmniSQL flags it:
SELECT email FROM users;Security Error: UnmaskedPIISelect: Selecting PII column 'users.email' without masking.
To prevent full-table scans and runaway cloud warehouse compute costs, OmniSQL can require explicit WHERE filters on partitioned tables.
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'
);Declare the partitioning column in the model metadata in schema.yml:
version: 2
models:
- name: events
meta:
partition_column: "event_date"❌ 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;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/Plugins execute as a standard WASI binary (_start entry point):
-
Input: OmniSQL sends the JSON-serialized Abstract Syntax Tree (AST) of each SQL statement via standard input (
stdin). -
Output: The plugin prints a JSON array of
LintIssueobjects to standard output (stdout):[ { "rule_name": "NoSelectStar", "severity": "Warning", "message": "Avoid using SELECT *, specify columns explicitly." } ]
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.wasmUsing 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));
}OmniSQL includes a native Language Server Protocol (LSP) server via the omnisql lsp subcommand.
Install the official extension:
code --install-extension omnisql.omnisql-vscodeIn your workspace .vscode/settings.json:
{
"omnisql.schemaPath": "path/to/schema.sql",
"omnisql.dialect": "postgres"
}OmniSQL's LSP server can be integrated with any editor supporting the Language Server Protocol.
Command:
omnisql lsp --schema path/to/schema.sql --dialect postgreslocal 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({})Run OmniSQL on every pull request to enforce SQL standards in CI.
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/-
--fixScope: 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 withdbt compileprior 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).
- Centralized YAML configuration file (
- Report Issues or Request Features: GitHub Issues
- Contribution Guide: Read CONTRIBUTING.md for architecture guidelines and setup instructions
- License: MIT