Skip to content

nao1215/sqlode

Repository files navigation

sqlode

Hex Hex Downloads CI license

sqlode reads SQL schema and query files and generates typed Gleam code. It follows the sqlc workflow: write SQL, run the generator, call the generated functions.

sqlode is inspired by sqlc but is not a drop-in replacement. Macros use the sqlode.* prefix — sqlc.* is not accepted.

Supported engines (raw and native): PostgreSQL (pog), MySQL 8.0 (shork), SQLite (sqlight). The per-engine support matrix lives in doc/capabilities.md.

First time here? doc/tutorials/getting-started-sqlite.md walks through a SQLite project end to end, and examples/sqlite-basic/ is the runnable version of the same tutorial. The rest of this README is reference material.

Targets

  • CLI (sqlode generate etc.): BEAM only (escript). The supported drivers — pog, shork, sqlight — are BEAM-native, so this is intentional.
  • sqlode/runtime: cross-target. Pure value transformation (prepare(query, params)#(String, List(Value))) with no FFI. Importable from a JavaScript-target Gleam app.
  • Generated modules: cross-target. They depend only on sqlode/runtime and the driver chosen by the consumer; the JS-target story is gated by whether a JavaScript-callable driver is available downstream.

Getting started

Install

sqlode ships as an Erlang escript, so most paths need Erlang/OTP on the host. Option D (Docker) bundles Erlang, and option E (mise) manages both the escript and Erlang together.

Whichever install path you pick, your Gleam project still needs gleam add sqlode because generated code imports sqlode/runtime.

A. One-line installer

curl -fsSL https://raw.githubusercontent.com/nao1215/sqlode/main/scripts/install.sh | sh

Writes the latest release escript to $HOME/.local/bin/sqlode and warns if Erlang/OTP is missing. To review the script first, download it, read it, then sh install.sh.

Environment variables:

  • SQLODE_VERSION=v0.1.0 pins a release tag instead of latest.
  • SQLODE_INSTALL_DIR=/path/to/bin installs elsewhere. System paths need sudo.

If $HOME/.local/bin is not on your PATH, add it:

export PATH="$HOME/.local/bin:$PATH"

B. Manual escript download

Grab the escript from GitHub Releases and put it on your PATH:

chmod +x sqlode
./sqlode generate --config=sqlode.yaml

C. Run via Gleam

gleam add sqlode
gleam run -m sqlode -- generate

D. Docker (no Erlang install)

docker run --rm -v "$PWD:/work" ghcr.io/nao1215/sqlode:latest init --engine=sqlite
docker run --rm -v "$PWD:/work" ghcr.io/nao1215/sqlode:latest generate

The container's working directory is /work, so mounting your project there lets init / generate / verify write into the host. Swap :latest for a version tag (:0.10.0) to pin a release. The :latest tag appears once the docker workflow has run on main; before that, docker build -t sqlode . at the repo root produces the same image.

E. mise (recommended for Gleam projects)

If you already use mise for managing Gleam and Erlang:

mise plugin add sqlode https://github.com/nao1215/sqlode.git#mise-plugin
mise install sqlode@latest

This installs the escript and manages versions alongside your Gleam/Erlang toolchain. Pin a version in .mise.toml:

[tools]
sqlode = "0.12.0"

mise handles PATH automatically — no manual exports needed.

Initialize config

# standalone CLI
sqlode init

# or via Gleam
gleam run -m sqlode -- init

This creates sqlode.yaml along with stub files db/schema.sql and db/query.sql:

version: "2"
sql:
  - schema: "db/schema.sql"
    queries: "db/query.sql"
    engine: "postgresql"
    gen:
      gleam:
        out: "src/db"
        runtime: "raw"

schema and queries each take a single path, a list of paths, or a directory (sqlode then picks up every .sql in it). An optional name on each sql block shows up in diagnostics when several blocks are configured.

The schema parser accepts either a schema snapshot or a migration history (additive and destructive DDL both work). The full supported-statement list is in Schema DDL scope.

Write SQL

Schema (db/schema.sql):

CREATE TABLE authors (
  id BIGSERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  bio TEXT,
  created_at TIMESTAMP NOT NULL
);

Queries (db/query.sql):

-- name: GetAuthor :one
SELECT id, name, bio
FROM authors
WHERE id = $1;

-- name: ListAuthors :many
SELECT id, name
FROM authors
ORDER BY name;

-- name: CreateAuthor :exec
INSERT INTO authors (name, bio)
VALUES (sqlode.arg(author_name), sqlode.narg(bio));

Generate

# standalone CLI
sqlode generate

# or via Gleam
gleam run -m sqlode -- generate

This writes params.gleam and queries.gleam under the configured output directory. models.gleam is added when the schema defines tables or when a :one / :many query returns result columns.

Generated code

params.gleam

pub type GetAuthorParams {
  GetAuthorParams(id: Int)
}

pub fn get_author_values(params: GetAuthorParams) -> List(Value) {
  [runtime.int(params.id)]
}

pub type CreateAuthorParams {
  CreateAuthorParams(author_name: String, bio: Option(String))
}

models.gleam

One record per table in the schema, plus row types for queries that return results. When a query's columns exactly match a table (same columns, types, nullability, order), sqlode emits an alias instead of a duplicate record.

// Table record (singularized), reusable across queries
pub type Author {
  Author(id: Int, name: String, bio: Option(String), created_at: String)
}

// Exact match: alias
pub type GetAuthorRow =
  Author

// Partial match: separate row type
pub type ListAuthorsRow {
  ListAuthorsRow(id: Int, name: String)
}

queries.gleam

Each query is a RawQuery(params). all() / QueryInfo enumerate queries without type parameters.

pub type QueryInfo {
  QueryInfo(name: String, sql: String, command: runtime.QueryCommand, param_count: Int)
}

pub fn all() -> List(QueryInfo) { ... }

pub fn get_author() -> runtime.RawQuery(params.GetAuthorParams) { ... }
pub fn list_authors() -> runtime.RawQuery(Nil) { ... }
pub fn create_author() -> runtime.RawQuery(params.CreateAuthorParams) { ... }

For the common case, call the generated prepare_* helper. It builds the params record and returns the (sql, values) tuple that Gleam database drivers accept directly:

let #(sql, values) = queries.prepare_get_author(id: 1)
// sql: "... WHERE id = $1"

sqlode.slice works the same way — pass a List, the SQL expands to the right number of placeholders:

let #(sql, values) = queries.prepare_get_authors_by_ids(ids: [1, 2, 3])
// sql: "... WHERE id IN ($1, $2, $3)"

If you need the RawQuery descriptor (caching, batching, custom wrappers), the low-level shape is still there:

let q = queries.get_author()
let #(sql, values) = runtime.prepare(q, params.GetAuthorParams(id: 1))

The placeholder dialect ($1 / ?) is baked into the RawQuery, so runtime.prepare does not take it as an argument.

Runtime modes

The runtime option controls what code sqlode emits.

Mode Generated files DB driver Use case
raw queries, params, models You run the queries yourself
native queries, params, models, adapter pog / sqlight / shork Full adapter: bind params, decode rows

sqlode itself must be a runtime dependency (not just dev) because the generated code imports sqlode/runtime. native mode also needs a driver:

gleam add sqlode
gleam add pog       # PostgreSQL native
gleam add sqlight   # SQLite native
gleam add shork     # MySQL native

Self-contained generation (vendor_runtime)

gen.gleam.vendor_runtime: true copies the sqlode/runtime module into the output directory as runtime.gleam and rewrites the generated imports to match. The generated package then only needs sqlode as a dev dependency. Native adapters still need their driver.

gen:
  gleam:
    out: "src/db"
    runtime: "raw"
    vendor_runtime: true

Shared-runtime is smaller and updates with gleam update sqlode; vendored is self-contained but has to be regenerated to pick up runtime changes.

Adapter generation

With runtime: "native", sqlode generates an adapter that wraps pog (PostgreSQL), sqlight (SQLite), or shork (MySQL 8.0). The three adapters have the same shape; MySQL routes :execrows through SELECT ROW_COUNT() and :execlastid through SELECT LAST_INSERT_ID() under the hood.

Out of scope today: MariaDB is not separately validated — the mysql engine targets MySQL 8.0. :execresult is rejected on every native target; use :exec, :execrows, or :execlastid. BLOB / BINARY round-trip through shork_ffi.coerce (the same identity FFI shork's value constructors use), so no shork API extension is needed.

gen:
  gleam:
    out: "src/db"
    runtime: "native"

An adapter function handles parameter binding, execution, and decoding:

// pog_adapter.gleam (generated)
pub fn get_author(db: pog.Connection, p: params.GetAuthorParams)
  -> Result(Option(models.GetAuthorRow), pog.QueryError)

Using the generated adapter

SQLite example

import db/params
import db/sqlight_adapter
import gleam/io
import gleam/option
import sqlight

pub fn main() {
  let assert Ok(db) = sqlight.open(":memory:")

  // Create table
  let assert Ok(_) = sqlight.exec(
    "CREATE TABLE authors (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      bio TEXT
    );",
    db,
  )

  // :exec — returns Result(Nil, sqlight.Error)
  let assert Ok(_) = sqlight_adapter.create_author(
    db,
    params.CreateAuthorParams(
      author_name: "Alice",
      bio: option.Some("Author bio"),
    ),
  )

  // :one — returns Result(Option(Row), sqlight.Error)
  let assert Ok(option.Some(author)) = sqlight_adapter.get_author(
    db,
    params.GetAuthorParams(id: 1),
  )
  io.debug(author.name)  // "Alice"

  // :many — returns Result(List(Row), sqlight.Error)
  let assert Ok(authors) = sqlight_adapter.list_authors(db)
  io.debug(authors)  // [ListAuthorsRow(id: 1, name: "Alice")]
}

PostgreSQL example

import db/params
import db/pog_adapter
import gleam/io
import gleam/option
import pog

pub fn main() {
  let db = pog.default_config()
    |> pog.host("localhost")
    |> pog.database("mydb")
    |> pog.connect()

  // :one — returns Result(Option(Row), pog.QueryError)
  let assert Ok(option.Some(author)) = pog_adapter.get_author(
    db,
    params.GetAuthorParams(id: 1),
  )
  io.debug(author.name)

  // :many — returns Result(List(Row), pog.QueryError)
  let assert Ok(authors) = pog_adapter.list_authors(db)
  io.debug(authors)
}

MySQL examples

MySQL works in both modes. raw returns the prepared SQL plus encoded params; native generates a mysql_adapter that wraps shork.

MySQL raw mode
sql:
  - engine: "mysql"
    schema: "db/schema.sql"
    queries: "db/query.sql"
    gen:
      gleam:
        out: "src/db"
        runtime: "raw"
-- name: GetAuthor :one
SELECT id, email, display_name
FROM authors
WHERE id = ?;
import db/params
import db/queries
import sqlode/runtime

pub fn fetch(id: Int) -> #(String, List(runtime.Value)) {
  runtime.prepare(queries.get_author(), params.GetAuthorParams(id:))
}
MySQL native mode
sql:
  - engine: "mysql"
    schema: "db/schema.sql"
    queries: "db/query.sql"
    gen:
      gleam:
        out: "src/db"
        runtime: "native"
