diff --git a/.cursor/rules/architecture.mdc b/.cursor/rules/architecture.mdc new file mode 100644 index 0000000..d5f50bd --- /dev/null +++ b/.cursor/rules/architecture.mdc @@ -0,0 +1,8 @@ +--- +alwaysApply: true +description: Project Overview +--- + +# Project Overview + +This project is a Rust library for formatting SQL queries. It is intended to support all mainstream SQL dialects. The crate exposes one interface for consumers, which is the `format` function exported at the top level of the crate. diff --git a/.cursor/rules/code-testing.mdc b/.cursor/rules/code-testing.mdc new file mode 100644 index 0000000..1514b77 --- /dev/null +++ b/.cursor/rules/code-testing.mdc @@ -0,0 +1,48 @@ +--- +description: Code Testing Best Practices +alwaysApply: false +--- + +# Code Testing Best Practices + +## Test Structure & Organization + +- Write descriptive test names that clearly explain what is being tested +- Follow the AAA pattern: Arrange, Act, Assert +- Group related tests together in logical test suites +- Keep tests independent - each test should be able to run in isolation + +## Test Coverage & Scope + +- Write tests for both happy path and edge cases +- Include tests for error conditions and invalid inputs +- Test boundary conditions (empty strings, null values, maximum/minimum values) +- All tests should have at least one assertion. Always use assertions instead of console logging to validate test results. +- Aim for meaningful coverage, not just high percentage coverage + +## Test Quality + +- Keep tests simple and focused - test one thing at a time +- Make tests deterministic - avoid randomness or time-dependent behavior +- Use clear, descriptive assertions with helpful error messages +- Avoid testing implementation details; focus on behavior and outcomes + +## Test Data & Setup + +- Use minimal test data that clearly demonstrates the test case +- Create reusable test fixtures and helper functions for common setup +- Clean up resources after tests (database records, files, network connections) +- Use test doubles (mocks, stubs, fakes) to isolate units under test + +## Performance & Maintenance + +- Keep tests fast - slow tests discourage frequent running +- Make tests easy to read and maintain +- Remove or update obsolete tests when code changes +- Run tests frequently during development + +## Documentation & Communication + +- Write tests that serve as documentation for expected behavior +- Include comments for complex test logic or non-obvious test scenarios +- Ensure test failures provide clear information about what went wrong diff --git a/.cursor/rules/effective-comments.mdc b/.cursor/rules/effective-comments.mdc new file mode 100644 index 0000000..8cbe6e0 --- /dev/null +++ b/.cursor/rules/effective-comments.mdc @@ -0,0 +1,127 @@ +--- +alwaysApply: true +description: Guidelines for writing meaningful and maintainable code comments +--- + +# Guidelines for writing meaningful and maintainable code comments + +You are an expert in clean code practices, documentation, and code readability. + +## When to Comment + +- Explain WHY, not WHAT the code does +- Document complex business logic or algorithms +- Clarify non-obvious implementations +- Warn about potential gotchas or side effects +- Provide context for future maintainers + +## Comment Types and Usage + +### Good Comments + +```javascript +// Using exponential backoff to avoid overwhelming the API +// during high traffic periods (see incident report #1234) +const delay = Math.min(1000 * Math.pow(2, retryCount), 30000); + +// WORKAROUND: Chrome bug #123456 requires explicit height +// Remove when Chrome 120+ adoption reaches 95% +element.style.height = "auto"; + +// Performance: Caching results reduces API calls by ~70% +// based on production metrics from 2023-Q4 +const cachedResult = cache.get(key); +``` + +### Bad Comments + +```javascript +// BAD: Obvious comment +// Increment counter by 1 +counter++; + +// BAD: Outdated comment +// Send email to user (actually sends SMS now) +await notificationService.send(user); + +// BAD: Commented-out code without context +// user.setStatus('active'); +// user.save(); +``` + +## Documentation Comments + +### JavaScript/TypeScript + +```typescript +/** + * Calculates compound interest with monthly contributions. + * Uses the formula: A = P(1 + r/n)^(nt) + PMT × (((1 + r/n)^(nt) - 1) / (r/n)) + * + * @param principal - Initial investment amount + * @param rate - Annual interest rate (as decimal, e.g., 0.05 for 5%) + * @param time - Investment period in years + * @param contribution - Monthly contribution amount + * @returns Total value after the investment period + * + * @example + * // Calculate 10-year investment with 5% annual return + * const total = calculateCompoundInterest(10000, 0.05, 10, 500); + * console.log(total); // 96,859.57 + */ +function calculateCompoundInterest( + principal: number, + rate: number, + time: number, + contribution: number +): number { + // Implementation +} +``` + +### Python + +```python +def process_transaction(transaction: Transaction) -> ProcessResult: + """ + Process a financial transaction with fraud detection and validation. + + This method performs multiple checks before processing: + 1. Validates transaction format and required fields + 2. Checks against fraud detection rules + 3. Verifies account balance and limits + 4. Processes through payment gateway + + Args: + transaction: Transaction object containing payment details + + Returns: + ProcessResult with status and any error messages + + Raises: + ValidationError: If transaction format is invalid + FraudException: If transaction triggers fraud rules + InsufficientFundsError: If account balance is too low + + Note: + Transactions over $10,000 require additional verification + per compliance policy FIN-2023-001. + """ +``` + +## TODO Comments + +```python +# TODO(john): Implement retry logic for network failures +# TODO(security): Add rate limiting before v2.0 release +# FIXME: Handle edge case when user has multiple accounts +# HACK: Temporary fix until database migration completes +``` + +## Best Practices + +- Keep comments concise and relevant +- Update comments when code changes +- Use consistent comment style across the codebase +- Avoid humor or cultural references that may not translate +- Review comments during code reviews diff --git a/.cursor/rules/github-actions.mdc b/.cursor/rules/github-actions.mdc new file mode 100644 index 0000000..bd81f5d --- /dev/null +++ b/.cursor/rules/github-actions.mdc @@ -0,0 +1,49 @@ +--- +alwaysApply: false +description: Best practices for GitHub Actions workflows +globs: .github/workflows/*.yml, .github/workflows/*.yaml +--- + +# Best practices for GitHub Actions workflows + +You are an expert in CI/CD, GitHub Actions, and DevOps automation. + +## Workflow Structure + +- Use descriptive workflow and job names +- Organize workflows by purpose (ci.yml, deploy.yml, release.yml) +- Implement proper job dependencies with needs +- Use matrix builds for testing multiple versions +- Keep workflows DRY using reusable workflows + +## Security Best Practices + +- Use GitHub Secrets for sensitive data +- Implement least privilege for GITHUB_TOKEN permissions +- Pin action versions to specific commits or tags +- Use OIDC for cloud provider authentication +- Scan for vulnerabilities in dependencies + +## Performance Optimization + +- Cache dependencies appropriately (npm, pip, etc.) +- Use artifacts efficiently between jobs +- Parallelize independent jobs +- Implement conditional workflows to avoid unnecessary runs +- Use concurrency groups to cancel outdated runs + +## Error Handling + +- Implement proper timeout values +- Add retry logic for flaky steps +- Use continue-on-error strategically +- Create informative failure messages +- Set up notifications for critical failures + +## Best Practices + +- Use composite actions for repeated tasks +- Document workflows with comments +- Implement branch protection rules +- Use environments for deployment stages +- Monitor workflow metrics and costs diff --git a/.cursor/rules/project-readme.mdc b/.cursor/rules/project-readme.mdc new file mode 100644 index 0000000..5574ad9 --- /dev/null +++ b/.cursor/rules/project-readme.mdc @@ -0,0 +1,149 @@ +--- +alwaysApply: false +description: Guidelines for creating comprehensive project README files +globs: README.md +--- + +# Guidelines for creating comprehensive project README files + +You are an expert in technical documentation, open source best practices, and developer experience. + +## README Structure + +A well-structured README should include these sections in order: + +1. Project Title and Description +2. Badges (build status, version, license) +3. Key Features +4. Screenshots/Demo (if applicable) +5. Quick Start +6. Installation +7. Usage Examples +8. API Reference (or link to docs) +9. Configuration +10. Contributing +11. License + +## Essential Sections + +### Project Header + +```markdown +# Project Name + +> One-line description of what this project does + +[![Build Status](https://img.shields.io/github/actions/workflow/status/user/repo/ci.yml)](https://github.com/user/repo/actions) +[![npm version](https://img.shields.io/npm/v/package-name)](https://www.npmjs.com/package/package-name) +[![License](https://img.shields.io/github/license/user/repo)](LICENSE) + +Brief paragraph explaining the project's purpose, main features, and why someone would want to use it. +``` + +### Quick Start + +```markdown +## Quick Start + +Get up and running in less than 5 minutes: + +\`\`\`bash +npm install package-name +npm run dev +\`\`\` + +Visit http://localhost:3000 to see the application. +``` + +### Installation + +```markdown +## Installation + +### Prerequisites + +- Node.js 18+ +- PostgreSQL 14+ +- Redis 6.2+ + +### Install from npm + +\`\`\`bash +npm install package-name +\`\`\` + +### Install from source + +\`\`\`bash +git clone https://github.com/user/repo.git +cd repo +npm install +npm run build +\`\`\` +``` + +### Usage Examples + +```markdown +## Usage + +### Basic Example + +\`\`\`javascript +import { Widget } from 'package-name'; + +const widget = new Widget({ +apiKey: 'your-api-key', +theme: 'dark' +}); + +widget.render('#app'); +\`\`\` + +### Advanced Example + +\`\`\`javascript +// Custom configuration with error handling +const widget = new Widget({ +apiKey: process.env.API_KEY, +theme: 'dark', +onError: (error) => { +console.error('Widget error:', error); +} +}); + +// Add custom event handlers +widget.on('ready', () => { +console.log('Widget is ready'); +}); + +widget.render('#app'); +\`\`\` +``` + +## Best Practices + +- Keep the README focused and concise +- Use clear, simple language +- Include code examples that actually work +- Add visuals when they help understanding +- Link to more detailed documentation +- Keep examples up-to-date with the code +- Test your installation instructions regularly + +## Common Mistakes to Avoid + +- Don't assume reader knowledge +- Don't skip the Quick Start section +- Don't use jargon without explanation +- Don't forget to update version numbers +- Don't include sensitive information + +## Formatting Tips + +- Use consistent heading levels +- Include a table of contents for long READMEs +- Use code blocks with language highlighting +- Add alt text to images +- Use tables for comparing options +- Include emoji sparingly and purposefully diff --git a/.cursor/rules/rust/rust-best-practices.mdc b/.cursor/rules/rust/rust-best-practices.mdc new file mode 100644 index 0000000..aaf2865 --- /dev/null +++ b/.cursor/rules/rust/rust-best-practices.mdc @@ -0,0 +1,69 @@ +--- +alwaysApply: false +description: Best practices and coding standards for Rust development +globs: *.rs +--- + +# Rust Best Practices + +## Key Principles + +- **Memory Safety**: Leverage Rust's ownership system to prevent memory-related bugs +- **Zero-Cost Abstractions**: Use high-level constructs without runtime overhead +- **Fearless Concurrency**: Write safe concurrent code using Rust's type system +- **Error Handling**: Use `Result` and `Option` types for robust error handling +- **Performance**: Write efficient code with predictable performance characteristics + +## Coding Standards + +- Use `cargo fmt` for consistent code formatting +- Run `cargo clippy` to catch common mistakes and improve code quality +- Write comprehensive tests with `cargo test` +- Use descriptive variable and function names +- Prefer immutable bindings by default (`let` over `let mut`) +- Use `snake_case` for variables and functions, `PascalCase` for types +- Keep functions small and focused on a single responsibility + +## Ownership and Borrowing + +- Understand the three rules of ownership: each value has one owner, only one owner at a time, value is dropped when owner goes out of scope +- Use borrowing (`&`) instead of moving when you don't need ownership +- Prefer borrowing over cloning for performance +- Use `Rc>` or `Arc>` sparingly for shared ownership +- Leverage lifetime annotations to express relationships between references + +## Error Handling + +- Use `Result` for recoverable errors +- Use `Option` for nullable values instead of null pointers +- Prefer `?` operator for error propagation +- Create custom error types using `thiserror` crate +- Use `anyhow` for application-level error handling +- Avoid `unwrap()` and `expect()` in production code + +## Dependencies + +- **Core Libraries**: `serde`, `tokio`, `clap`, `thiserror`, `anyhow` +- **Development**: `cargo-watch`, `cargo-audit`, `cargo-tarpaulin` +- **Testing**: Built-in test framework, `proptest` for property testing +- **Linting**: `clippy`, `rustfmt` + +## Performance Guidelines + +- Use `Vec` for dynamic arrays, prefer `&[T]` for function parameters +- Use `String` for owned strings, `&str` for string slices +- Prefer iterators over explicit loops for better performance and readability +- Use `collect()` judiciously - consider streaming operations +- Profile with `cargo bench` and `perf` tools +- Avoid unnecessary allocations and clones + +## Conventions + +1. **Project Structure**: Use `src/lib.rs` for libraries, `src/main.rs` for binaries +2. **Module Organization**: One module per file, use `mod.rs` for module directories +3. **Documentation**: Write doc comments with `///` for public APIs +4. **Testing**: Place unit tests in the same file using `#[cfg(test)]` +5. **Integration Tests**: Place in `tests/` directory +6. **Examples**: Provide runnable examples in `examples/` directory +7. **Cargo.toml**: Keep dependencies minimal and well-documented +8. **Version Pinning**: Use semantic versioning and appropriate version constraints diff --git a/.cursor/rules/rust/rust-ownership-borrowing.mdc b/.cursor/rules/rust/rust-ownership-borrowing.mdc new file mode 100644 index 0000000..49786e8 --- /dev/null +++ b/.cursor/rules/rust/rust-ownership-borrowing.mdc @@ -0,0 +1,110 @@ +--- +alwaysApply: false +description: Guidelines for effective ownership and borrowing patterns in Rust +globs: *.rs +--- + +# Rust Ownership and Borrowing Guidelines + +## Ownership Principles + +- **Single Owner**: Each value has exactly one owner at any given time +- **Move Semantics**: Values are moved by default, not copied +- **RAII**: Resources are automatically cleaned up when owners go out of scope +- **No Garbage Collector**: Memory management happens at compile time + +## Borrowing Best Practices + +### Immutable Borrowing + +- Use `&T` for read-only access to data +- Multiple immutable borrows are allowed simultaneously +- Prefer immutable borrows when you don't need to modify data +- Pass `&str` instead of `String` for function parameters when possible + +### Mutable Borrowing + +- Use `&mut T` for exclusive write access +- Only one mutable borrow allowed at a time +- No other borrows (mutable or immutable) can coexist with a mutable borrow +- Keep mutable borrow scopes as small as possible + +## Common Patterns + +### Function Parameters + +```rust +// Prefer borrowing over ownership +fn process_data(data: &[u8]) -> Result<(), Error> { /* ... */ } + +// Use owned types when you need to store or transform +fn store_data(data: String) -> Result<(), Error> { /* ... */ } +``` + +### Return Values + +- Return owned types when creating new data +- Return borrowed types when referencing existing data +- Use lifetime parameters when returning references + +### Collections + +- Use `Vec` for owned collections +- Use `&[T]` for borrowed slices +- Use `HashMap` for owned key-value pairs + +## Lifetime Guidelines + +### Explicit Lifetimes + +- Use lifetime parameters when compiler cannot infer relationships +- Keep lifetime annotations minimal and clear +- Use descriptive lifetime names (`'a`, `'input`, `'output`) + +### Lifetime Elision + +- Rely on lifetime elision rules when possible +- Understand when explicit lifetimes are required +- Single input lifetime is assigned to all outputs + +## Anti-Patterns to Avoid + +- **Excessive Cloning**: Don't clone unnecessarily to avoid borrow checker +- **Reference Cycles**: Avoid `Rc>` cycles that cause memory leaks +- **Fighting the Borrow Checker**: Restructure code instead of using unsafe +- **Premature Arc/Mutex**: Use shared ownership only when necessary + +## Advanced Patterns + +### Interior Mutability + +- Use `Cell` for `Copy` types that need interior mutability +- Use `RefCell` for runtime borrow checking +- Use `Mutex` for thread-safe interior mutability + +### Smart Pointers + +- `Box` for heap allocation and owned data +- `Rc` for reference counting in single-threaded contexts +- `Arc` for atomic reference counting in multi-threaded contexts + +## Common Solutions + +### Multiple Mutable References + +- Split borrows using index access or separate methods +- Restructure data to avoid borrowing conflicts +- Use interior mutability patterns when appropriate + +### Lifetime Issues + +- Avoid returning references to local variables +- Use owned types when lifetime relationships are complex +- Consider using lifetime bounds on generic types + +## Testing Ownership Patterns + +- Write tests that verify memory safety properties +- Test edge cases with borrowing and lifetimes +- Use `cargo miri` to catch undefined behavior +- Profile memory usage patterns in complex applications diff --git a/.cursor/rules/solid-principles.mdc b/.cursor/rules/solid-principles.mdc new file mode 100644 index 0000000..6cdd13c --- /dev/null +++ b/.cursor/rules/solid-principles.mdc @@ -0,0 +1,85 @@ +--- +description: SOLID Design Principles +alwaysApply: true +--- + +# SOLID Design Principles + +When generating, reviewing, or modifying code, follow these guidelines to ensure adherence to SOLID principles: + +## 1. Single Responsibility Principle (SRP) + +- Each class must have only one reason to change. +- Limit class scope to a single functional area or abstraction level. +- When a class exceeds 100-150 lines, consider if it has multiple responsibilities. +- Separate cross-cutting concerns (logging, validation, error handling) from business logic. +- Create dedicated classes for distinct operations like data access, business rules, and UI. +- Method names should clearly indicate their singular purpose. +- If a method description requires "and" or "or", it likely violates SRP. +- Prioritize composition over inheritance when combining behaviors. + +## 2. Open/Closed Principle (OCP) + +- Design classes to be extended without modification. +- Use abstract classes and interfaces to define stable contracts. +- Implement extension points for anticipated variations. +- Favor strategy patterns over conditional logic. +- Use configuration and dependency injection to support behavior changes. +- Avoid switch/if-else chains based on type checking. +- Provide hooks for customization in frameworks and libraries. +- Design with polymorphism as the primary mechanism for extending functionality. + +## 3. Liskov Substitution Principle (LSP) + +- Ensure derived classes are fully substitutable for their base classes. +- Maintain all invariants of the base class in derived classes. +- Never throw exceptions from methods that don't specify them in base classes. +- Don't strengthen preconditions in subclasses. +- Don't weaken postconditions in subclasses. +- Never override methods with implementations that do nothing or throw exceptions. +- Avoid type checking or downcasting, which may indicate LSP violations. +- Prefer composition over inheritance when complete substitutability can't be achieved. + +## 4. Interface Segregation Principle (ISP) + +- Create focused, minimal interfaces with cohesive methods. +- Split large interfaces into smaller, more specific ones. +- Design interfaces around client needs, not implementation convenience. +- Avoid "fat" interfaces that force clients to depend on methods they don't use. +- Use role interfaces that represent behaviors rather than object types. +- Implement multiple small interfaces rather than a single general-purpose one. +- Consider interface composition to build up complex behaviors. +- Remove any methods from interfaces that are only used by a subset of implementing classes. + +## 5. Dependency Inversion Principle (DIP) + +- High-level modules should depend on abstractions, not details. +- Make all dependencies explicit, ideally through constructor parameters. +- Use dependency injection to provide implementations. +- Program to interfaces, not concrete classes. +- Place abstractions in a separate package/namespace from implementations. +- Avoid direct instantiation of service classes with 'new' in business logic. +- Create abstraction boundaries at architectural layer transitions. +- Define interfaces owned by the client, not the implementation. + +## Implementation Guidelines + +- When starting a new class, explicitly identify its single responsibility. +- Document extension points and expected subclassing behavior. +- Write interface contracts with clear expectations and invariants. +- Question any class that depends on many concrete implementations. +- Use factories, dependency injection, or service locators to manage dependencies. +- Review inheritance hierarchies to ensure LSP compliance. +- Regularly refactor toward SOLID, especially when extending functionality. +- Use design patterns (Strategy, Decorator, Factory, Observer, etc.) to facilitate SOLID adherence. + +## Warning Signs + +- God classes that do "everything" +- Methods with boolean parameters that radically change behavior +- Deep inheritance hierarchies +- Classes that need to know about implementation details of their dependencies +- Circular dependencies between modules +- High coupling between unrelated components +- Classes that grow rapidly in size with new features +- Methods with many parameters diff --git a/README.md b/README.md index c18b63a..37ec2cb 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,207 @@ # sqlformat -[![Build Status](https://github.com/shssoichiro/sqlformat-rs/workflows/sqlformat/badge.svg)](https://github.com/shssoichiro/sqlformat-rs/actions?query=branch%3Amaster) -[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) [![Version](https://img.shields.io/crates/v/sqlformat.svg)](https://crates.io/crates/sqlformat) [![Docs](https://docs.rs/sqlformat/badge.svg)](https://docs.rs/sqlformat) +[![Build Status](https://github.com/shssoichiro/sqlformat-rs/workflows/sqlformat/badge.svg)](https://github.com/shssoichiro/sqlformat-rs/actions?query=branch%3Amaster) +[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) +[![Codecov](https://img.shields.io/codecov/c/github/shssoichiro/sqlformat-rs)](https://app.codecov.io/gh/shssoichiro/sqlformat-rs) + +Format SQL strings into readable, consistently styled output. `sqlformat` is a pure-Rust library designed to pretty-print SQL from a variety of mainstream dialects, ideal for logging, debugging, tests, or developer tools. + +This crate is a Rust port of [sql-formatter-plus](https://github.com/kufii/sql-formatter-plus). There is currently no binary; the crate is intended to be used as a library. + +## Key features + +- **Broad SQL support**: Common constructs from PostgreSQL, MySQL/MariaDB, SQLite, SQL Server, and Oracle (DDL, DML, CTEs, CASE, JOINs, window functions, operators, type casts, etc.). +- **Configurable style**: Indentation (spaces or tabs), upper/lower/preserve keyword case, control lines between statements. +- **Inline controls**: Keep short blocks or argument lists inline when they fit; split when they don’t. +- **Parameter interpolation**: Supports `?`, `?1`, `$1`, `$name`, `:name`, `@name`, and bracketed variants via `QueryParams`. +- **Comment-aware**: Respects line/block comments; supports in-query toggles to temporarily disable formatting. +- **Safe Rust**: `#![forbid(unsafe_code)]`. + +## Quick start + +```rust +use sqlformat::{format, FormatOptions, Indent, QueryParams}; + +fn main() { + let sql = "SELECT id, name FROM users WHERE created_at > NOW();"; + let options = FormatOptions::default(); + let formatted = format(sql, &QueryParams::None, &options); + println!("{}", formatted); +} +``` + +Output: + +```text +SELECT + id, + name +FROM + users +WHERE + created_at > NOW(); +``` + +## Installation + +Add via Cargo: + +```bash +cargo add sqlformat +``` + +Or manually in `Cargo.toml`: + +```toml +[dependencies] +sqlformat = "*" +``` + +Minimum Supported Rust Version (MSRV): `1.84`. + +## Usage examples + +### Basic formatting + +```rust +use sqlformat::{format, FormatOptions, QueryParams}; + +let sql = "SELECT count(*), col FROM t WHERE a = 1 AND b = 2;"; +let out = format(sql, &QueryParams::None, &FormatOptions::default()); +``` + +### Indentation + +```rust +use sqlformat::{format, FormatOptions, Indent, QueryParams}; + +let options = FormatOptions { indent: Indent::Spaces(4), ..Default::default() }; +let out = format("SELECT a, b FROM t;", &QueryParams::None, &options); + +let options = FormatOptions { indent: Indent::Tabs, ..Default::default() }; +let out = format("SELECT a, b FROM t;", &QueryParams::None, &options); +``` + +### Keyword case conversion + +```rust +use sqlformat::{format, FormatOptions, QueryParams}; + +// Uppercase reserved keywords +let options = FormatOptions { uppercase: Some(true), ..Default::default() }; +let out = format("select distinct * from foo where bar = 1", &QueryParams::None, &options); + +// Lowercase reserved keywords +let options = FormatOptions { uppercase: Some(false), ..Default::default() }; +let out = format("SELECT DISTINCT * FROM FOO WHERE BAR = 1", &QueryParams::None, &options); + +// Preserve case with exceptions +let options = FormatOptions { + uppercase: Some(true), + ignore_case_convert: Some(vec!["from", "where"]), + ..Default::default() +}; +let out = format("select * from foo where bar = 1", &QueryParams::None, &options); +``` + +### Inline/compact formatting + +Control how aggressively short blocks and argument lists are kept on one line. + +```rust +use sqlformat::{format, FormatOptions, QueryParams}; + +let options = FormatOptions { + inline: false, // when true, forces single-line output + max_inline_block: 50, // characters allowed to keep a parenthesized block inline + max_inline_arguments: Some(40), + max_inline_top_level: Some(40), + ..Default::default() +}; +let out = format("SELECT a, b, c, d, e, f, g, h FROM t;", &QueryParams::None, &options); +``` + +### JOIN layout + +Treat any JOIN as a top-level keyword (affects line breaks): + +```rust +use sqlformat::{format, FormatOptions, QueryParams}; + +let options = FormatOptions { joins_as_top_level: true, ..Default::default() }; +let out = format("SELECT * FROM a INNER JOIN b ON a.id = b.a_id", &QueryParams::None, &options); +``` + +### Parameter interpolation + +`sqlformat` can substitute placeholders using `QueryParams`: + +```rust +use sqlformat::{format, FormatOptions, QueryParams}; + +// Numbered / positional (e.g., ?, ?1, $1) +let sql = "SELECT ?1, ?, $2;"; +let params = QueryParams::Indexed(vec!["first".to_string(), "second".to_string(), "third".to_string()]); +let out = format(sql, ¶ms, &FormatOptions::default()); + +// Named (e.g., $name, :name, @name, :\"weird name\") +let sql = "SELECT $hash, :name, @`var name`;"; +let params = QueryParams::Named(vec![ + ("hash".to_string(), "hash value".to_string()), + ("name".to_string(), "Alice".to_string()), + ("var name".to_string(), "Bob".to_string()), +]); +let out = format(sql, ¶ms, &FormatOptions::default()); +``` + +### Controlling blank lines between statements + +```rust +use sqlformat::{format, FormatOptions, QueryParams}; + +let options = FormatOptions { lines_between_queries: 2, ..Default::default() }; +let out = format("SELECT 1; SELECT 2;", &QueryParams::None, &options); +``` + +### Temporarily disabling the formatter + +You can turn formatting off/on using SQL comments. This is helpful when you want to preserve a very specific layout. + +```sql +-- fmt: off +SELECT * FROM t WHERE a=1 AND b=2; -- preserved as-is +-- fmt: on + +/* fmt: off */ SELECT 1 + 2; /* fmt: on */ +``` + +## Configuration reference + +The formatter is configured through `FormatOptions`. See the full API on the docs site for list of options. + +## API reference + +- Crate docs: [`docs.rs/sqlformat`](https://docs.rs/sqlformat) +- Primary entry point: `format(query: &str, params: &QueryParams, options: &FormatOptions) -> String` + +## Contributing + +Contributions are welcome! + +- Run tests: `cargo test` +- Run benchmarks (optional): `cargo bench` + +Please open issues and pull requests with clear descriptions and examples. Bug reports that include an input SQL snippet, your `FormatOptions`, and the actual vs. expected output are especially helpful. + +## License + +Dual-licensed under either of: + +- MIT License (`LICENSE-MIT`) +- Apache License, Version 2.0 (`LICENSE-APACHE`) -This crate is a port of [sql-formatter-plus](https://github.com/kufii/sql-formatter-plus) -written in Rust. It is intended to be usable as a pure-Rust library -for formatting SQL queries. +## Acknowledgements -There is currently no binary interface. -This crate was written for formatting queries to logs -within `sqlx`, but it may be useful to other crates -in the Rust ecosystem. +Based on the excellent work in [`sql-formatter-plus`](https://github.com/kufii/sql-formatter-plus).