Skip to content
This repository was archived by the owner on May 29, 2026. It is now read-only.

Commit 86716d0

Browse files
kalbasitclaude
andcommitted
feat: initial implementation of sqlc-multi-db
Extracts the gen-db-wrappers tool from ncps into a standalone, reusable repository with three improvements: - Split into library (generator/) + thin CLI (main.go) so other programs can import the generator logic directly - Remove hardcoded ncps import path: findImportBase() auto-detects the module name and relative path from the nearest go.mod - Generate generated_errors.go (ErrNotFound, ErrMismatchedSlices) so these are never manually maintained Also includes a complete example/ demonstrating a books+tags schema across SQLite, PostgreSQL, and MySQL, exercising bulk inserts (@bulk-for), nullable fields (sql.NullString), and MySQL Create/Update patterns. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
0 parents  commit 86716d0

45 files changed

Lines changed: 4765 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
-- migrate:up
2+
CREATE TABLE books (
3+
id BIGINT AUTO_INCREMENT PRIMARY KEY,
4+
title VARCHAR(255) NOT NULL,
5+
author VARCHAR(255) NOT NULL,
6+
description TEXT,
7+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
8+
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
9+
);
10+
11+
CREATE TABLE tags (
12+
id BIGINT AUTO_INCREMENT PRIMARY KEY,
13+
name VARCHAR(255) NOT NULL UNIQUE,
14+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
15+
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
16+
);
17+
18+
CREATE TABLE book_tags (
19+
book_id BIGINT NOT NULL,
20+
tag_id BIGINT NOT NULL,
21+
PRIMARY KEY (book_id, tag_id),
22+
FOREIGN KEY (book_id) REFERENCES books(id) ON DELETE CASCADE,
23+
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
24+
);
25+
26+
-- migrate:down
27+
DROP TABLE book_tags;
28+
DROP TABLE tags;
29+
DROP TABLE books;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-- migrate:up
2+
CREATE TABLE books (
3+
id BIGSERIAL PRIMARY KEY,
4+
title TEXT NOT NULL,
5+
author TEXT NOT NULL,
6+
description TEXT,
7+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
8+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
9+
);
10+
11+
CREATE TABLE tags (
12+
id BIGSERIAL PRIMARY KEY,
13+
name TEXT NOT NULL UNIQUE,
14+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
15+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
16+
);
17+
18+
CREATE TABLE book_tags (
19+
book_id BIGINT NOT NULL REFERENCES books(id) ON DELETE CASCADE,
20+
tag_id BIGINT NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
21+
PRIMARY KEY (book_id, tag_id)
22+
);
23+
24+
-- migrate:down
25+
DROP TABLE book_tags;
26+
DROP TABLE tags;
27+
DROP TABLE books;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-- migrate:up
2+
CREATE TABLE books (
3+
id INTEGER PRIMARY KEY AUTOINCREMENT,
4+
title TEXT NOT NULL,
5+
author TEXT NOT NULL,
6+
description TEXT,
7+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
8+
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
9+
);
10+
11+
CREATE TABLE tags (
12+
id INTEGER PRIMARY KEY AUTOINCREMENT,
13+
name TEXT NOT NULL UNIQUE,
14+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
15+
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
16+
);
17+
18+
CREATE TABLE book_tags (
19+
book_id INTEGER NOT NULL REFERENCES books(id) ON DELETE CASCADE,
20+
tag_id INTEGER NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
21+
PRIMARY KEY (book_id, tag_id)
22+
);
23+
24+
-- migrate:down
25+
DROP TABLE book_tags;
26+
DROP TABLE tags;
27+
DROP TABLE books;