import db/mysql_adapter
import db/params
import gleam/option
import shork

pub fn main() {
  let assert Ok(db) = shork.connect(shork.default_config())

  // :execlastid — returns the AUTO_INCREMENT id of the new row.
  let assert Ok(id) =
    mysql_adapter.create_author(
      db,
      params.CreateAuthorParams(
        email: "[email protected]",
        display_name: "Alice",
        bio: option.None,
        is_active: True,
        avatar: option.None,
      ),
    )

  // :one — returns Result(Option(Row), shork.QueryError).
  let assert Ok(option.Some(author)) =
    mysql_adapter.get_author(db, params.GetAuthorParams(id:))
  let _ = author.display_name
  Nil
}

Return types by annotation

Annotation sqlight return type pog return type shork return type
:one Result(Option(Row), sqlight.Error) Result(Option(Row), pog.QueryError) Result(Option(Row), shork.QueryError)
:many Result(List(Row), sqlight.Error) Result(List(Row), pog.QueryError) Result(List(Row), shork.QueryError)
:exec Result(Nil, sqlight.Error) Result(Nil, pog.QueryError) Result(Nil, shork.QueryError)
:execrows Result(Int, sqlight.Error) Result(Int, pog.QueryError) Result(Int, shork.QueryError)
:execlastid Result(Int, sqlight.Error) Result(Int, pog.QueryError) Result(Int, shork.QueryError)

