Skip to content

Commit 3df6860

Browse files
committed
hide private funcs in migrations, add more tests, migrations example and change README
1 parent 64ac08d commit 3df6860

27 files changed

Lines changed: 971 additions & 347 deletions

README.md

Lines changed: 93 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,12 @@
44
[![Go CI](https://github.com/SennovE/qrafter/actions/workflows/go.yml/badge.svg?branch=main)](https://github.com/SennovE/qrafter/actions/workflows/go.yml)
55
[![Go Report Card](https://goreportcard.com/badge/github.com/SennovE/qrafter)](https://goreportcard.com/report/github.com/SennovE/qrafter)
66

7-
**qrafter is a fluent, type-safe SQL query builder for Go — no ORM, no codegen, just typed SQL-shaped Go.**
7+
qrafter is a fluent, type-safe SQL toolkit for Go. It gives you typed query
8+
composition, DDL builders, schema introspection, and generated Go migrations
9+
without becoming an ORM.
810

9-
qrafter helps you build parameterized SQL from typed Go table structs.
10-
You define tables once, compose queries from typed columns, and render SQL plus
11-
driver arguments for `database/sql`, `sqlx`, and similar packages.
12-
13-
It is designed for Go developers who want a Go-style way to build explicit SQL: keep queries readable and under control, while avoiding fragile hand-written column names, placeholders, and query fragments.
14-
15-
## Why qrafter?
16-
17-
Use qrafter when you want:
18-
19-
- Typed table and column references with `qrafter.Column[T]`
20-
- SQL that still looks and feels like SQL
21-
- Parameterized queries instead of interpolated user values
22-
- Dialect-aware identifier quoting and placeholders
23-
- Compatibility with your existing database driver and connection pool
24-
- A lightweight query builder instead of a full ORM
25-
- No code generation step in your build workflow
11+
Use qrafter when you want explicit SQL, typed table/column references,
12+
database/sql compatibility, and migration files you can read and edit.
2613

2714
## Install
2815

@@ -81,80 +68,88 @@ LIMIT 10
8168
[18 Alice]
8269
```
8370

84-
## Larger examples
71+
## Migrations
8572

86-
More application-shaped examples live in [examples](examples):
73+
The migration tool lives in `cmd/qrafter-migrations`. It creates a Go config
74+
file, compares the configured schema with the live database, generates Go
75+
migration files, registers them, and applies or reverts them.
8776

88-
* [database_sql](examples/database_sql) shows repository-style code with
89-
`database/sql`, context-aware execution, and typed query rendering.
90-
* [reporting](examples/reporting) builds a larger analytical query with joins,
91-
grouping, a CTE, and a window function.
92-
* [schema](examples/schema) renders DDL for tables, constraints, indexes, and
93-
table alterations.
77+
Show CLI help:
9478

95-
## How it works
79+
```sh
80+
go run github.com/SennovE/qrafter/cmd/qrafter-migrations@latest help
81+
go run github.com/SennovE/qrafter/cmd/qrafter-migrations@latest help revision
82+
```
9683

97-
A qrafter table is a Go struct with typed column fields:
84+
Create `./migrations/qrafter_config.go`:
9885

99-
```go
100-
type User struct {
101-
q.Table `table:"users"`
102-
103-
ID q.Column[int] `db:"id"`
104-
UserName q.Column[string]
105-
Age q.Column[int]
106-
}
86+
```sh
87+
go run github.com/SennovE/qrafter/cmd/qrafter-migrations@latest init \
88+
--dir ./migrations \
89+
--driver-import github.com/lib/pq \
90+
--driver postgres \
91+
--dialect postgres \
92+
--dsn "postgres://app_user:app_password@localhost:5432/app_db?sslmode=disable"
10793
```
10894

109-
`q.MustNewTable[User]()` binds the struct fields to SQL table and column names.
110-
Queries are then composed from those typed columns and rendered for a selected
111-
SQL dialect.
95+
The generated config is regular Go code. Add your table configs to
96+
`desiredSchema`:
97+
98+
```go
99+
package migrations
100+
101+
import (
102+
"github.com/SennovE/qrafter/dialect"
103+
qmig "github.com/SennovE/qrafter/migrations"
104+
_ "github.com/lib/pq"
105+
)
112106

113-
Field names are converted into column names automatically, or you can override
114-
them with `db` tags.
107+
var MigrationConfig = qmig.MigrationToolConfig{
108+
DriverName: "postgres",
109+
DataSourceName: "postgres://app_user:app_password@localhost:5432/app_db?sslmode=disable",
110+
Introspector: qmig.NewPostgreSQL(qmig.WithSchemas("public")),
111+
Dialect: dialect.PostgreSQL{},
112+
Desired: desiredSchema,
113+
VersionTable: qmig.DefaultMigrationVersionTable,
114+
}
115115

116-
## When to use it
116+
var Registry = []qmig.Migration{
117+
}
117118

118-
qrafter is useful when you want typed query composition while still keeping
119-
control over the generated SQL.
119+
func desiredSchema(d dialect.Renderer) qmig.Schema {
120+
var schema qmig.Schema
121+
qmig.RegisterTable[User](&schema, d)
122+
return schema
123+
}
124+
```
120125

121-
Good fits:
126+
Generate a migration from the live database diff:
122127

123-
* services that already use `database/sql` or `sqlx`
124-
* projects that prefer explicit SQL over ORM abstractions
125-
* codebases where query fragments need to be composed safely
126-
* applications that want typed table and column references without codegen
128+
```sh
129+
go run github.com/SennovE/qrafter/cmd/qrafter-migrations@latest revision \
130+
--dir ./migrations \
131+
--comment create_users
132+
```
127133

128-
Less ideal fits:
134+
This creates a timestamped Go file and appends it to `Registry` in
135+
`qrafter_config.go`. Generated migrations return `ddl.Statements`, so you can
136+
edit them and add custom statements such as `qddl.RawSQL("CREATE EXTENSION ...")`
137+
when needed.
129138

130-
* projects that want a full ORM
131-
* applications that expect automatic relationship loading
132-
* teams that prefer writing raw SQL files and generating Go code from them
133-
* projects that need schema migrations as part of the same tool
139+
Apply or revert registered migrations:
134140

135-
## Features
141+
```sh
142+
go run github.com/SennovE/qrafter/cmd/qrafter-migrations@latest up --dir ./migrations --to head
143+
go run github.com/SennovE/qrafter/cmd/qrafter-migrations@latest down --dir ./migrations --to base
144+
```
136145

137-
* Typed table structs with `qrafter.Column[T]`
138-
* Table configuration via embedded `qrafter.Table` or `TableConfig()`
139-
* Automatic column binding from field names or `db` tags
140-
* Custom field-to-column mapping through `qrafter.NameMapper`
141-
* Dialect-aware identifier quoting and placeholders
142-
* Human-readable multiline SQL rendering
143-
* Parameterized `SELECT`, joins, grouping, ordering, limits, and offsets
144-
* Parameterized `INSERT` with `VALUES`, `DEFAULT VALUES`, `INSERT ... SELECT`, and `RETURNING`
145-
* Parameterized `UPDATE` with `SET`, `FROM`, `WHERE`, CTEs, and `RETURNING`
146-
* Parameterized `DELETE` with `WHERE`, `USING`, CTEs, and `RETURNING`
147-
* CTEs and recursive CTEs
148-
* Compound queries such as `UNION` and `UNION ALL`
149-
* Aggregates and window functions
150-
* DDL builders for tables, columns, constraints, and indexes
151-
* Centralized SQL compiler with dialect override hooks for database-specific syntax
152-
* `database/sql` and `sqlx`-friendly scanning helpers
146+
The apply command stores the current version in
147+
`qrafter_schema_version` by default. Override it in config with
148+
`VersionTable` or from CLI with `--version-table`.
153149

154-
## DDL
150+
## DDL Builders
155151

156-
Schema statements live in the separate `ddl` package so the root package can
157-
stay focused on query building:
152+
Schema statements live in the `ddl` package:
158153

159154
```go
160155
sql, err := ddl.CreateTable("users").
@@ -166,87 +161,39 @@ sql, err := ddl.CreateTable("users").
166161
Render(dialect.PostgreSQL{})
167162
```
168163

169-
DDL rendering returns an error when a dialect cannot safely render a requested
170-
feature, such as SQLite column type changes or MySQL partial indexes.
171-
172-
Constraints and indexes are built explicitly from table and column names:
173-
174-
```go
175-
sql, err := ddl.Statements{
176-
ddl.CreateTable("users").
177-
Columns(
178-
ddl.Column("id", ddl.BigSerial()).PrimaryKey(),
179-
ddl.Column("org_id", ddl.BigInt()).NotNull(),
180-
ddl.Column("email", ddl.VarChar(320)).NotNull(),
181-
).
182-
Constraints(
183-
ddl.Unique("email").Named("users_email_key"),
184-
ddl.ForeignKey("org_id").
185-
References("orgs", "id").
186-
OnDelete(ddl.Cascade),
187-
),
188-
ddl.CreateIndex("users_email_idx").
189-
IfNotExists().
190-
OnCols("users", "email"),
191-
}.Render(dialect.PostgreSQL{})
192-
```
164+
DDL rendering is dialect-aware and returns an error when a dialect cannot safely
165+
render a requested feature.
193166

194167
## Dialects
195168

196169
qrafter currently includes:
197170

198-
* `dialect.BaseDialect` for ANSI-style double-quoted identifiers and `?` placeholders
199-
* `dialect.PostgreSQL` for PostgreSQL-style `$1`, `$2`, ... placeholders
200-
* `dialect.MySQL` for backtick-quoted identifiers, MySQL `LIMIT`/`OFFSET`,
201-
empty-row inserts, multi-table `UPDATE`/`DELETE`, and NULL ordering emulation
202-
* `dialect.SQLite` for SQLite literals, `LIMIT`/`OFFSET`, and fail-fast
203-
handling for unsupported `DELETE USING`
204-
* `dialect.Oracle` for Oracle placeholders, boolean literals,
205-
`OFFSET`/`FETCH`, and Oracle-specific DDL overrides
206-
* `dialect.SQLServer` for bracket-quoted identifiers, `@p1` placeholders,
207-
`OFFSET`/`FETCH`, NULL ordering emulation, and SQL Server DDL overrides
208-
209-
Rendering is intentionally centralized. Query and DDL builders store statement
210-
state; the compiler renders statements, expressions, clauses, and DDL nodes; a
211-
dialect supplies primitive rules such as identifier quoting, literals,
212-
placeholders, and `LIMIT`/`OFFSET`, and can override specific compiler nodes
213-
with `CompileNode`.
214-
215-
New dialects can start with the primitive methods and then override focused
216-
nodes for features such as `RETURNING`, `UPDATE` sources, `DELETE` sources,
217-
joins, default inserts, NULL ordering, partial indexes, and dialect-specific
218-
`ALTER TABLE` forms.
219-
220-
New dialects can be added by implementing `dialect.Renderer`.
221-
222-
## Comparison
223-
224-
| Approach | Good when | Tradeoff |
225-
| ------------------- | ----------------------------------------------------- | ------------------------------------------------------------------- |
226-
| Raw `database/sql` | You want full control over every query | SQL strings, placeholders, and column names are maintained manually |
227-
| SQL code generation | You want generated Go code from SQL files | Adds a generation step and a SQL-first workflow |
228-
| ORM | You want high-level model and relationship management | SQL can become less explicit and harder to control |
229-
| qrafter | You want typed SQL-shaped Go without ORM or codegen | It is a lightweight query builder, not a full database framework |
230-
231-
## Project status
232171

233-
qrafter is pre-v1. The API may still change while the package evolves.
172+
dialect | DML | DDL | migrations
173+
----------- |:---:|:---:|:----------:
174+
BaseDialect | <ul><li>- [x] </li></ul> | <ul><li>- [x] </li></ul> | -
175+
PostgreSQL | <ul><li>- [x] </li></ul> | <ul><li>- [x] </li></ul> | <ul><li>- [x] </li></ul>
176+
MySQL | <ul><li>- [x] </li></ul> | <ul><li>- [x] </li></ul> | <ul><li>- [ ] </li></ul>
177+
SQLite | <ul><li>- [x] </li></ul> | <ul><li>- [x] </li></ul> | <ul><li>- [ ] </li></ul>
178+
Oracle | <ul><li>- [x] </li></ul> | <ul><li>- [x] </li></ul> | <ul><li>- [ ] </li></ul>
179+
SQLServer | <ul><li>- [x] </li></ul> | <ul><li>- [x] </li></ul> | <ul><li>- [ ] </li></ul>
234180

235-
Feedback is especially welcome around:
181+
## Examples
236182

237-
* API naming
238-
* query composition ergonomics
239-
* dialect support
240-
* real-world usage with `database/sql` and `sqlx`
183+
More application-shaped examples live in [examples](examples):
241184

242-
## Contributing
185+
- [database_sql](examples/database_sql) shows repository-style code with
186+
`database/sql`.
187+
- [reporting](examples/reporting) builds a larger analytical query with joins,
188+
grouping, a CTE, and a window function.
189+
- [schema](examples/schema) renders DDL for tables, constraints, indexes, and
190+
table alterations.
191+
- [migrations](examples/migrations) is a standalone module with Docker Compose
192+
that generates, applies, and reverts qrafter migrations against PostgreSQL.
243193

244-
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for the local
245-
development workflow and pull request guidelines.
194+
## Project Status
246195

247-
Good first areas to explore:
196+
qrafter is pre-v1. The API may still change while the package evolves.
248197

249-
* Add examples for common query patterns
250-
* Improve dialect coverage
251-
* Expand integration tests
252-
* Polish package documentation on pkg.go.dev
198+
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for the local
199+
development workflow and pull request guidelines.

examples/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,16 @@ dialect, including PostgreSQL, MySQL, SQLite, Oracle, and SQL Server:
3030
```sh
3131
go run ./examples/schema
3232
```
33+
34+
## migrations
35+
36+
`migrations` is a standalone module with a PostgreSQL Docker Compose file and a
37+
ready qrafter migration config. It shows the full flow: start a database,
38+
generate a migration, apply it, and revert it.
39+
40+
```sh
41+
cd examples/migrations
42+
docker compose up -d
43+
go run github.com/SennovE/qrafter/cmd/qrafter-migrations revision --dir ./migrations --comment create_organizations_and_users
44+
go run github.com/SennovE/qrafter/cmd/qrafter-migrations up --dir ./migrations --to head
45+
```

examples/migrations/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.gocache/
2+
.gotmp/
3+
.qrafter/

examples/migrations/README.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# qrafter migrations example
2+
3+
This is a small standalone module that shows the full migration loop:
4+
5+
1. start PostgreSQL in Docker;
6+
2. describe the desired schema in Go;
7+
3. generate a migration from the live database diff;
8+
4. apply it;
9+
5. revert it.
10+
11+
The example uses a local `replace` in `go.mod`, so commands run against the
12+
checkout you are editing.
13+
14+
## Start PostgreSQL
15+
16+
```sh
17+
docker compose up -d
18+
```
19+
20+
The database listens on `localhost:55432`:
21+
22+
```text
23+
postgres://qrafter:qrafter@localhost:55432/qrafter_demo?sslmode=disable
24+
```
25+
26+
## Generate a Migration
27+
28+
The desired schema lives in [`migrations/tables.go`](migrations/tables.go), and
29+
the qrafter config lives in
30+
[`migrations/qrafter_config.go`](migrations/qrafter_config.go).
31+
32+
Run the migration tool from this directory:
33+
34+
```sh
35+
go run github.com/SennovE/qrafter/cmd/qrafter-migrations revision \
36+
--dir ./migrations \
37+
--comment create_organizations_and_users
38+
```
39+
40+
This creates a timestamped Go migration file in `./migrations` and appends it to
41+
`Registry` in `qrafter_config.go`.
42+
43+
## Apply and Revert
44+
45+
Apply all registered migrations:
46+
47+
```sh
48+
go run github.com/SennovE/qrafter/cmd/qrafter-migrations up \
49+
--dir ./migrations \
50+
--to head
51+
```
52+
53+
Revert everything back to base:
54+
55+
```sh
56+
go run github.com/SennovE/qrafter/cmd/qrafter-migrations down \
57+
--dir ./migrations \
58+
--to base
59+
```
60+
61+
qrafter stores the current version in `qrafter_schema_version`.
62+
63+
## Try Another Change
64+
65+
Edit `migrations/tables.go`, for example add a column to `User`, then run:
66+
67+
```sh
68+
go run github.com/SennovE/qrafter/cmd/qrafter-migrations revision \
69+
--dir ./migrations \
70+
--comment add_user_column
71+
72+
go run github.com/SennovE/qrafter/cmd/qrafter-migrations up \
73+
--dir ./migrations \
74+
--to head
75+
```
76+
77+
Generated migrations are regular Go files. You can edit them and add custom SQL
78+
with `qddl.RawSQL(...)`.
79+
80+
## Reset
81+
82+
```sh
83+
docker compose down -v
84+
```

0 commit comments

Comments
 (0)