VillageSQL is a drop-in replacement for MySQL with extensions.
All examples in this guide work on VillageSQL. Install Now →
posts table, generates code with sqlc against MySQL, and runs it against a live server.
Installing sqlc
Install sqlc with Homebrew, or withgo install if you prefer to manage it as a Go tool:
Project Layout
Create a Go module and three files: a schema, a query file, and an sqlc configuration file.schema.sql defines the table sqlc will generate types from. body has no NOT NULL constraint, which matters later:
Writing Annotated Queries
sqlc reads plain SQL files and turns a-- name: comment above each statement into a generated Go method. The word after the query name sets the return shape: :one for a single row, :many for a slice, :exec for no return value, :execresult for the raw sql.Result, and :execrows for the affected row count.
MySQL’s INSERT statements do not support RETURNING, so getting a new row’s ID back needs a dedicated annotation. :execlastid calls sql.Result.LastInsertId() for you and returns it directly:
Configuring sqlc.yaml
sqlc.yaml points sqlc at the schema and query files and sets the MySQL engine. Paths are resolved relative to the location of sqlc.yaml, not the directory you run sqlc from:
package and out name the generated Go package and the directory it is written to. sql_package: "database/sql" tells sqlc to generate code against the standard library’s database/sql, which is what go-sql-driver/mysql implements.
Generating Code
posts/: db.go (the Queries struct and its constructor), models.go (the Post struct), and query.sql.go (one method per annotated query). models.go is where the nullable body column shows up:
Nullable Columns Generate sql.NullString, Not a Pointer
Becausebody TEXT has no NOT NULL constraint, sqlc generated sql.NullString for it rather than a plain string or a *string. The same rule applies to CreatePostParams, generated from the INSERT statement’s parameter list:
sql.NullString explicitly, even to pass a value:
Valid: false rather than leaving the field as an empty string. An empty sql.NullString{} zero value already has Valid: false, so it is equivalent to writing sql.NullString{Valid: false} explicitly:
.Valid before trusting .String:
NULL, make that a deliberate schema decision. Adding NOT NULL to body in schema.sql and re-running sqlc generate changes the generated field from sql.NullString to a plain string everywhere it appears, which removes the .Valid check from every call site.
Connecting and Running Migrations
Create the database and an application user with a real password, scoped to that one database:schema.sql with the mysql client before running the application:
parseTime=true, which is required for MySQL’s TIMESTAMP and DATETIME columns to scan into Go’s time.Time instead of []byte:
CreatePost Returns int64, GetPost Takes uint64
id BIGINT UNSIGNED maps to uint64 everywhere sqlc generates it as a column, including the id parameter of GetPost. But CreatePost, generated with :execlastid, returns a plain int64. That comes from sql.Result.LastInsertId(), which the standard library defines as int64 regardless of the column’s actual signedness. Passing a freshly created ID into GetPost needs an explicit conversion:
Tuning the Connection Pool
database/sql manages its own connection pool underneath sql.Open, and sqlc’s generated Queries struct uses whatever pool the *sql.DB you pass it already has. Two settings matter for a MySQL backend specifically:
SetConnMaxLifetime should be set below MySQL’s wait_timeout, which defaults to 28800 seconds (8 hours) on this server. Without it, connections that MySQL has already closed server-side stay in the Go pool as idle entries until a query against one fails.
Frequently Asked Questions
Does sqlc run against a live database?
Only when you explicitly enable it.sqlc generate parses schema.sql and query.sql statically and does not need a running server. sqlc vet and sqlc push can connect to a real database for additional checks, but the basic generate workflow in this guide never opens a connection.
What happens if a query in query.sql has a syntax error?
sqlc generate fails with a parse error naming the file and line before generating anything. Nothing is written to the output directory on failure, so a broken query never produces stale or partial generated code.
Can sqlc generate code for INSERT … ON DUPLICATE KEY UPDATE?
Yes. sqlc parses it as a normalINSERT statement. Annotate it with :exec, :execresult, :execrows, or :execlastid depending on which part of the result you need, the same as any other insert.
Troubleshooting
See also
- MySQL and ORMs — the runtime-query-building approach sqlc is an alternative to
- Schema Migrations in MySQL — versioning
schema.sqlchanges safely once the table is in production - MySQL Connection Pooling — pool sizing and persistent connections beyond
database/sql’s defaults