:batchone, :batchmany, :batchexec, and :copyfrom are not implemented and fail generation — see Planned annotations.

:execresult is raw only. Native rejects it because it is indistinguishable from :execrows once rows are decoded.

Query annotations

Annotation Description
:one Returns at most one row
:many Returns zero or more rows
:exec Returns nothing
:execresult Returns the execution result (raw runtime only)
:execrows Returns the number of affected rows
:execlastid Returns the last inserted ID

Planned annotations

Reserved for future work; any use fails generation today.

Annotation Planned behavior
:batchone Batch variant of :one
:batchmany Batch variant of :many
:batchexec Batch variant of :exec
:copyfrom Bulk insert

Query macros

Macro Description
sqlode.arg(name) Names a parameter
sqlode.narg(name) Names a nullable parameter
sqlode.slice(name) Expands to a list parameter for IN clauses
sqlode.embed(table) Embeds all columns of a table into the result
@name Shorthand for sqlode.arg(name)

Skipping a query

Prefix with -- sqlode:skip to exclude a query from generation — useful when the SQL uses syntax sqlode cannot yet parse.

-- sqlode:skip
-- name: ComplexQuery :many
SELECT ...;

sqlode.slice example

-- name: GetAuthorsByIds :many
SELECT id, name FROM authors
WHERE id IN (sqlode.slice(ids));

