Migration Guide
Use this guide to roll out holywell across an existing codebase with minimal churn.
1) Understand the style model
holywell is intentionally opinionated:
- Optional
.holywellrc.jsonfor operational settings (maxLineLength,maxDepth,maxInputSize,maxTokenCount,dialect,strict,recover) - No style toggles (indent/casing/alignment modes)
- Deterministic output
Plan for one-time diffs when first applying formatting.
2) Known behavior changes
Before you run holywell on existing SQL, understand what will change:
Keywords become uppercase
All SQL keywords are uppercased. select becomes SELECT, inner join becomes INNER JOIN, etc.
ALL-CAPS identifiers become lowercase
ALL-CAPS unquoted identifiers are lowercased to avoid shouting: MYTABLE becomes mytable, USERID becomes userid. Mixed-case identifiers are preserved as-is: MyTable stays MyTable, userId stays userId.
Exception: projection aliases (column aliases in SELECT) are not lowercased, even when ALL-CAPS. SELECT col AS TOTAL keeps TOTAL unchanged.
Quoted identifiers are preserved exactly. "MyTable" stays "MyTable".
Whitespace is normalized
- Trailing whitespace is stripped from every line
- Original indentation is replaced with river-aligned formatting
- Blank lines inside statements are removed
- A trailing newline is added at the end of each statement
Warning: case-sensitive databases
Most databases (PostgreSQL, MySQL, SQL Server) treat unquoted identifiers as case-insensitive, so lowercasing ALL-CAPS identifiers has no effect on query behavior. Mixed-case identifiers are preserved, so this is rarely an issue.
However, if your database or collation is configured to treat unquoted identifiers as case-sensitive (uncommon, but possible in some configurations), the ALL-CAPS lowercasing could change which table or column is referenced. In this case:
- Use quoted identifiers (
"MyTable") for any names that depend on specific casing - Or run
holywell --checkfirst to preview changes before applying--write
What does NOT change
- String literals are preserved exactly (
'Hello World'stays'Hello World') - Numeric literals are preserved
- Quoted identifiers are preserved
- Comments are preserved (though their position may shift with reformatting)
- SQL semantics are not altered -- only whitespace and casing change
3) Start in check-only mode
Run in CI without writing changes:
npx holywell --check "**/*.sql"
If your repo has generated/vendor SQL, exclude it first:
npx holywell --check --ignore "vendor/**" --ignore "generated/**" "**/*.sql"
Or define ignores once in .holywellignore:
vendor/**
generated/**
4) Preview changes before writing
Use --dry-run to see what would change without modifying any files:
npx holywell --dry-run "**/*.sql"
This implies --check --diff, showing a unified diff for each file that would be reformatted.
5) Batch-format in one commit
Create a dedicated formatting commit:
npx holywell --write "**/*.sql"
git add -A
git commit -m "style: apply holywell"
Keeping formatting separate from feature changes makes review and rollback easier.
6) Enforce in CI
After baseline formatting, enforce check mode in CI:
npx holywell --check "**/*.sql"
Useful companion flag for PR logs:
npx holywell --check --list-different "**/*.sql"
7) Add pre-commit guard
Run only on staged SQL files:
npx holywell --check $(git diff --cached --name-only -- '*.sql')
Or auto-fix staged files before commit:
npx holywell --write $(git diff --cached --name-only -- '*.sql')
git add $(git diff --cached --name-only -- '*.sql')
8) Monorepo rollout strategy
For large repos, migrate package-by-package:
- Format one domain/folder.
- Merge.
- Enable CI check for that scope.
- Repeat until full coverage.
9) Handling unsupported syntax
Both the CLI and the formatSQL API default to recovery mode (recover: true). Statements that fail structural parsing are preserved as raw SQL where possible.
To make parse failures block CI:
npx holywell --strict --check "**/*.sql"
For custom tooling, pass recover: false to opt into strict mode.
For a current dialect-by-dialect coverage snapshot, see SQL Dialect Support.