example/db/query.mysql.sql

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
-- name: GetBook :one
2+
-- SELECT `id`, `title`, `author`, `description`, `created_at`, `updated_at` FROM books WHERE id = ?
3+
SELECT `id`, `title`, `author`, `description`, `created_at`, `updated_at`
4+
FROM books
5+
WHERE id = ?;
6+
7+
-- name: ListBooks :many
8+
-- SELECT `id`, `title`, `author`, `description`, `created_at`, `updated_at` FROM books ORDER BY title
9+
SELECT `id`, `title`, `author`, `description`, `created_at`, `updated_at`
10+
FROM books
11+
ORDER BY `title`;
12+
13+
-- name: GetBooksByAuthor :many
14+
-- SELECT `id`, `title`, `author`, `description`, `created_at`, `updated_at` FROM books WHERE author = ?
15+
SELECT `id`, `title`, `author`, `description`, `created_at`, `updated_at`
16+
FROM books
17+
WHERE `author` = ?;
18+
19+
-- name: CreateBook :execresult
20+
-- INSERT INTO books (`title`, `author`, `description`) VALUES (?, ?, ?)
21+
INSERT INTO books (`title`, `author`, `description`)
22+
VALUES (?, ?, ?);
23+
24+
-- name: UpdateBook :execresult
25+
-- UPDATE books SET `title` = ?, `author` = ?, `description` = ?, `updated_at` = NOW() WHERE id = ?
26+
UPDATE books
27+
SET `title` = ?,
28+
`author` = ?,
29+
`description` = ?,
30+
`updated_at` = NOW()
31+
WHERE id = ?;
32+
33+
-- name: DeleteBook :exec
34+
-- DELETE FROM books WHERE id = ?
35+
DELETE FROM books
36+
WHERE id = ?;
37+
38+
-- name: CreateTag :execresult
39+
-- INSERT INTO tags (`name`) VALUES (?)
40+
INSERT INTO tags (`name`)
41+
VALUES (?);
42+
43+
-- name: GetTag :one
44+
-- SELECT `id`, `name`, `created_at`, `updated_at` FROM tags WHERE id = ?
45+
SELECT `id`, `name`, `created_at`, `updated_at`
46+
FROM tags
47+
WHERE id = ?;
48+
49+
-- name: AddBookTag :exec
50+
-- INSERT INTO book_tags (`book_id`, `tag_id`) VALUES (?, ?)
51+
INSERT INTO book_tags (`book_id`, `tag_id`)
52+
VALUES (?, ?);
53+
54+
-- name: AddBookTags :exec
55+
-- INSERT INTO book_tags (`book_id`, `tag_id`) VALUES (?, ?)
56+
-- @bulk-for AddBookTag
57+
INSERT INTO book_tags (`book_id`, `tag_id`)
58+
VALUES (?, ?);
59+
60+
-- name: GetBookTags :many
61+
-- SELECT t.`id`, t.`name`, t.`created_at`, t.`updated_at` FROM tags t INNER JOIN book_tags bt ON t.id = bt.tag_id WHERE bt.book_id = ?
62+
SELECT t.`id`, t.`name`, t.`created_at`, t.`updated_at`
63+
FROM tags t
64+
INNER JOIN book_tags bt ON t.id = bt.tag_id
65+
WHERE bt.book_id = ?;

example/db/query.postgres.sql

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
-- name: GetBook :one
2+
-- SELECT "id", "title", "author", "description", "created_at", "updated_at" FROM books WHERE id = $1
3+
SELECT "id", "title", "author", "description", "created_at", "updated_at"
4+
FROM books
5+
WHERE id = $1;
6+
7+
-- name: ListBooks :many
8+
-- SELECT "id", "title", "author", "description", "created_at", "updated_at" FROM books ORDER BY title
9+
SELECT "id", "title", "author", "description", "created_at", "updated_at"
10+
FROM books
11+
ORDER BY title;
12+
13+
-- name: GetBooksByAuthor :many
14+
-- SELECT "id", "title", "author", "description", "created_at", "updated_at" FROM books WHERE author = $1
15+
SELECT "id", "title", "author", "description", "created_at", "updated_at"
16+
FROM books
17+
WHERE author = $1;
18+
19+
-- name: CreateBook :one
20+
-- INSERT INTO books ("title", "author", "description") VALUES ($1, $2, $3) RETURNING "id", "title", "author", "description", "created_at", "updated_at"
21+
INSERT INTO books ("title", "author", "description")
22+
VALUES ($1, $2, $3)
23+
RETURNING "id", "title", "author", "description", "created_at", "updated_at";
24+
25+
-- name: UpdateBook :one
26+
-- UPDATE books SET "title" = $1, "author" = $2, "description" = $3, "updated_at" = NOW() WHERE id = $4 RETURNING "id", "title", "author", "description", "created_at", "updated_at"
27+
UPDATE books
28+
SET "title" = $1,
29+
"author" = $2,
30+
"description" = $3,
31+
"updated_at" = NOW()
32+
WHERE id = $4
33+
RETURNING "id", "title", "author", "description", "created_at", "updated_at";
34+
35+
-- name: DeleteBook :exec
36+
-- DELETE FROM books WHERE id = $1
37+
DELETE FROM books
38+
WHERE id = $1;
39+
40+
-- name: CreateTag :one
41+
-- INSERT INTO tags ("name") VALUES ($1) RETURNING "id", "name", "created_at", "updated_at"
42+
INSERT INTO tags ("name")
43+
VALUES ($1)
44+
RETURNING "id", "name", "created_at", "updated_at";
45+
46+
-- name: GetTag :one
47+
-- SELECT "id", "name", "created_at", "updated_at" FROM tags WHERE id = $1
48+
SELECT "id", "name", "created_at", "updated_at"
49+
FROM tags
50+
WHERE id = $1;
51+
52+
-- name: AddBookTag :exec
53+
-- INSERT INTO book_tags ("book_id", "tag_id") VALUES ($1, $2)
54+
INSERT INTO book_tags ("book_id", "tag_id")
55+
VALUES ($1, $2);
56+
57+
-- name: AddBookTags :exec
58+
-- INSERT INTO book_tags ("book_id", "tag_id") VALUES (unnest($1::bigint[]), unnest($2::bigint[]))
59+
-- @bulk-for AddBookTag
60+
INSERT INTO book_tags ("book_id", "tag_id")
61+
VALUES (unnest($1::bigint[]), unnest($2::bigint[]));
62+
63+
-- name: GetBookTags :many
64+
-- SELECT t."id", t."name", t."created_at", t."updated_at" FROM tags t INNER JOIN book_tags bt ON t.id = bt.tag_id WHERE bt.book_id = $1
65+
SELECT t."id", t."name", t."created_at", t."updated_at"
66+
FROM tags t
67+
INNER JOIN book_tags bt ON t.id = bt.tag_id
68+
WHERE bt.book_id = $1;

