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.
- 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.
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.
Add this to your shard.yml:
dependencies:
tren:
github: sdogruyol/trenThen install dependencies:
shards installEach 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 }}{{ 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 }}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.
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) }}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")- Prefer
{{ value }}over raw interpolation. - Use
{{! ... }}only for trusted SQL fragments. - If your driver supports prepared statements, prefer them for user input.
Tren now fails with clearer parse errors (including file and line) for invalid metadata or malformed placeholders.
Expected metadata format:
-- name: method_name(args)Run tests:
crystal specRun format check:
crystal tool format --check src spec- 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
Built on a TREN from Ankara to Istanbul.