Generates a parameter with type List(Int):

pub type GetAuthorsByIdsParams {
  GetAuthorsByIdsParams(ids: List(Int))
}

Important

sqlode.slice(...) is only supported on PostgreSQL because the generated native SQLite (sqlight) and MySQL (shork) adapters cannot bind array parameters at runtime. sqlode generate / sqlode verify reject any block that targets sqlite or mysql with a slice macro present.

If you need IN-clause expansion against SQLite/MySQL, drop the macro and either:

  • inline the placeholders yourself in the SQL and bind one parameter per element from your application code, or
  • move the query to runtime: raw and call runtime.prepare followed by your driver's parameter-list call directly, where you control how the list flattens to scalar bindings.

sqlode.embed example

-- name: GetBookWithAuthor :one
SELECT sqlode.embed(authors), books.title
FROM books
JOIN authors ON books.author_id = authors.id
WHERE books.id = $1;

The embedded table becomes a nested field:

pub type GetBookWithAuthorRow {
  GetBookWithAuthorRow(authors: Author, title: String)
}

JOIN support

Columns from JOINed tables are resolved against their source tables:

-- name: GetBookWithAuthor :one
SELECT books.title, authors.name
FROM books
JOIN authors ON books.author_id = authors.id;

books.title and authors.name end up correctly typed in the generated row.

RETURNING clause

PostgreSQL RETURNING columns become the result type:

-- name: CreateAuthor :one
INSERT INTO authors (name, bio) VALUES ($1, $2)
RETURNING id, name;
pub type CreateAuthorRow {
  CreateAuthorRow(id: Int, name: String)
}

CTE (WITH clause)

Common Table Expressions are supported — sqlode strips the CTE prefix and infers types from the main query:

-- name: GetRecentAuthors :many
WITH filtered AS (
  SELECT id FROM authors WHERE id > 0
)
SELECT authors.id, authors.name
FROM authors
JOIN filtered ON authors.id = filtered.id;

Type mapping

SQL type Gleam type
INT, INTEGER, SMALLINT, BIGINT, SERIAL, BIGSERIAL Int
FLOAT, DOUBLE, REAL, NUMERIC, DECIMAL, MONEY Float
BOOLEAN, BOOL Bool
TEXT, VARCHAR, CHAR String
BYTEA, BLOB, BINARY BitArray
TIMESTAMP, DATETIME String
DATE String
TIME, TIMETZ, INTERVAL String
UUID String
JSON, JSONB String
TYPE[], TYPE ARRAY List(TYPE)
CITEXT, INET, CIDR, MACADDR, XML, BIT, TSVECTOR, TSQUERY String
POINT, LINE, LSEG, BOX, PATH, POLYGON, CIRCLE String
PostgreSQL ENUM Generated custom type (with to_string/from_string helpers)

Nullable columns (no NOT NULL) are wrapped in Option(T).

Overrides

Each sql block can carry type overrides and column renames:

sql:
  - schema: "db/schema.sql"
    queries: "db/query.sql"
    engine: "postgresql"
    gen:
      gleam:
        out: "src/db"
    overrides:
      types:
        - db_type: "uuid"
          gleam_type: "String"
        - column: "users.id"
          gleam_type: "String"
      renames:
        - table: "authors"
          column: "bio"
          rename_to: "biography"