example/db/query.sqlite.sql

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
-- name: GetBook :one
2+
-- SELECT "id", "title", "author", "description", "created_at", "updated_at" FROM books WHERE id = ?
3+
SELECT "id", "title", "author", "description", "created_at", "updated_at"
4+
FROM books
5+
WHERE id = ?;
6+
7+
-- name: ListBooks :many
8+
-- SELECT "id", "title", "author", "description", "created_at", "updated_at" FROM books ORDER BY title
9+
SELECT "id", "title", "author", "description", "created_at", "updated_at"
10+
FROM books
11+
ORDER BY title;
12+
13+
-- name: GetBooksByAuthor :many
14+
-- SELECT "id", "title", "author", "description", "created_at", "updated_at" FROM books WHERE author = ?
15+
SELECT "id", "title", "author", "description", "created_at", "updated_at"
16+
FROM books
17+
WHERE author = ?;
18+
19+
-- name: CreateBook :one
20+
-- INSERT INTO books ("title", "author", "description") VALUES (?, ?, ?) RETURNING "id", "title", "author", "description", "created_at", "updated_at"
21+
INSERT INTO books ("title", "author", "description")
22+
VALUES (?, ?, ?)
23+
RETURNING "id", "title", "author", "description", "created_at", "updated_at";
24+
25+
-- name: UpdateBook :one
26+
-- UPDATE books SET "title" = ?, "author" = ?, "description" = ?, "updated_at" = CURRENT_TIMESTAMP WHERE id = ? RETURNING "id", "title", "author", "description", "created_at", "updated_at"
27+
UPDATE books
28+
SET "title" = ?,
29+
"author" = ?,
30+
"description" = ?,
31+
"updated_at" = CURRENT_TIMESTAMP
32+
WHERE id = ?
33+
RETURNING "id", "title", "author", "description", "created_at", "updated_at";
34+
35+
-- name: DeleteBook :exec
36+
-- DELETE FROM books WHERE id = ?
37+
DELETE FROM books
38+
WHERE id = ?;
39+
40+
-- name: CreateTag :one
41+
-- INSERT INTO tags ("name") VALUES (?) RETURNING "id", "name", "created_at", "updated_at"
42+
INSERT INTO tags ("name")
43+
VALUES (?)
44+
RETURNING "id", "name", "created_at", "updated_at";
45+
46+
-- name: GetTag :one
47+
-- SELECT "id", "name", "created_at", "updated_at" FROM tags WHERE id = ?
48+
SELECT "id", "name", "created_at", "updated_at"
49+
FROM tags
50+
WHERE id = ?;
51+
52+
-- name: AddBookTag :exec
53+
-- INSERT INTO book_tags ("book_id", "tag_id") VALUES (?, ?)
54+
INSERT INTO book_tags ("book_id", "tag_id")
55+
VALUES (?, ?);
56+
57+
-- name: AddBookTags :exec
58+
-- INSERT INTO book_tags ("book_id", "tag_id") VALUES (?, ?)
59+
-- @bulk-for AddBookTag
60+
INSERT INTO book_tags ("book_id", "tag_id")
61+
VALUES (?, ?);
62+
63+
-- name: GetBookTags :many
64+
-- SELECT t."id", t."name", t."created_at", t."updated_at" FROM tags t INNER JOIN book_tags bt ON t.id = bt.tag_id WHERE bt.book_id = ?
65+
SELECT t."id", t."name", t."created_at", t."updated_at"
66+
FROM tags t
67+
INNER JOIN book_tags bt ON t.id = bt.tag_id
68+
WHERE bt.book_id = ?;

