Releases: coregx/relica
Release list
v0.14.1 — OrderBySub/GroupBySub for Type-Safe Expression Ordering
Added
OrderBySub(exp Expression)— type-safe expressions in ORDER BY using CaseWhen() builderGroupBySub(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
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
Fixed (ozzo-dbx parity)
- SELECT alias quoting —
Select("u.name AS display")now properly quotes both column and alias:"u"."name" AS "display"(was passed through raw) - Function-call guard in quoteColumn —
COUNT(*),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 9223372036854775807for MySQL compatibility (MySQL requires LIMIT before OFFSET)
Added
AndSelect(cols...)— append columns to existing SELECT for conditional query building, matching ozzo-dbx'sAndSelectpattern
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
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
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
Enterprise-level audit of the entire codebase: 32 findings from 4 independent audits, all resolved. 7 PRs merged (#23-#29).
Breaking Changes
var→func— All 32 exported expression builders and error helpers are now proper functions. Improves godoc, prevents reassignment.ErrNotFoundremainsvar.Distinct(bool)→Distinct()— No parameter, always enables DISTINCT.GenerateParamName()→GenerateParamName(index int)— Now dialect-aware.QueryParams()deprecated — UseParams()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/QuoteColumnNamenow 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
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.Ptrwithreflect.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
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
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:
- Expression API (preferred):
Where(relica.Eq("id", 1)) - Named placeholders:
Where("id = {:id}", relica.Params{"id": 1}) - Positional placeholders:
Where("id = ?", 1)
Full Changelog: v0.10.0...v0.10.1
v0.10.0 - Direct API, Enterprise Testing, awesome-go Ready
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()notdb.Builder().Select()) Select()without args =SELECT *(no need forSelect("*"))- 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 (
mainis the single source of truth)
Full Changelog: v0.9.1...v0.10.0