PostgreSQL query builder for MoonBit: SQL AST and bound parameters
moon add ash1day/sqlimport {
"ash1day/sql/ast",
"ash1day/sql/builder",
}let q = @builder.select()
.columns(["id", "name", "email"])
.from("users")
.where_(@builder.col("active").eq(@builder.val_bool(true)))
.order_by("name", @ast.Asc)
.limit(20)
.build()
// q.sql => SELECT "id", "name", "email" FROM "users" WHERE ("active" = $1) ORDER BY "name" ASC LIMIT $2
// q.params => [Bool(true), Int(20)]
assert_eq(q.params, [@ast.Bool(true), @ast.Int(20)])// Basic
let _ = @builder.select()
.columns(["id", "name"]) // specific columns
.from("users")
.build()
// All columns (*)
let _ = @builder.select().all().from("users").build()
// DISTINCT
let _ = @builder.select().distinct().columns(["country"]).from("users").build()
// FROM with alias
let _ = @builder.select().all().from_as("users", "u").build()
// FROM with schema
let _ = @builder.select().all().from_schema("public", "users").build()
// Single column
let _ = @builder.select().column("id").from("users").build()
// Expression with alias
let _ = @builder.select()
.expr_as(@builder.col("price").mul(@builder.val_int(2)), "double_price")
.from("products")
.build()
// table.*
let _ = @builder.select().table_all_columns("users").from("users").build()let _ = @builder.select()
.all()
.from("users")
.where_(@builder.col("age").gte(@builder.val_int(18)))
.where_(@builder.col("active").eq(@builder.val_bool(true)))
.build()
// WHERE (("age" >= $1) AND ("active" = $2))let _ = @builder.select()
.expr(@builder.table_col("orders", "id"))
.expr(@builder.table_col("users", "name"))
.from("orders")
.left_join("users", @builder.table_col("orders", "user_id").eq(@builder.table_col("users", "id")))
.build()
// With alias
let _ = @builder.select()
.all()
.from("orders")
.left_join_as("users", "u", @builder.table_col("orders", "user_id").eq(@builder.table_col("u", "id")))
.build()let _ = @builder.select()
.columns(["department"])
.expr_as(@builder.func("COUNT", [@builder.asterisk()]), "cnt")
.from("employees")
.group_by("department")
.having(@builder.func("COUNT", [@builder.asterisk()]).gt(@builder.val_int(5)))
.build()let _ = @builder.select()
.all()
.from("products")
.order_by("price", @ast.Desc)
.limit(10)
.offset(20)
.build()// Single row
let _ = @builder.insert_into("users")
.columns(["name", "email"])
.values([@builder.val_str("Alice"), @builder.val_str("alice@example.com")])
.build()
// Multi-row
let _ = @builder.insert_into("users")
.columns(["name", "email"])
.values([@builder.val_str("Alice"), @builder.val_str("alice@example.com")])
.values([@builder.val_str("Bob"), @builder.val_str("bob@example.com")])
.build()
// RETURNING
let _ = @builder.insert_into("users")
.columns(["name"])
.values([@builder.val_str("Carol")])
.returning(["id", "created_at"])
.build()let _ = @builder.update("users")
.set("name", @builder.val_str("Bob"))
.set("active", @builder.val_bool(false))
.where_(@builder.col("id").eq(@builder.val_int(42)))
.returning(["id", "name"])
.build()let _ = @builder.delete_from("users")
.where_(@builder.col("id").in_list([@builder.val_int(1), @builder.val_int(2), @builder.val_int(3)]))
.returning(["id"])
.build()| Function | Description |
|---|---|
| col("name") | Column reference: "name" |
| table_col("t", "col") | Table-qualified column: "t"."col" |
| val_int(42) | Integer literal |
| val_str("hello") | String literal |
| val_bool(true) | Boolean literal |
| val_double(3.14) | Double literal |
| val_int64(9999999999L) | Int64 literal |
| val_null() | NULL |
| func("UPPER", [col("name")]) | Function call (raises on invalid name) |
| asterisk() | * |
| window_func(...) | Window function (see below) |
| in_sub(expr, sub_builder) | expr IN (subquery) |
| not_in_sub(expr, sub_builder) | expr NOT IN (subquery) |
| Method | SQL |
|---|---|
| .eq(other) | = other |
| .ne(other) | <> other |
| .gt(other) | > other |
| .lt(other) | < other |
| .gte(other) | >= other |
| .lte(other) | <= other |
| .and_(other) | AND other |
| .or_(other) | OR other |
| .like("pat%") | LIKE 'pat%' |
| .is_null() | IS NULL |
| .is_not_null() | IS NOT NULL |
| .in_list([...]) | IN (...) |
| .not_in_list([...]) | NOT IN (...) |
| .between(low, high) | BETWEEN low AND high |
| .not_between(low, high) | NOT BETWEEN low AND high |
| .add(other) | + other |
| .sub_(other) | - other |
| .mul(other) | * other |
| .div(other) | / other |
| .not_() | NOT expr |
| .neg() | -expr |
// FROM subquery
let sub = @builder.select()
.columns(["id", "name"])
.from("users")
.where_(@builder.col("active").eq(@builder.val_bool(true)))
let _ = @builder.select().all().from_sub(sub, "active_users").build()
// SELECT * FROM (SELECT "id", "name" FROM "users" WHERE ("active" = $1)) AS "active_users"
// NOT IN subquery
let banned = @builder.select().columns(["user_id"]).from("bans")
let _ = @builder.select()
.all()
.from("users")
.where_(@builder.not_in_sub(@builder.col("id"), banned))
.build()let active = @builder.select()
.columns(["id", "name"])
.from("users")
.where_(@builder.col("active").eq(@builder.val_bool(true)))
let _ = @builder.select()
.with_("active_users", active)
.all()
.from("active_users")
.build()
// WITH "active_users" AS (SELECT "id", "name" FROM "users" WHERE ("active" = $1))
// SELECT * FROM "active_users"let _ = @builder.select().columns(["id"]).from("admins")
.union(@builder.select().columns(["id"]).from("moderators"))
.build()
// SELECT "id" FROM "admins" UNION SELECT "id" FROM "moderators"let _ = @builder.insert_into("users")
.columns(["id", "name", "email"])
.values([@builder.val_int(1), @builder.val_str("Alice"), @builder.val_str("alice@example.com")])
.on_conflict_do_update(["id"], [("name", @builder.table_col("excluded", "name"))])
.build()
// INSERT INTO "users" ("id", "name", "email") VALUES ($1, $2, $3)
// ON CONFLICT ("id") DO UPDATE SET "name" = "excluded"."name"
// Do nothing on conflict
let _ = @builder.insert_into("users")
.columns(["id", "name"])
.values([@builder.val_int(1), @builder.val_str("Alice")])
.on_conflict_do_nothing(["id"])
.build()let _ = @builder.select()
.expr_as(
@builder.window_func(
"ROW_NUMBER",
[],
partition_by=["department"],
order_by=[("salary", @ast.Desc)]
),
"rank"
)
.from("employees")
.build()
// SELECT ROW_NUMBER() OVER (PARTITION BY "department" ORDER BY "salary" DESC) AS "rank"
// FROM "employees"const result = await client.query({ text: q.sql, values: driverValues });npm ci
DATABASE_URL=postgresql://localhost/mydb node examples/node-postgres.mjscurl -fsSL https://cli.moonbitlang.com/install/unix.sh -o /tmp/install-moon.sh
bash /tmp/install-moon.sh "$(cat .moonbit-version)"
moon update
moon check --target all
moon test --target all
python3 scripts/check-readme.pynpm ci
DATABASE_URL=postgresql://localhost/postgres npm run test:postgresInstall
Download zipPostgreSQL query builder for MoonBit: SQL AST and bound parameters