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:
-- name: get_users(name : String, age : Int32)
SELECT * FROM users WHERE name = '{{ name }}' AND age = {{ age }}
Load and call it:
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:
dependencies:
tren:
github: sdogruyol/tren
Then install dependencies:
shards install
SQL File Format
Each query must start with metadata:
-- name: method_name(arg : Type, ...)
After that line, write the SQL body:
-- name: find_user(id : Int32)
SELECT * FROM users WHERE id = {{ id }}
Parameter Rules
{{ value }}: escaped parameter (default, safer).{{! value }}: raw parameter (not escaped).
-- 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:
-- 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:
-- 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:
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:
-- name: method_name(args)
Development
Run tests:
crystal spec
Run format check:
crystal tool format --check src spec
Contributing
- Fork it (github.com/sdogruyol/tren/fork)
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am "Add some feature") - Push to the branch (
git push origin my-new-feature) - Open a Pull Request
Contributors
Built on a TREN from Ankara to Istanbul.