Skip to main content

Databases

ant runs on five databases. PostgreSQL gets everything. The others run queries, writes, relations, change tracking, and transactions, but don't have migrations yet.

Support​

PostgreSQLMySQLMariaDBSQLiteSQL Server
Tested on13 to 188.411.8modernc 1.612022
Dialectpostgres.Dialectmysql.Dialectmysql.MariaDBsqlite.Dialectsqlserver.Dialect
Driver namepgxmysqlmysqlsqlitesqlserver
Queries, relations, tracking, transactionsYesYesYesYesYes
Migrations, EnsureCreated, SchemaOfYesNoNoNoNo
Whole feature suiteYesNoNoNoNo
Conformance suiteYesYesYesYesYes
UpsertYesYesYesYesNo
MERGEFrom 15NoNoNoYes
Row locksYesYesForUpdate onlyNoNo
FULL JOINYesNoNoYesYes
Lateral joinsYesYesNoNoNo
UpdateReturningYesNoNoYesYes
DeleteReturning, ToggleYesNoYesYesYes
TodayYesYesYesNoNo
DatePartYesNoNoNoNo
ILike, FILTER, NULLS FIRSTNativeEmulatedEmulatedILike emulatedEmulated

Only PostgreSQL runs the full feature suite, whose tables are written in its own DDL. The conformance suite covers what each dialect writes differently, on every database, but it's smaller, so expect a few rough edges off PostgreSQL that nobody has hit yet.

When something is missing​

ant refuses before it sends any SQL, so the error tells you what's missing. To check ahead of time, ask the dialect.

_, err := db.Users.Where(ant.Eq(&U.ID, id)).DeleteReturning(ctx)

if errors.Is(err, ant.ErrUnsupported) {
log.Println("not on this database")
}

if dialect.Supports(db.Dialect(), dialect.Merge) {
log.Println("MERGE is available")
}

Drivers​

Import a driver in your application. ant doesn't choose one for you, with one exception: dialect/mysql imports go-sql-driver/mysql, because it has no other way to read the driver's error numbers.

import _ "github.com/jackc/pgx/v5/stdlib" // PostgreSQL
import _ "github.com/go-sql-driver/mysql" // MySQL and MariaDB
import _ "modernc.org/sqlite" // SQLite
import _ "github.com/microsoft/go-mssqldb" // SQL Server

Use parseTime=true in a MySQL DSN, or times come back as bytes. Use _pragma=foreign_keys(1) in a SQLite DSN, or foreign keys aren't enforced.

What differs​

Generated keys. MySQL has no RETURNING, so a key comes from LastInsertId, and other database-filled columns are read back with a second query. Rows with generated keys go in one at a time there, in both SaveChanges and InsertMany.

ON DUPLICATE KEY on MySQL and MariaDB. It has no target: any unique key triggers it, not only the columns you name. If a different key is the one that collides, Upsert can't find the row by your columns, and reports that it wrote nothing. Name the only unique key that can collide.

OUTPUT on SQL Server. SQL Server refuses it on a table with triggers, so a model that needs a key back can't be inserted into one.

Upserts on SQL Server. There's no ON CONFLICT. Use ant.Merge.

Paging on SQL Server. OFFSET ... FETCH needs an ORDER BY, so ant adds a meaningless one if the query has none. SQL Server refuses a Limit of zero.

Times. SQLite stores times as text, and SQL Server's DATETIME2 has no zone, so ant binds UTC on both to keep one clock. MySQL's driver does the same. ant.Past and ant.Future compare with the server's own clock, so run the server in UTC. On SQLite and MySQL, CURRENT_TIMESTAMP has whole seconds.

Parameters. Limits differ: 65,535 on PostgreSQL and MySQL, 32,766 on SQLite, and about 2,100 on SQL Server, where ant stays at 2,000. InsertMany, SaveChanges, and Include split their work to fit. Your own In with a long list doesn't.

Types. postgres.Array, postgres.JSONSet, postgres.Vector, and the rest of dialect/postgres need PostgreSQL. Collate quotes its name, which SQL Server doesn't accept.

Adding a dialect​

A dialect implements dialect.Dialect. Its Render calls dialect.Render, and its Syntax method lists what differs from PostgreSQL, so it states only its differences. dialect.RegisterErrors connects its error codes. Migrations are a separate interface, schema.Dialect. See dialect/sqlite/sqlite.go, which is about 70 lines.