🌐 Developer Guide: SQL Dialects¶
This guide documents how Entiqon handles database-specific dialect behavior using a modular Dialect
interface.
🧩 Interface¶
All dialects implement the following:
type Dialect interface {
Name() string
Placeholder(position int) string
QuoteTable(expr string) string
}
🧱 Implementations¶
✅ BaseDialect (embedded)¶
Provides default behaviors:
Name()
→ dialect exprPlaceholder()
→ returns"?"
QuoteIdentifier()
→ returns identifier without quotingQuoteLiteral(value)
→ handles:- Strings →
'value'
- Numbers →
42
,3.14
- Booleans →
true
,false
- Other types →
fmt.Sprintf("'%v'", v)
SupportsUpsert()
→false
SupportsReturning()
→false
BuildLimitOffset(limit, offset int)
→ SQL LIMIT/OFFSET string
🚦 Limit/Offset Behavior¶
BuildLimitOffset(limit, offset int)
Returns:
Input | Output |
---|---|
limit=10, offset=20 |
LIMIT 10 OFFSET 20 |
limit=5, offset=-1 |
LIMIT 5 |
limit=-1, offset=50 |
OFFSET 50 |
limit=-1, offset=-1 |
"" (empty string) |
✅ All conditions are fully tested.
🧠 Condition Resolution (Builder Integration)¶
Where("status = active")
is parsed inline and supportedWhere("status", "active")
is structured key-value form- Anything with more than 1 parameter is rejected for now
- Future support will allow:
go Where("status = $1 AND age > $2", []any{true, 45}) Where("status = :status AND age = :age", map[string]any{...})
🔒 Current Limitation¶
Expressions containing multiple logical parts (e.g. AND
, OR
) are not yet parsed or grouped.
🧠 TODO¶
- Grouped conditions will eventually be wrapped in parentheses to preserve precedence
- Example:
sql WHERE (status = $1 AND age > $2) OR (country = $3)
🛠️ Provided Dialects¶
DB Engine | Dialect Name | Placeholder Style | Quote Style | Alias Style | RETURNING Support | UPSERT Support | Since |
---|---|---|---|---|---|---|---|
Generic | generic |
? |
(none) | ❌ Unsupported | ❌ None | ❌ None | v1.4.0 |
PostgreSQL | postgres |
$1, $2... |
" |
AS |
✅ Full | ✅ Full | v1.4.0 |
MySQL | mysql |
? |
` |
AS |
❌ None | 🚫 Limited | v1.4.0 |
SQLite | sqlite |
? , :expr |
" |
Optional AS |
✅ v3.35+ | ✅ v3.24+ | v1.5.0 |
SQL Server | mssql |
@param |
[ ] |
Optional AS |
✅ via OUTPUT | 🚫 via MERGE | v1.4.0 |
Oracle | oracle |
:param |
" |
Optional AS |
✅ Full | ✅ via MERGE | v1.6.0 |
IBM DB2 | db2 |
:param |
" |
Optional AS |
✅ Partial | ✅ via MERGE | v1.6.0 |
Firebird | firebird |
? , :param |
" |
Optional AS |
✅ Supported | ✅ Limited | v1.6.0 |
Informix | informix |
? |
" |
Optional AS |
✅ Supported | ❌ Not native | v1.6.0 |
Use:
ResolveDialect("postgres") // returns PostgresDialect
ResolveDialect("unknown") // returns BaseDialect named "generic"
🔗 Integration with ParamBinder¶
The ParamBinder
uses dialect.Placeholder(n)
to assign placeholders during query construction.
pb := NewParamBinder(dialect)
pb.Bind("id") // → $1 or ?
✅ Test Strategy¶
All dialects are tested via TestDialectSuite
using shared assertions for:
- Placeholder generation
- Literal and identifier quoting
- Dialect fallback resolution
- Limit/Offset formatting
🚧 Notes¶
- Dialects are non-configurable at runtime
- If needed, create a
NewMySQLDialect()
or similar - Do not hardcode placeholders inside builders — use dialect
📁 Files¶
engine.go
— interfacedialect_base.go
— base logicdialect_generic.go
—NewGenericDialect
dialect_postgres.go
— PostgreSQL behaviordialect_resolver.go
— dialect lookup mapdialect_test.go
— shared test coverage