Skip to content

Releases: coregx/relica

v0.14.1 — OrderBySub/GroupBySub for Type-Safe Expression Ordering

Choose a tag to compare

@kolkov kolkov released this 17 Jul 13:37
ee99a3f

Added

  • OrderBySub(exp Expression) — type-safe expressions in ORDER BY using CaseWhen() builder
  • GroupBySub(exp Expression) — type-safe expressions in GROUP BY
// Type-safe ordering with CaseWhen builder (no raw SQL):
db.Select("id", "title").From("tasks t").
    OrderBySub(relica.CaseWhen().
        When("t.due_date < CURRENT_DATE", 0).
        When("t.due_date IS NULL", 3).
        Else(1)).
    OrderBy("t.due_date ASC").
    All(&rows)

6 new tests. Parameters correctly numbered for all dialects.


Full Changelog: v0.14.0...v0.14.1

v0.14.0 — OrderByExpr and GroupByExpr for Raw SQL Expressions

Choose a tag to compare

@kolkov kolkov released this 17 Jul 13:04
afa72c2

Added

  • OrderByExpr(expr, args...) — raw SQL expressions in ORDER BY without quoting. Fixes CASE WHEN, COALESCE, and complex ordering that was previously quoted as column names (#34)
  • GroupByExpr(expr, args...) — raw SQL expressions in GROUP BY. Supports DATE(), EXTRACT(), and computed grouping

Follows bun's OrderExpr pattern. Ahead of squirrel (open issue #139) and ozzo-dbx.

db.Select("id", "title").
    From("tasks t").
    OrderByExpr("CASE WHEN t.due_date < CURRENT_DATE THEN 0 ELSE 1 END").
    OrderBy("t.due_date ASC").
    All(&rows)

db.Select("DATE(created_at) AS day", "COUNT(*)").
    From("orders").
    GroupByExpr("DATE(created_at)").
    All(&stats)

14 new test functions covering CASE WHEN, parameterized expressions, PostgreSQL placeholder numbering, and combined patterns.


Full Changelog: v0.13.1...v0.14.0

v0.13.1 — ozzo-dbx Parity: Alias Quoting, Function Guard, MySQL OFFSET

Choose a tag to compare

@kolkov kolkov released this 17 Jul 11:43
124a411

Fixed (ozzo-dbx parity)

  • SELECT alias quotingSelect("u.name AS display") now properly quotes both column and alias: "u"."name" AS "display" (was passed through raw)
  • Function-call guard in quoteColumnCOUNT(*), MAX(price), SUM(total) are no longer wrapped in quotes. Fixes invalid SQL in ORDER BY and GROUP BY with aggregate functions
  • OFFSET without LIMIT — auto-emits LIMIT 9223372036854775807 for MySQL compatibility (MySQL requires LIMIT before OFFSET)

Added

  • AndSelect(cols...) — append columns to existing SELECT for conditional query building, matching ozzo-dbx's AndSelect pattern

Testing

16 new enterprise parity tests covering alias quoting (3 dialects, case-insensitive, table.column, schema.table.column), function-call guard (COUNT, MAX, SUM, COALESCE), OFFSET/LIMIT combinations, AndSelect patterns, and a combined real-world query.


Full Changelog: v0.13.0...v0.13.1

v0.13.0 — Type-Safe Scalar Subqueries and Column Comparison

Choose a tag to compare

@kolkov kolkov released this 16 Jul 21:59
b60de9a

New Features

SelectSub — Type-Safe Scalar Subqueries in SELECT

No more raw SQL for correlated subqueries. Use the builder API:

// Before: raw SQL (not type-safe)
db.Select("id", "name").
    SelectExpr("(SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id) AS order_count").
    From("users")

// After: type-safe builder API
sub := db.Select("COUNT(*)").From("orders").
    Where(relica.EqCol("orders.user_id", "users.id"))
db.Select("id", "name").
    SelectSub(sub.AsExpression(), "order_count").
    From("users")

EqCol — Column-to-Column Comparison

Proper identifier quoting for both sides:

relica.EqCol("o.user_id", "u.id")
// PostgreSQL: "o"."user_id" = "u"."id"
// MySQL:      `o`.`user_id` = `u`.`id`

Also: NotEqCol, GreaterThanCol, LessThanCol.

Testing

21 new test functions (488 lines) covering all dialects, correlated subqueries, parameter ordering, and the full pattern from issue #31.


Full Changelog: v0.12.1...v0.13.0

v0.12.1 — Transaction Performance: 3x Latency Reduction

Choose a tag to compare

@kolkov kolkov released this 05 Jul 08:53
59f6697

Performance

Transaction queries now use direct tx.ExecContext()/tx.QueryContext() (1 network round-trip) instead of PrepareContext() + ExecContext() + Close() (3 round-trips).

This is a ~3x latency reduction for all queries inside transactions. The optimization follows the same pattern used by ozzo-dbx, sqlx, bun, and GORM (default mode).

Explicit Prepare() remains available as opt-in for batch scenarios where the same SQL is executed multiple times within one transaction.

Testing

13 new transaction integration tests covering:

  • Multiple operations in a single transaction (commit + verify)
  • Rollback on error and panic (nothing persisted)
  • Read-your-own-writes within transaction
  • BatchInsert in transaction (commit + rollback)
  • Update + Delete in same transaction
  • Upsert in transaction
  • Manual Begin/Commit/Rollback
  • 50-operation stress test

All tests run on real PostgreSQL, MySQL, and SQLite in CI.


Full Changelog: v0.12.0...v0.12.1

v0.12.0 — Enterprise Audit: Security, API Pre-freeze, Zero Panics

Choose a tag to compare

@kolkov kolkov released this 03 Jul 16:39
50e4c34

Enterprise-level audit of the entire codebase: 32 findings from 4 independent audits, all resolved. 7 PRs merged (#23-#29).

Breaking Changes

  • varfunc — All 32 exported expression builders and error helpers are now proper functions. Improves godoc, prevents reassignment. ErrNotFound remains var.
  • Distinct(bool)Distinct() — No parameter, always enables DISTINCT.
  • GenerateParamName()GenerateParamName(index int) — Now dialect-aware.
  • QueryParams() deprecated — Use Params() instead.

Security

  • All column names in INSERT/UPDATE/UPSERT SQL now properly quoted via QuoteIdentifier()
  • Model API PK columns use Eq() expression instead of raw string interpolation
  • Null-byte defense added to QuoteIdentifier() in all 3 dialects
  • Functional expressions (CASE, COALESCE, etc.) now handle table-aliased columns

Zero Panics

17 panic paths converted to stored buildErr errors. Affected: Where(), OrWhere(), Having(), FromSelect(), With(), WithRecursive(), JOIN ON, BatchInsert, LikeExp.EscapeChars(). Model(nil) returns clean error. pgx driver registered.

New Features

  • Tx symmetry: BatchInsert(), BatchUpdate(), Upsert(), NewQuery() on transactions
  • ToSQL() on all 6 query types (was 3)
  • ModelQuery.WithContext(ctx) — per-operation context
  • Query.Params() — canonical parameter accessor

Correctness Fixes

  • HAVING placeholder renumber for multi-arg clauses on PostgreSQL
  • QuoteTableName/QuoteColumnName now use dialect (was hardcoded ")
  • Validator applied to builder queries (was raw SQL only)
  • Empty Insert()/Update() returns error instead of broken SQL
  • Missing named params {:name} detected and reported
  • Schema-qualified tables: From("public.users u")"public"."users" AS "u"
  • SQL operator spacing: "col" = ? instead of "col"=?

Testing

  • 1576 lines of new integration tests with reserved word columns (order, select, group, user, table, where, index)
  • All 3 databases tested in CI: PostgreSQL 15, MySQL 8, SQLite
  • 50+ subtests covering security, table aliases, Model API, ToSQL consistency, Tx symmetry, schema-qualified tables

Full Changelog: v0.11.1...v0.12.0

v0.11.1 - Expression Table Alias Quoting Fix

Choose a tag to compare

@kolkov kolkov released this 19 Jun 17:03
0bb1236

Bug Fix

Expression API: table-aliased column quoting (#21)

relica.Eq("c.deleted_at", nil) now correctly generates "c"."deleted_at" IS NULL instead of "c.deleted_at" IS NULL.

All expression types fixed: Eq, NotEq, GreaterThan, LessThan, GreaterOrEqual, LessOrEqual, In, NotIn, Between, NotBetween, Like, NotLike, HashExp. All three dialects: PostgreSQL, MySQL, SQLite.

// Now works correctly with table aliases in all expressions:
db.Select("c.id", "c.name").
    From("companies c").
    LeftJoin("lead_scoring ls", "ls.company_id = c.id").
    Where(relica.Eq("c.deleted_at", nil)).            // "c"."deleted_at" IS NULL
    Where(relica.In("c.status", "active", "trial")).   // "c"."status" IN (?, ?)
    All(&rows)

Code Quality

  • Extracted shared quoteColumn() helper — single source of truth for column quoting
  • Replaced deprecated reflect.Ptr with reflect.Pointer (21 occurrences)
  • Extracted repeated string literals into constants (28 occurrences)
  • golangci-lint: 49 → 0 issues

Tests

  • 36 new test cases for table-aliased columns across all expression types and dialects
  • All 1600+ tests pass, 88%+ coverage maintained

Full Changelog: v0.11.0...v0.11.1

v0.11.0 - Query Helpers, Error Handling, Model Upsert/UpdateChanged

Choose a tag to compare

@kolkov kolkov released this 17 Mar 16:49
026dd7b

What's New

Query Helpers

// Check if record exists
exists, _ := db.Select().From("users").Where(relica.Eq("email", email)).Exists()

// Count records
count, _ := db.Select().From("users").Where(relica.Eq("status", 1)).Count()

// Preview SQL without executing
sql, params := db.Select().From("users").Where(relica.Eq("id", 1)).ToSQL()

Error Handling

// ErrNotFound — clean replacement for sql.ErrNoRows
err := db.Select().From("users").Where(relica.Eq("id", 999)).One(&user)
if errors.Is(err, relica.ErrNotFound) { /* not found */ }

// Database-agnostic error classification (PostgreSQL, MySQL, SQLite)
if relica.IsUniqueViolation(err) { /* duplicate key */ }
if relica.IsForeignKeyViolation(err) { /* FK violation */ }
if relica.IsNotNullViolation(err) { /* NOT NULL */ }
if relica.IsCheckViolation(err) { /* CHECK */ }

Model API Extensions

// Struct-based upsert (INSERT ON CONFLICT DO UPDATE)
user := User{ID: 1, Name: "Alice", Email: "alice@example.com"}
db.Model(&user).Upsert()              // all non-PK fields
db.Model(&user).Upsert("name")        // selective fields

// Update only changed fields
original := user
user.Name = "Updated"
db.Model(&user).UpdateChanged(&original) // UPDATE SET name=? WHERE id=?

Documentation

  • 15 documentation files updated with new feature examples
  • All guides reflect v0.11.0 API

Full Changelog: v0.10.1...v0.11.0

v0.10.1 - Named placeholders in fluent builder

Choose a tag to compare

@kolkov kolkov released this 05 Mar 15:37
336bf12

Fixed

Named placeholders {:name} now work in the fluent builder — not just in NewQuery():

// Now works everywhere: Where, AndWhere, OrWhere on Select, Update, Delete
db.Select().From("users").
    Where("id = {:id} AND status = {:status}", relica.Params{
        "id":     1,
        "status": "active",
    }).
    All(&users)

// Same param reused
db.Select().From("categories").
    Where("parent_id = {:id} OR id = {:id}", relica.Params{"id": catID}).
    All(&categories)

All three WHERE styles now work consistently:

  1. Expression API (preferred): Where(relica.Eq("id", 1))
  2. Named placeholders: Where("id = {:id}", relica.Params{"id": 1})
  3. Positional placeholders: Where("id = ?", 1)

Full Changelog: v0.10.0...v0.10.1

v0.10.0 - Direct API, Enterprise Testing, awesome-go Ready

Choose a tag to compare

@kolkov kolkov released this 05 Mar 14:56
c550ff1

What's New

Direct API Shortcuts

All operations now available without Builder():

// Before (v0.9.x)
db.Builder().BatchInsert("users", columns).Values(...).Execute()

// After (v0.10.0)
db.BatchInsert("users", columns).Values(...).Execute()
db.BatchUpdate("users", "id").Set(1, data).Execute()
db.Upsert("users", values).Execute()

Full list of direct methods on DB: Select, Insert, Update, Delete, InsertStruct, BatchInsertStruct, UpdateStruct, Model, BatchInsert, BatchUpdate, Upsert, NewQuery

Enterprise-Grade Test Suite

  • 1500+ test cases (was 650+)
  • 88.3% total coverage (was 80.7%)
  • internal/dialects: 0% → 100%
  • internal/util: 41.2% → 97.1%
  • db.go (public API): 66.3% → 99.2%

Documentation Overhaul

  • All examples use direct API (db.Select() not db.Builder().Select())
  • Select() without args = SELECT * (no need for Select("*"))
  • Validated all documentation against actual code signatures
  • Fixed non-compiling examples in guides

Bug Fixes

  • extractTableName() in security/auditor now correctly parses SQL
  • All golangci-lint issues resolved (misspell, prealloc, staticcheck, cyclop)

Workflow

  • Switched from Git Flow to GitHub Flow (main is the single source of truth)

Full Changelog: v0.9.1...v0.10.0