Skip to main content
Version: 1.8.0

Dialects

Every SQL difference between the six supported engines is handled in one place: the dialect. It owns placeholders, identifier quoting, insert-ID behaviour, upsert syntax, and DDL — which is why the same model and query code runs everywhere.

You normally never choose one. Quark detects the dialect from the driver name at quark.New. This page is the engine-by-engine reference for what that detection gets you, and where an engine still behaves differently enough to matter.

DialectConstructorNotes
PostgreSQLquark.PostgreSQL()$N placeholders, RETURNING, ON CONFLICT.
MySQLquark.MySQL()AUTO_INCREMENT, LAST_INSERT_ID, ON DUPLICATE KEY UPDATE.
MariaDBquark.MariaDB()MySQL-compatible upsert behavior (ON DUPLICATE KEY UPDATE). Treated as a distinct dialect for DDL edge cases.
SQLitequark.SQLite()In-memory friendly, RETURNING where supported.
MSSQLquark.MSSQL()IDENTITY, OFFSET/FETCH, MERGE upserts.
Oraclequark.Oracle()GENERATED AS IDENTITY, MERGE upserts.

MariaDB is detected automatically since v1.1.0. MariaDB and MySQL share the "mysql" driver, so the driver name alone can't tell them apart. On a quark.New("mysql", dsn) connection Quark probes SELECT VERSION() once at construction and switches to the MariaDB dialect when the server reports MariaDB. You get the MariaDB DDL edge cases without naming the constructor yourself.

Upsert mapping

user := User{Email: "alice@example.com", Name: "Alice Updated"}

err := quark.For[User](ctx, client).Upsert(
&user,
[]string{"email"},
[]string{"name", "active"},
)
DialectGenerated shape
PostgreSQLON CONFLICT (email) DO UPDATE SET ...
MySQLON DUPLICATE KEY UPDATE ...
MariaDBON DUPLICATE KEY UPDATE ...
SQLiteON CONFLICT (email) DO UPDATE SET col = excluded.col
MSSQLMERGE INTO ... USING ... WHEN MATCHED ...
OracleMERGE INTO ... USING ... WHEN MATCHED ...

Oracle-specific behavior

Oracle differs from the other five engines in two ways worth knowing:

  • Empty strings are NULL. Oracle stores '' as NULL, so an empty string written to a VARCHAR2/CLOB column comes back as NULL. When scanning into a non-pointer string field, Quark coerces that NULL to "", so empty strings round-trip consistently with the other engines. Use *string or sql.Null[string] if you need to tell NULL apart from "".
  • JSON path is inlined. WhereJSON binds the JSON path as a parameter on every engine except Oracle, whose JSON_VALUE rejects a bound path (ORA-40454: path expression not a literal). On Oracle the path is inlined as a literal instead. It stays injection-safe because the path is validated against a strict [A-Za-z0-9_.] grammar before it reaches the SQL.

Custom dialects

Custom or proprietary engines can be registered:

myDialect := &MyCustomDialect{}

quark.RegisterDialect("customdb", myDialect)

dialect, err := quark.DetectDialectByName("customdb")
if err != nil {
return err
}

client, err := quark.New("customdb", dsn, quark.WithDialect(dialect))

DetectDialectByName looks up a registered dialect by name and returns (Dialect, error). Use RegisterDialect once at init time.

The dialect interface covers SQL generation, placeholder formatting, identifier quoting, and schema DDL.