example/db/schema/mysql.sql

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
CREATE TABLE books (
2+
id BIGINT AUTO_INCREMENT PRIMARY KEY,
3+
title VARCHAR(255) NOT NULL,
4+
author VARCHAR(255) NOT NULL,
5+
description TEXT,
6+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
7+
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
8+
);
9+
10+
CREATE TABLE tags (
11+
id BIGINT AUTO_INCREMENT PRIMARY KEY,
12+
name VARCHAR(255) NOT NULL UNIQUE,
13+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
14+
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
15+
);
16+
17+
CREATE TABLE book_tags (
18+
book_id BIGINT NOT NULL,
19+
tag_id BIGINT NOT NULL,
20+
PRIMARY KEY (book_id, tag_id),
21+
FOREIGN KEY (book_id) REFERENCES books(id) ON DELETE CASCADE,
22+
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
23+
);

example/db/schema/postgres.sql

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
CREATE TABLE books (
2+
id BIGSERIAL PRIMARY KEY,
3+
title TEXT NOT NULL,
4+
author TEXT NOT NULL,
5+
description TEXT,
6+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
7+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
8+
);
9+
10+
CREATE TABLE tags (
11+
id BIGSERIAL PRIMARY KEY,
12+
name TEXT NOT NULL UNIQUE,
13+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
14+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
15+
);
16+
17+
CREATE TABLE book_tags (
18+
book_id BIGINT NOT NULL REFERENCES books(id) ON DELETE CASCADE,
19+
tag_id BIGINT NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
20+
PRIMARY KEY (book_id, tag_id)
21+
);

example/db/schema/sqlite.sql

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
CREATE TABLE books (
2+
id INTEGER PRIMARY KEY AUTOINCREMENT,
3+
title TEXT NOT NULL,
4+
author TEXT NOT NULL,
5+
description TEXT,
6+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
7+
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
8+
);
9+
10+
CREATE TABLE tags (
11+
id INTEGER PRIMARY KEY AUTOINCREMENT,
12+
name TEXT NOT NULL UNIQUE,
13+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
14+
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
15+
);
16+
17+
CREATE TABLE book_tags (
18+
book_id INTEGER NOT NULL REFERENCES books(id) ON DELETE CASCADE,
19+
tag_id INTEGER NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
20+
PRIMARY KEY (book_id, tag_id)
21+
);

example/go.mod

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
module github.com/kalbasit/sqlc-multi-db/example
2+
3+
go 1.25.5
4+
5+
tool github.com/kalbasit/sqlc-multi-db
6+
7+
require (
8+
github.com/go-sql-driver/mysql v1.9.2
9+
github.com/jackc/pgx/v5 v5.7.4
10+
github.com/mattn/go-sqlite3 v1.14.28
11+
)
12+
13+
require (
14+
filippo.io/edwards25519 v1.1.0 // indirect
15+
github.com/google/go-cmp v0.7.0 // indirect
16+
github.com/jackc/pgpassfile v1.0.0 // indirect
17+
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
18+
github.com/jackc/puddle/v2 v2.2.2 // indirect
19+
github.com/jinzhu/inflection v1.0.0 // indirect
20+
github.com/kalbasit/sqlc-multi-db v0.0.0 // indirect
21+
golang.org/x/crypto v0.31.0 // indirect
22+
golang.org/x/mod v0.33.0 // indirect
23+
golang.org/x/sync v0.19.0 // indirect
24+
golang.org/x/text v0.21.0 // indirect
25+
golang.org/x/tools v0.42.0 // indirect
26+
mvdan.cc/gofumpt v0.9.2 // indirect
27+
)
28+
29+
replace github.com/kalbasit/sqlc-multi-db => ../

0 commit comments

Comments
 (0)