vlang

/

v Public
0 commits 40 issues 0 pull requests 0 contributors Discussions Projects CI

orm.Connection interface lacks exec() method, forcing raw SQL execution to bypass the interface #14

Describe the feature

orm.Connectiondefinesselect, insert, update, delete, create, drop, and last_id, but **not exec()**. Any code that needs to run raw SQL (DDL, DML, or arbitrary statements) must obtain the concrete driver type (e.g., mysql.DB, pg.Conn) and call its exec()` directly, rather than going through the interface.

Current workaround in application code:

// Must cast to concrete type just to call exec()
mut db := conn as mysql.DB
db.exec("INSERT INTO ...") or { ... }

Expected: the same pattern used by every Go SQL library — call exec() on the connection directly:

// Go database/sql, GORM, sqlx — all have exec() on the main type
db.Exec("INSERT INTO ...")

All four database drivers already have exec():

Driver Existing signature File
mysql fn (db &DB) exec(query string) ![]Row db/mysql/mysql.c.v
postgres fn (c &Conn) exec(query string) ![]db.pg.Row db/pg/pg.c.v
sqlite fn (db &DB) exec(query string) ![]Row db/sqlite/sqlite.c.v
mssql fn (h HStmt) exec(sql_ string) ! db/mssql/stmt_handle.c.v

Stronger evidence: pg/orm.v already uses c.exec() internally to implement the ORM's create() and drop() methods:

// db/pg/orm.v
pub fn (c &Conn) create(table orm.Table, fields []orm.TableField) ! {
    query := orm.orm_table_gen(...)
    for stmt in stmts {
        c.exec(stmt + ';')!    // ORM implementation already depends on exec()
    }
}

This shows exec() is a fundamental capability of every SQL driver, not a driver-specific extension.

Comparison with Go ecosystem:

Library Where is exec()?
Go database/sql *sql.DB.Exec() — on the main type
GORM *gorm.DB.Exec() — on the main type
sqlx *sqlx.DB.Exec() — on the main type
V ORM (current) Not on orm.Connection — must use concrete driver type
V ORM (proposed) orm.Connection.exec() — add to the interfac

Use Case

Database initialization and migration scripts commonly execute raw DDL/DML that cannot be expressed through the ORM's sql { ... } DSL. For example, seeding reference data or running custom DDL:

// init_core_database.v — seeding initial data into core tables
sql_commands := [core_api, core_app, core_menu, core_role, ...]
for cmd in sql_commands {
    _ := db.exec(cmd) or { return error(...) }  // ← exec() needed here
}

The sql db { ... } DSL handles structured operations (create table, select, insert with struct mapping), but arbitrary SQL strings — for example INSERT INTO ... with hand-crafted values, or executing lists of pre-defined SQL statements — cannot go through the DSL. They require exec().

Without exec() on orm.Connection, the calling code must:

  1. Cast the connection to the concrete driver type (e.g., mysql.DB)
  2. Call a separate raw-acquisition method (acquire_raw())

This couples application code to a specific database driver and adds noise that has no equivalent in other ecosystems.

Proposed Solution

  1. vlib/orm/orm.v — add one line to the interface:
pub interface Connection {
mut:
    select(config SelectConfig, data QueryData, where QueryData) ![][]Primitive
    insert(table Table, data QueryData) !
    update(table Table, data QueryData, where QueryData) !
    delete(table Table, where QueryData) !
    create(table Table, fields []TableField) !
    drop(table Table) !
    last_id() int
    exec(s string) !          // ← new
}

A ! (void result) return type is sufficient for DDL/DML — application code that needs query results can use select() which already exists on the interface.

2. Driver ORM adapters — each driver's orm.v needs a thin wrapper to normalize the return type to !, since current return types vary (![]Row, ![]db.pg.Row, !).

Compatibility:

  • Zero breaking changes — purely additive
  • All drivers already have exec() — no C code changes needed
  • Application code can call orm.Connection.exec() directly without casting to concrete types

Other Information

No response

Acknowledgements

  • I may be able to implement this feature request
  • This feature might incur a breaking change

Version used

V 0.5.1 3690f88

Environment details (OS name and version, etc.)

V full version V 0.5.1 e4a265a9914ea9ccb947436b277c31362141c90d.3690f88
OS linux, Deepin 25
Processor 20 cpus, 64bit, little endian, 13th Gen Intel(R) Core(TM) i5-13500
Memory 10.77GB/31.02GB
V executable /home/Jengro/opt/v/v
V last modified time 2026-06-10 08:18:59
V home dir OK, value: /home/Jengro/opt/v
VMODULES OK, value: /home/Jengro/.vmodules
VTMP OK, value: /tmp/v_1000
Current working dir OK, value: /home/Jengro
Git version git version 2.51.0
V git status 0.5.1-1879-g3690f882
.git/config present true
cc version cc (Deepin 12.3.0-17deepin17) 12.3.0
gcc version gcc (Deepin 12.3.0-17deepin17) 12.3.0
clang version Deepin clang version 17.0.6 (5deepin7)
tcc version tcc version 0.9.28rc 2025-02-13 HEAD@f8bd136d (x86_64 Linux)
tcc git status thirdparty-linux-amd64 696c1d84
emcc version N/A
glibc version ldd (Debian GLIBC 2.38-6deepin24) 2.38

[!NOTE] You can use the 👍 reaction to increase the issue's priority for developers.

Please note that only the 👍 reaction to the issue itself counts as a vote. Other reactions and those to comments will not be taken into account.