Skip to content

Releases: microbus-io/sequel

Removed FOR_UPDATE() virtual function

08 Apr 14:35

Choose a tag to compare

FOR_UPDATE was a no-op on SQLite which resulted in unexpected deadlocks during concurrent tests. Use an UPDATE statement to lock rows on all databases instead of a SELECT ... FOR UPDATE.

Add parens around virtual function expansions

07 Apr 03:02

Choose a tag to compare

Bug fix: Added parenthesis around expressions generated through virtual functions. NOW_UTC() did not work as a DEFAULT value in a CREATE TABLE statement in SQLite.

Fix nested virtual function expansion

25 Mar 01:30

Choose a tag to compare

Bug fixed: Nested virtual functions were not expanded correctly in certain situations.

SQLite in-memory test databases and FOR_UPDATE

22 Mar 04:43

Choose a tag to compare

Fix: SQLite in-memory test databases

  • Changed the default SQLite data source from :memory: to file:?mode=memory&cache=shared so that multiple connections share the same in-memory database.
  • Removed the special-case that skipped setDatabaseInDataSourceName for SQLite; it now follows the same code path as other drivers.
  • Updated the comment to clarify that SQLite in-memory databases are automatically freed when all connections close.

New: FOR_UPDATE() virtual function

Added a FOR_UPDATE() virtual function for portable row-level locking in SELECT statements.

SELECT * FROM users WHERE id=? FOR_UPDATE()

Expands per driver:

  • MySQL / PostgreSQLFOR UPDATE
  • MSSQLWITH (UPDLOCK, ROWLOCK)
  • SQLite — empty string (transaction-level locking is sufficient)

SQLite Driver Support

21 Mar 17:12

Choose a tag to compare

SQLite Driver Support

Sequel now supports SQLite via the pure-Go modernc.org/sqlite driver. No CGo or external libraries required.

Opening a connection

db, err := sequel.Open("sqlite", "file:path/to/database.db")
db, err := sequel.Open("sqlite", ":memory:")
db, err := sequel.Open("", "file:mydata.db") // driver inferred automatically

The driver name "sqlite3" is accepted as an alias for "sqlite".

Testing

SQLite is now the default driver for OpenTesting when no driver or DSN is provided. Tests use in-memory databases for speed and zero cleanup:

db, err := sequel.OpenTesting("", "", t.Name()) // SQLite in-memory

Virtual function support

All built-in virtual functions expand to SQLite-compatible SQL:

Function SQLite expansion
NOW_UTC() STRFTIME('%Y-%m-%d %H:%M:%f', 'now')
DATE_ADD_MILLIS(base, ms) STRFTIME('%Y-%m-%d %H:%M:%f', base, '+' || (ms/1000.0) || ' seconds')
DATE_DIFF_MILLIS(a, b) (JULIANDAY(a) - JULIANDAY(b)) * 86400000.0
LIMIT_OFFSET(l, o) LIMIT l OFFSET o
REGEXP_TEXT_SEARCH(? IN cols) cols LIKE ('%' || ? || '%')

Note: REGEXP_TEXT_SEARCH uses LIKE for substring matching since SQLite does not have built-in regex support. This is case-insensitive for ASCII but not a true regular expression match.

Other details

  • Placeholders: ? (same as MySQL and SQL Server — no conversion needed)
  • InsertReturnID: Uses LastInsertId() (same mechanism as MySQL)
  • Schema migration: Fully supported with SQLite-native datetime functions and INSERT OR IGNORE for concurrency safety
  • Driver-specific migration files: Use -- DRIVER: sqlite comments

Internal improvements

  • Removed hardcoded $1, $2 PostgreSQL placeholders from migration queries — these now use ? and rely on automatic placeholder conversion like all other query paths
  • Replaced driver-specific datetime expressions in migration logic with NOW_UTC() and DATE_ADD_MILLIS() virtual functions, collapsing many four-way switch statements into shared code
  • Default OpenTesting driver changed from MySQL to SQLite for zero-configuration testing

Executor interface

15 Mar 04:29

Choose a tag to compare

The Executor interface are the shared methods in DB and Tx

Extend sql.Tx

15 Mar 02:34

Choose a tag to compare

This release extend sql.Tx to enable virtual functions and placeholder replacement as well as with InsertReturnID.

InsertReturnID

14 Mar 05:17

Choose a tag to compare

InsertReturnID executes an INSERT statement and returns the auto-generated ID for the named ID column

Cross-Driver Support and Virtual Functions

10 Mar 03:36

Choose a tag to compare

Cross-Driver Support

Automatic Placeholder Conversion

All query methods (Exec, Query, QueryRow, Prepare, and their Context variants) now automatically convert ? placeholders to the driver's native syntax. For PostgreSQL, ? becomes $1, $2, etc. Placeholders inside quoted strings are left untouched.

Virtual Functions

Virtual functions are driver-agnostic function calls in SQL that Sequel expands into driver-specific expressions before execution. They are matched case-insensitively and support nesting.

Built-in virtual functions:

  • NOW_UTC() - current UTC timestamp with millisecond precision
  • REGEXP_TEXT_SEARCH(expr IN col1, col2, ...) - case-insensitive regex search across columns
  • DATE_ADD_MILLIS(baseExpr, milliseconds) - add milliseconds to a timestamp
  • DATE_DIFF_MILLIS(a, b) - difference between two timestamps in milliseconds
  • LIMIT_OFFSET(limit, offset) - cross-driver pagination

Custom virtual functions can be registered with the package-level RegisterVirtualFunc:

sequel.RegisterVirtualFunc("BOOL", func(driverName string, args string) (string, error) {
    switch driverName {
    case "mysql", "pgx":
        return args, nil
    case "mssql":
        return "CAST(" + args + " AS BIT)", nil
    default:
        return "", errors.New("unsupported driver: %s", driverName)
    }
})

New Public API

  • sequel.RegisterVirtualFunc(name, handler) - register custom virtual functions globally
  • db.UnpackQuery(query) - expand virtual functions and conform placeholders (called automatically by query methods)

Deprecated

  • db.ConformArgPlaceholders() - placeholder conversion is now automatic
  • db.NowUTC() - use the NOW_UTC() virtual function instead
  • db.RegexpTextSearch() - use the REGEXP_TEXT_SEARCH() virtual function instead

Move code generation from Sequel to Fabric

01 Mar 05:59

Choose a tag to compare

Code generation is now done by a coding agent using the instructions in the Fabric's .claude directory. The legacy deterministic code generator is removed. It's now as simple as:

HEY CLAUDE

Create a new microservice to persist books in a SQL database