Installation

# Add this to your shard.yml
dependencies:
  tren:
    github: sdogruyol/tren
    version: ~> 2.0.0

Then run:

shards install

shard.yml

Crystal
no constraint declared
License
MIT
Authors
Fatih Kadir Akın, Serdar Doğruyol

Dependencies

This version declares no dependencies.

README

![Tren Logo](logo.svg)

[![Build](https://github.com/sdogruyol/tren/actions/workflows/ci.yml/badge.svg)](https://github.com/sdogruyol/tren/actions/workflows/ci.yml)

# Tren

Use SQL files as first-class Crystal methods.

Tren reads SQL files at compile time and generates native Crystal methods from them.  
You keep SQL in `.sql` files, then call it like regular Crystal code.

## Why Tren?

- Write SQL where it belongs: in SQL files.
- Keep typed method signatures in metadata.
- Get Crystal overload checks at compile time.
- Compose SQL snippets without repeating yourself.

## 30-Second Example

Create `queries/users.sql`:

```sql
-- name: get_users(name : String, age : Int32)
SELECT * FROM users WHERE name = '{{ name }}' AND age = {{ age }}
```

Load and call it:

```crystal
require "tren"

Tren.load("./queries/*.sql")

sql = get_users("john", 42)
# => "SELECT * FROM users WHERE name = 'john' AND age = 42"
```

That method (`get_users`) is generated by Tren during compilation.

## Installation

Add this to your `shard.yml`:

```yaml
dependencies:
  tren:
    github: sdogruyol/tren
```

Then install dependencies:

```bash
shards install
```

## SQL File Format

Each query must start with metadata:

```sql
-- name: method_name(arg : Type, ...)
```

After that line, write the SQL body:

```sql
-- name: find_user(id : Int32)
SELECT * FROM users WHERE id = {{ id }}
```

## Parameter Rules

- `{{ value }}`: escaped parameter (default, safer).
- `{{! value }}`: raw parameter (not escaped).

```sql
-- name: by_name(name : String)
SELECT * FROM users WHERE name = '{{ name }}'

-- name: with_clause(clause : String)
SELECT * FROM users {{! clause }}
```

## Overloading

Multiple SQL entries can share the same method name with different signatures:

```sql
-- name: get_users(name : String, surname : String)
SELECT * FROM users WHERE name = '{{ name }}' AND surname = '{{ surname }}'

-- name: get_users(name : String, age : Int32)
SELECT * FROM users WHERE name = '{{ name }}' AND age = {{ age }}
```

Crystal resolves overloads and reports errors if arguments do not match.

## Composing Queries

You can reuse generated SQL methods to build larger queries:

```sql
-- name: filter_user(name : String, surname : String)
WHERE name = '{{ name }}' AND surname = '{{ surname }}'

-- name: get_users(name : String, surname : String)
SELECT * FROM users {{! filter_user(name, surname) }}
```

## Escaping Behavior

String parameters are escaped by default.  
Non-string values are passed through as-is.

You can customize the escape behavior:

```crystal
Tren.escape_character = "\\"
# => escapes both quotes and backslashes with a backslash prefix (default)

Tren.escape_character = "\\'"
# => PostgreSQL-style single-quote escaping ("I'm" => "I''m")
```

## Security Notes

- Prefer `{{ value }}` over raw interpolation.
- Use `{{! ... }}` only for trusted SQL fragments.
- If your driver supports prepared statements, prefer them for user input.

## Error Messages

Tren now fails with clearer parse errors (including file and line) for invalid metadata or malformed placeholders.

Expected metadata format:

```sql
-- name: method_name(args)
```

## Development

Run tests:

```bash
crystal spec
```

Run format check:

```bash
crystal tool format --check src spec
```

## Contributing

1. Fork it ([github.com/sdogruyol/tren/fork](https://github.com/sdogruyol/tren/fork))
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am "Add some feature"`)
4. Push to the branch (`git push origin my-new-feature`)
5. Open a Pull Request

## Contributors

- [f](https://github.com/f) Fatih Kadir Akın - creator, maintainer
- [sdogruyol](https://github.com/sdogruyol) Serdar Doğruyol - creator, maintainer

_Built on a TREN from Ankara to Istanbul._