{"id":49066694,"url":"https://github.com/f4rkh4d/drift","last_synced_at":"2026-05-21T19:01:37.703Z","repository":{"id":352541343,"uuid":"1215552856","full_name":"f4rkh4d/drift","owner":"f4rkh4d","description":"sql linter and formatter in rust. 7 dialects, 80 rules, schema-aware. single binary. 50-200x sqlfluff. demo at drift.frkhd.com/play/","archived":false,"fork":false,"pushed_at":"2026-05-20T20:53:18.000Z","size":1561,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-20T23:53:40.449Z","etag":null,"topics":["bigquery","cli","database","dbt","formatter","linter","lsp","migration","mysql","postgres","rust","schema-aware","snowflake","sql","sql-linter","sqlfluff","sqlite","static-analysis","tsql","wasm"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/f4rkh4d.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-20T03:05:07.000Z","updated_at":"2026-05-20T20:53:20.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/f4rkh4d/drift","commit_stats":null,"previous_names":["f4rkh4d/drift"],"tags_count":75,"template":false,"template_full_name":null,"purl":"pkg:github/f4rkh4d/drift","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/f4rkh4d%2Fdrift","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/f4rkh4d%2Fdrift/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/f4rkh4d%2Fdrift/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/f4rkh4d%2Fdrift/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/f4rkh4d","download_url":"https://codeload.github.com/f4rkh4d/drift/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/f4rkh4d%2Fdrift/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33311393,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-21T12:23:38.849Z","status":"ssl_error","status_checked_at":"2026-05-21T12:22:11.673Z","response_time":62,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bigquery","cli","database","dbt","formatter","linter","lsp","migration","mysql","postgres","rust","schema-aware","snowflake","sql","sql-linter","sqlfluff","sqlite","static-analysis","tsql","wasm"],"created_at":"2026-04-20T05:05:22.486Z","updated_at":"2026-05-21T19:01:37.685Z","avatar_url":"https://github.com/f4rkh4d.png","language":"Rust","funding_links":[],"categories":["Utilities"],"sub_categories":[],"readme":"# drift\n\n[![ci](https://github.com/f4rkh4d/drift/actions/workflows/ci.yml/badge.svg)](https://github.com/f4rkh4d/drift/actions/workflows/ci.yml)\n[![crates.io](https://img.shields.io/crates/v/drift-sql.svg)](https://crates.io/crates/drift-sql)\n[![release](https://img.shields.io/github/v/release/f4rkh4d/drift)](https://github.com/f4rkh4d/drift/releases)\n[![license](https://img.shields.io/github/license/f4rkh4d/drift)](LICENSE)\n\n![demo](docs/demo.gif)\nsql linter and formatter in rust. 7 dialects. single binary.\n\n**try it in your browser at [drift.frkhd.com/play/](https://drift.frkhd.com/play/)** -- same engine as the cli, no install needed.\n\n94 rules across 9 categories. on a 200-file SQL corpus drift's median wall time is **34 ms** vs sqlfluff's **15.3 s** (~448x faster). on a 4,200-file dbt project the ratio narrows to 60-180x because sqlfluff's heavy macro work pulls less from python overhead. full numbers and method in [`benches/RESULTS.md`](benches/RESULTS.md). reproduce: `bash benches/sqlfluff_compare.sh`.\n\n```\n$ drift check migrations/\n\nerror    drift.correctness.missing-where-update  UPDATE statement has no WHERE clause\n   ┌─ migrations/0042_backfill.sql:12:1\n   │\n12 │ update accounts set status = 'archived';\n   │ ^^^^^^\n\nwarning  drift.style.keyword-case  keyword `select` should be `SELECT`\n   ┌─ migrations/0044_seed.sql:3:1\n   │\n 3 │ select id, email from users where deleted_at = null;\n   │ ^^^^^^ → SELECT\n\nerror    drift.correctness.null-equality  comparing to NULL with = or \u003c\u003e; use IS NULL\n   ┌─ migrations/0044_seed.sql:3:43\n   │\n 3 │ select id, email from users where deleted_at = null;\n   │                                           ^^^^^^^^^^\n```\n\nDiagnostics are rendered as code frames with the offending span underlined and the fix shown inline. Machine output is one flag away: `--format json`, `sarif`, `checkstyle`, or `compact`.\n\n## install\n\n```sh\n# one-liner (linux + mac, amd64 + arm64)\ncurl -fsSL https://raw.githubusercontent.com/f4rkh4d/drift/main/install.sh | sh\n\n# homebrew (mac)\nbrew install f4rkh4d/tap/drift\n\n# cargo. note: the unscoped `drift` name on crates.io is an unrelated openapi\n# tool; this crate ships as `drift-sql`, the installed binary is still `drift`.\ncargo install drift-sql\n```\n\npre-built binaries for linux/mac (amd64 + arm64) are attached to every release: \u003chttps://github.com/f4rkh4d/drift/releases\u003e.\n\n## quick start\n\n```\ndrift init                                 # scaffold a drift.toml in the current dir\ndrift check **/*.sql                       # lint, exit 1 on errors\n                                           # honors `-- drift:disable RULE_ID` line comments\ndrift check --fail-on warning ...          # exit 1 on warnings or errors\ndrift check --format sarif ...             # output for github code scanning\ndrift check --format json ...              # output for any other consumer\ndrift check --baseline .drift-baseline.json ...  # silence violations recorded in the baseline\ndrift check --watch migrations/            # re-lint on every save\ndrift check --diff                         # lint only files changed vs git HEAD (staged, unstaged, untracked)\ndrift check --staged                       # lint exactly what is staged for commit (the index, not the working tree)\ndrift fix --diff                           # auto-fix only the files git considers changed\ndrift baseline create migrations/          # snapshot current violations into .drift-baseline.json\ndrift baseline show                        # print a summary of an existing baseline\ndrift profile migrations/                  # which rules fire most, what to disable\ndrift docs                                 # regenerate docs/rules/\u003cid\u003e.md from rule trait\ndrift docs --check                         # ci gate: fail if docs are stale\ndrift fix                                  # apply safe auto-fixes\ndrift format queries.sql                   # reformat to stdout\ndrift format -i queries.sql                # rewrite in place\ndrift rules                                # list all rules\ndrift explain drift.correctness.null-equality\ndrift lsp                                  # language server over stdio\n```\n\nany of them take `--dialect postgres|mysql|sqlite|bigquery|snowflake|tsql|ansi`. when left unset, drift looks at the file extension and then the nearest `drift.toml`.\n\n## dialects\n\n- **postgres**. primary. about 95% coverage in my test corpus.\n- **mysql / mariadb**. merged. around 80%. some of the wilder index hints don't parse.\n- **sqlite**. ~80%. `WITHOUT ROWID` works, `STRICT` is partial.\n- **bigquery**. ~60%. struct/array literals parse, scripting blocks (`DECLARE`/`BEGIN`) mostly don't.\n- **snowflake**. new in 0.15.0. `LATERAL FLATTEN`, named arguments (`input =\u003e`), QUALIFY, `:variant.path` accessors. roughly 70%; multi-statement scripts and stored procedures still partial.\n- **tsql / sql server**. new in 0.17.0. `TOP N`, `[bracket]` identifiers, `OUTPUT` clause, `MERGE`. roughly 65%; CTE-heavy stored procedures still need work.\n- **ansi**. baseline, for when you're writing for portability.\n\nnot shipped yet: redshift, oracle. on the roadmap. if you only use those, drift won't help you today.\n\n## rule catalog\n\nrun `drift rules` for the full list. the short version:\n\n| category | count | examples |\n|---|---|---|\n| style | 20 | keyword-case, indent, trailing-whitespace, leading-comma |\n| correctness | 15 | missing-where-update, null-equality, cartesian-join |\n| performance | 8 | select-star, like-leading-wildcard, offset-paging |\n| security | 7 | plaintext-password, grant-all, drop-without-if-exists |\n| portability | 8 | backtick-quote, on-duplicate-key, top-vs-limit |\n| conventions | 8 | snake-case-tables, plural-table-name, index-naming |\n| ambiguity | 5 | mixed-bool, reserved-as-identifier |\n| migration | 7 | add-column-not-null, create-index-no-concurrently, drop-column, rename-column, alter-column-type, drop-table, truncate |\n| schema | 4 | unknown-table, unknown-column, unknown-qualifier, ambiguous-column (require `--schema`) |\n\nper-rule markdown pages live in `docs/rules/`.\n\n## schema-aware lint\n\npoint drift at a schema dump and the schema-aware rules wake up: `unknown-table` and `unknown-column`. silent when `--schema` is omitted, so the default check stays the same.\n\n```sh\n# typical workflow:\npg_dump --schema-only -d mydb \u003e schema.sql\ndrift check --schema schema.sql migrations/\n\n# stdout:\n# migrations/0042.sql:8:14 error  drift.schema.unknown-column\n#   column `emial` does not exist on `users`. did you mean `email`?\n```\n\nmulti-table joins / CTEs / subqueries are intentionally skipped to keep the false-positive budget low — proper binding analysis is on the roadmap. ingests `CREATE TABLE` and `ALTER TABLE ADD COLUMN` from any sql file; pg_dump output is the typical input.\n\n## playground\n\npaste sql, see lint diagnostics in your browser, share a url. same engine as the cli — wasm-compiled, no server, no telemetry. build it locally:\n\n```sh\ncargo install wasm-pack    # one-time\ncd wasm \u0026\u0026 ./build.sh \u0026\u0026 cd playground \u0026\u0026 python3 -m http.server 8000\n```\n\n`wasm/playground/` is a flat static directory; deploy with rsync to any host.\n\n## config\n\ndrop a `drift.toml` at your repo root (drift walks up looking for one):\n\n```toml\n[drift]\ndialect = \"postgres\"\npreset  = \"strict\"       # strict | warn | compat\n\n[rules]\n\"drift.style.keyword-case\"        = { severity = \"warning\", case = \"upper\" }\n\"drift.performance.select-star\"   = \"error\"\n\"drift.correctness.*\"             = \"error\"\n\"drift.portability.*\"             = \"off\"\n\n[format]\nindent       = 2\nmax-line     = 100\nkeyword-case = \"upper\"\n```\n\nseverity: `error` | `warning` | `info` | `off`. wildcards work at the leaf: `drift.correctness.*`.\n\n## vs sqlfluff\n\n|  | drift | sqlfluff |\n|---|---|---|\n| language | rust | python |\n| binary | single static binary | pip install + deps |\n| speed | seconds on a big dbt repo | minutes |\n| memory | ~20mb | much more |\n| dialects | 7 | 12+ |\n| rule count | 80+ | 60+ |\n| jinja / dbt macros | no | yes |\n| plugin system | no | yes |\n| lsp | yes | no |\n\nshort version: sqlfluff is broader, drift is faster. if your pipeline already runs sqlfluff against unrendered jinja, stay there. if it runs against rendered sql, drift's probably what you want.\n\n## vs pgformatter, sqlfmt\n\npgformatter is postgres-only, formatter-only. sqlfmt is postgres + dbt-flavored, opinionated one-true-style. drift lints first, formats second, and spans more dialects. pick sqlfmt if you already live in that formatting style and don't care about lint rules.\n\n## ci\n\n### github actions\n\n```yaml\n- uses: f4rkh4d/drift@main\n  with:\n    paths: 'migrations/'\n    fail-on: error\n```\n\ninputs: `command` (default `check`), `paths`, `fail-on` (`error|warning|info|never`), `config`, `version`, `args`, `diff-base`. pin `@main` to a release tag for stability.\n\nPR-only mode - lint just what the PR adds or changes, not the whole tree:\n\n```yaml\n- uses: actions/checkout@v4\n  with:\n    fetch-depth: 0   # need history to diff against the base branch\n- uses: f4rkh4d/drift@main\n  with:\n    diff-base: origin/${{ github.base_ref }}   # e.g. origin/main\n    fail-on: error\n```\n\n`diff-base` runs drift with `--diff \u003cref\u003e`, which compares the working tree (including untracked files) to that ref. handy on large legacy schemas where a full-tree check is noisy but PR diffs aren't.\n\n### github code scanning (sarif)\n\n`--format sarif` produces SARIF 2.1.0 that github ingests natively. each violation surfaces as an inline annotation on the pull request that opened it.\n\n```yaml\nname: drift\non: [pull_request]\npermissions:\n  contents: read\n  security-events: write\njobs:\n  drift:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: f4rkh4d/drift@main\n        with:\n          paths: 'migrations/'\n          args: '--format sarif'\n          fail-on: never\n        continue-on-error: true\n        id: drift\n      - uses: github/codeql-action/upload-sarif@v3\n        with:\n          sarif_file: drift.sarif\n```\n\n(piping drift's stdout to `drift.sarif` and uploading is the easy way; the action's `args: '--format sarif'` makes drift emit it. `fail-on: never` keeps the job from failing on findings so the upload always runs.)\n\n### pre-commit\n\n```yaml\n# .pre-commit-config.yaml\nrepos:\n  - repo: https://github.com/f4rkh4d/drift\n    rev: v0.14.43\n    hooks:\n      - id: drift-check\n      - id: drift-fix      # apply the safe auto-fixes\n      - id: drift-format   # format in place\n```\n\ndrift's pre-commit hooks invoke the binary on your `$PATH`, so install drift first via brew, the install script, or cargo.\n\nif you don't want the pre-commit framework, drift ships a one-line native hook:\n\n```\ndrift hook install     # writes .git/hooks/pre-commit\ndrift hook uninstall   # removes it\n```\n\nit runs `drift check --staged`, which lints exactly the bytes about to be committed (the index blobs, not the working tree) - unstaged edits on top of a staged file are ignored. bypass with `git commit --no-verify` when you really need to.\n\n## editor setup\n\nvscode: scaffold lives at [`editors/vscode`](editors/vscode). marketplace publish is queued.\n\nhelix. in `languages.toml`:\n\n```toml\n[[language]]\nname = \"sql\"\nlanguage-servers = [\"drift\"]\n\n[language-server.drift]\ncommand = \"drift\"\nargs = [\"lsp\"]\n```\n\nneovim (nvim-lspconfig):\n\n```lua\nrequire('lspconfig.configs').drift = {\n  default_config = {\n    cmd = { 'drift', 'lsp' },\n    filetypes = { 'sql' },\n    root_dir = require('lspconfig.util').root_pattern('drift.toml', '.git'),\n  }\n}\nrequire('lspconfig').drift.setup{}\n```\n\n## silencing one violation without touching drift.toml\n\ndrop a `-- drift:disable[-next] RULE_ID` line comment near the offending SQL. four forms:\n\n```sql\nSELECT * FROM users; -- drift:disable drift.performance.select-star\n                     -- ^ silences just this line, just this rule\n\n-- drift:disable-next drift.correctness.null-equality\nSELECT * FROM users WHERE x = NULL;\n\n-- drift:disable    -- empty rule list = silence every rule on the next sql line\nSELECT * FROM users WHERE x = NULL;\n\n-- drift:disable rule.a, rule.b  -- comma-separated, applies to next line\nSELECT id FROM USERS;\n```\n\na comment that lives on a line with SQL applies to that same line. a comment that lives on a line by itself applies to the next line. an explicit `-next` always means \"next line\".\n\na handful of pre-existing rules report violations with a hard-coded line of 1; until those get fixed, disable comments cannot suppress them. the rules covered in `docs/rules/` use accurate line numbers and are honoured.\n\n## profiling your codebase\n\n`drift profile` runs a check and aggregates the results: which rules fire most, where the noise lives. it ends with a recommended `drift.toml` snippet for the rules that are over 5% of all hits.\n\n```sh\n$ drift profile migrations/\nscanned 200 files in 7 ms\ntotal violations: 6680\nby severity:     6680 warning\n\ntop 5 rules:\n  count  severity    rule\n   6400  warning     drift.style.keyword-case\n    120  warning     drift.style.identifier-case\n     40  warning     drift.style.double-blank-line\n     40  warning     drift.style.indent\n     40  warning     drift.style.line-length\n\nsuggested drift.toml (rules above 5% of all hits, demoted to warning):\n\n  [rules]\n  \"drift.style.keyword-case\" = \"warning\"   # 6400 hits\n```\n\n`--json` for tool integration, `--top N` to widen the list.\n\n## editor: vscode\n\na vscode extension lives at [`editors/vscode`](editors/vscode). it spawns `drift lsp` and surfaces diagnostics, code actions, and format-on-save inside the editor. install drift first via brew or the one-liner. marketplace publish is queued.\n\n## adopting drift on a legacy codebase (baseline)\n\nrunning drift cold against a 10-year-old SQL repo emits thousands of warnings, nobody fixes them, the team turns drift off. the baseline file fixes this:\n\n```sh\n# one-time: take a snapshot of every current violation.\ndrift baseline create migrations/ models/ analytics/\n\n# from now on, only NEW violations surface. the legacy debt is locked.\ndrift check --baseline .drift-baseline.json migrations/ models/ analytics/\n```\n\nhow it works:\n\n- `.drift-baseline.json` records, for each file, the count of violations per rule. line numbers are not part of the key on purpose: code edits shift them.\n- on subsequent `drift check --baseline`, the first N matching violations per (file, rule) are silenced. the (N+1)th and beyond surface.\n- adding a new file with a violation surfaces normally. introducing a NEW rule violation in an old file surfaces normally.\n- the summary line tells you how many were suppressed, so you always know how much debt remains: `... (147 suppressed by baseline)`.\n- to refresh the baseline (e.g. after a cleanup pass): rerun `drift baseline create`.\n\ncommit `.drift-baseline.json` to your repo. inspect with `drift baseline show`.\n\n## faq\n\n**why rust?** because sqlfluff takes 4 minutes on our dbt project. this one takes 3 seconds. the rest is nice-to-have.\n\n**will it replace sqlfluff?** for most projects, yes. if you need the plugin system or custom jinja templating, stay on sqlfluff. that's not what drift is.\n\n**7 dialects? really all 7?** postgres is 95% coverage. mysql and sqlite are 80-ish. snowflake and bigquery are around 65-70. tsql is around 65. ansi is just a baseline. it'll improve.\n\n**why is the binary 8mb if it's written in rust?** sqlparser vendored + clap + the whole serde stack + the lsp bits. every byte paid for. strip + thin-lto gets it under 6mb on mac arm64.\n\n**is fix mode safe?** it only touches whitespace, keyword case, final newline, and trailing semicolons. it won't rewrite a query. i don't trust myself with anything semantic and you shouldn't either.\n\n**does it support dbt?** not as a first-class thing. run dbt compile and point drift at `target/compiled`.\n\n## roadmap\n\n- oracle, redshift dialects\n- dbt project integration (read `dbt_project.yml`, respect `{{ ref() }}`)\n- column type inference (catch `users.id::TEXT` when id is INT)\n- DDL diff mode (compare two schemas for breaking changes)\n- live `--schema-from` postgres connection (skip pg_dump entirely)\n- rule sdk so teams can add their own checks without forking\n- hover + completion in lsp\n\n## license\n\nMIT. see LICENSE.\n\n## reporting bugs\n\nopen an issue at [github.com/f4rkh4d/drift/issues](https://github.com/f4rkh4d/drift/issues) with a minimal reproduction.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ff4rkh4d%2Fdrift","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ff4rkh4d%2Fdrift","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ff4rkh4d%2Fdrift/lists"}