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.
| Dialect | Constructor | Notes |
|---|---|---|
| PostgreSQL | quark.PostgreSQL() | $N placeholders, RETURNING, ON CONFLICT. |
| MySQL | quark.MySQL() | AUTO_INCREMENT, LAST_INSERT_ID, ON DUPLICATE KEY UPDATE. |
| MariaDB | quark.MariaDB() | MySQL-compatible upsert behavior (ON DUPLICATE KEY UPDATE). Treated as a distinct dialect for DDL edge cases. |
| SQLite | quark.SQLite() | In-memory friendly, RETURNING where supported. |
| MSSQL | quark.MSSQL() | IDENTITY, OFFSET/FETCH, MERGE upserts. |
| Oracle | quark.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"},
)
| Dialect | Generated shape |
|---|---|
| PostgreSQL | ON CONFLICT (email) DO UPDATE SET ... |
| MySQL | ON DUPLICATE KEY UPDATE ... |
| MariaDB | ON DUPLICATE KEY UPDATE ... |
| SQLite | ON CONFLICT (email) DO UPDATE SET col = excluded.col |
| MSSQL | MERGE INTO ... USING ... WHEN MATCHED ... |
| Oracle | MERGE 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
''asNULL, so an empty string written to aVARCHAR2/CLOBcolumn comes back asNULL. When scanning into a non-pointerstringfield, Quark coerces thatNULLto"", so empty strings round-trip consistently with the other engines. Use*stringorsql.Null[string]if you need to tellNULLapart from"". - JSON path is inlined.
WhereJSONbinds the JSON path as a parameter on every engine except Oracle, whoseJSON_VALUErejects 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.