Two targeting modes:

  • db_type — every column of a given database type (e.g. every uuid becomes String).
  • column — a specific column via table.column (e.g. only users.id).

Column-level overrides win over db_type overrides.

Custom type aliases

A non-primitive gleam_type (e.g. UserId instead of Int) keeps the name in generated record fields but, by default, encodes and decodes through the underlying primitive. That works when the mapped type is a transparent alias:

// OK: transparent alias — the underlying primitive encoders apply
// directly because `UserId` is just `Int` at runtime.
pub type UserId = Int

For opaque domain types (single-constructor wrappers around the underlying value), declare explicit encode / decode codec hooks alongside the override. The generated code then routes the value through the user-supplied pair instead of calling the primitive encoder on the wrapped form:

overrides:
  types:
    - db_type: "int"
      gleam_type: "myapp/types.UserId"
      encode: "user_id_to_int"
      decode: "int_to_user_id"
// myapp/types.gleam
pub opaque type UserId {
  UserId(Int)
}

pub fn user_id_to_int(id: UserId) -> Int {
  let UserId(inner) = id
  inner
}

pub fn int_to_user_id(value: Int) -> UserId {
  UserId(value)
}

encode and decode MUST be specified together — providing only one half is rejected at config-load time. Hook function names are resolved relative to the module of gleam_type: with gleam_type: "myapp/types.UserId" the generated code calls types.user_id_to_int(value) (the trailing-segment alias of the existing type import), so the user only needs the type itself in scope.

Without codec hooks, sqlode emits a warning at generation time pointing at the transparent-alias requirement; the warning is suppressed when codec hooks are provided.

sqlode checks that gleam_type starts with an uppercase letter and encode / decode start with a lowercase letter (or underscore).

Semantic type mappings

By default UUID, JSON, DATE, TIME, TIMESTAMP become String. type_mapping opts into richer aliases:

gen:
  gleam:
    out: "src/db"
    type_mapping: "rich"
SQL type string (default) rich strong
TIMESTAMP / DATETIME String SqlTimestamp SqlTimestamp(String)
DATE String SqlDate SqlDate(String)
TIME / TIMETZ String SqlTime SqlTime(String)
UUID String SqlUuid SqlUuid(String)
JSON / JSONB String SqlJson SqlJson(String)

rich is a plain String alias — readable in signatures, not enforced by the compiler. strong emits a single-constructor wrapper with an *_to_string helper; SqlUuid and String are then distinct at compile time, and adapters wrap / unwrap values automatically.

Example with type_mapping: "strong":

// Generated in models.gleam
pub type SqlUuid {
  SqlUuid(String)
}

pub fn sql_uuid_to_string(value: SqlUuid) -> String {
  let SqlUuid(inner) = value
  inner
}

Limitations

sqlode is still early. A few constraints to check before adopting it; most are tracked for future releases.

Parameter type inference

sqlode infers a parameter's type from its surrounding SQL. Four contexts are recognised today:

  1. INSERT INTO t (col) VALUES ($1) — parameter inherits col's type.
  2. WHERE col = $1 (and !=, <, <=, >, >=).
  3. WHERE col IN ($1, $2, ...) and sqlode.slice($1).
  4. $1::int / CAST($1 AS int) — explicit cast.

Anywhere else, sqlode fails generation with:

Query "Name": could not infer type for parameter $N. Use a type cast (e.g. $N::int) to specify the type

Cases that need an explicit cast today: scalar arithmetic (price + $1), parameters inside CASE WHEN branches whose other branches are also parameters, and function arguments sqlode does not yet recognise. Pin the type with $N::int (PostgreSQL) or CAST($N AS INTEGER) (SQLite).

Schema DDL scope

The schema parser accepts both schema snapshots and migration histories (including destructive DDL). Supported statements:

  • CREATE TABLE, CREATE VIEW, CREATE TYPE (enum)
  • ALTER TABLE ... ADD COLUMN / DROP COLUMN
  • ALTER TABLE ... RENAME TO / RENAME COLUMN
  • ALTER TABLE ... ALTER COLUMN TYPE / SET NOT NULL / DROP NOT NULL
  • DROP TABLE, DROP VIEW, DROP TYPE

Anything else (CREATE INDEX, transaction blocks, comments) is silently skipped.

View resolution

CREATE VIEW ... AS SELECT ... columns resolve against the base tables so generated models have real types. By default sqlode fails generation when any view column cannot be resolved — a partially resolved view is almost always a sign that the schema and the config have drifted, and silently dropping columns lets that drift reach generated code.

If you need the old warn-and-continue behaviour, set strict_views: false:

sql:
  - schema: "db/schema.sql"
    queries: "db/query.sql"
    engine: "postgresql"
    gen:
      gleam:
        out: "src/db"
        strict_views: false

Unresolvable columns are then printed to stderr and dropped (or the whole view is dropped if nothing resolves).

Custom types: transparent aliases by default, opaque via codec hooks

See Custom type aliases. Without codec hooks, the mapped gleam_type MUST be a transparent alias (pub type UserId = Int) — the generated code calls primitive encoders directly on the value. Opaque single-constructor types (pub opaque type UserId { UserId(Int) }) are supported via the explicit encode / decode hooks documented there.

Config options

emit_sql_as_comment

Attach the original SQL as a comment on each generated adapter function.

gen:
  gleam:
    out: "src/db"
    emit_sql_as_comment: true

emit_exact_table_names

Keep table names as-is instead of singularising. authors stays pub type Authors { ... } (default would be Author).

gen:
  gleam:
    out: "src/db"
    emit_exact_table_names: true

CLI

# Standalone escript
sqlode generate [--config=./sqlode.yaml]
sqlode verify   [--config=./sqlode.yaml]
sqlode init     [--output=./sqlode.yaml]

# Via Gleam
gleam run -m sqlode -- generate [--config=./sqlode.yaml]
gleam run -m sqlode -- verify   [--config=./sqlode.yaml]
gleam run -m sqlode -- init     [--output=./sqlode.yaml]

sqlode verify

verify is the static check lane for CI. It loads the project like generate does — schema parsing, query parsing, analyser pass — but writes no files and collects every failure into a single report instead of short-circuiting on the first error.

$ sqlode verify
Verifying config: sqlode.yaml
[src/db] query "FilterAuthors" has 4 inferred parameter(s), exceeds query_parameter_limit 3

Non-zero exit on any finding, so it gates generation in CI:

- run: sqlode verify
- run: sqlode generate

Per-block policies verify honours:

  • strict_views — promote view-resolution warnings to findings (same as generate).
  • query_parameter_limit — per-query cap on inferred parameters, mirroring sqlc's option. Unset means no limit.

Migrating from sqlc

sqlode follows sqlc conventions, so most SQL files move over untouched. The differences:

sqlc sqlode
Install Standalone binary (brew install sqlc) Escript or gleam add sqlode
Config sqlc.yaml / sqlc.json sqlode.yaml (v2 format only), also accepts sqlc.yaml / sqlc.yml / sqlc.json on autodiscovery
Generate sqlc generate sqlode generate
Init sqlc init sqlode init
Vet/Verify sqlc vet, sqlc verify sqlode verify (static analysis + query_parameter_limit)
Target language Go, Python, Kotlin, etc. Gleam
Runtime Generated code is self-contained Generated code imports sqlode/runtime by default; set vendor_runtime: true to vendor a copy and drop the runtime dependency (see Self-contained generation)

Migration steps

  1. Install sqlode — see Install.

  2. Keep your existing sqlc.yaml / sqlc.yml / sqlc.json. sqlode generate auto-discovers them in the current directory when no --config is passed (search order: sqlode.yaml, sqlode.yml, sqlc.yaml, sqlc.yml, sqlc.json; pass --config=<path> if more than one exists). If you prefer a dedicated file, copy the config to sqlode.yaml. Either way, keep version: "2" and the sql blocks and replace the gen section:

    gen:
      gleam:
        out: "src/db"
        runtime: "raw"   # or "native"
  3. Swap sqlc.arg / sqlc.narg / sqlc.slice / sqlc.embed for the sqlode.* versions in your .sql files. The @name shorthand is unchanged.

  4. Run sqlode generate (or gleam run -m sqlode -- generate).

Unsupported sqlc features

  • sqlc.yaml v1 format
  • vet and verify commands
  • emit_json_tags and other sqlc-specific emit options not listed above

License

MIT

About

Generate type-safe Gleam code from SQL schemas and queries. sqlc-style workflow with PostgreSQL, MySQL, and SQLite support.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages