Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 30 additions & 13 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,23 @@ go test -v ./internal/config -run TestConfig
go test -race ./internal/config
```

## Test Types
## Testing Strategy

### 1. Unit Tests
**Cover new work with end-to-end tests, not unit tests.** A change to the
compiler, an engine, the analysis core or codegen is exercised by running sqlc
the way a user does — a schema, a query file and a committed golden output —
so the test says what sqlc produces rather than what an internal function
returns. Internal APIs move around; the SQL that goes in and the output that
comes out is the contract worth pinning down.

- **Location:** Throughout the codebase as `*_test.go` files
- **Run without:** Database or external dependencies
- **Examples:**
- `/internal/config/config_test.go` - Configuration parsing
- `/internal/compiler/selector_test.go` - Compiler logic
- `/internal/metadata/metadata_test.go` - Query metadata parsing
Adding coverage means adding a directory under `/internal/endtoend/testdata/`,
not a `*_test.go` next to the code. Reach for a unit test only when the
behavior genuinely cannot be reached through the CLI, and say why in the test.

### 2. End-to-End Tests
Some `*_test.go` files predate this and remain; they are not a precedent for
new ones.

### End-to-End Tests

- **Location:** `/internal/endtoend/`
- **Requirements:** `--tags=examples` flag and running databases
Expand All @@ -111,7 +116,15 @@ go test -race ./internal/config
- `TestJsonSchema` - JSON schema validation
- `TestExamplesVet` - Static analysis tests

### 3. Example Tests
A case is a directory holding the inputs and the expected output. `exec.json`
names the command and its arguments — omit it and the case runs `generate`,
comparing the generated files against the ones committed alongside; give it
`{"command": "analyze", "args": [...]}` and the case compares the command's
stdout against `stdout.txt`. A case that is expected to fail commits its
`stderr.txt`. Regenerate a golden by running the command in its directory and
writing the output back over the committed file.

### Example Tests

- **Location:** `/examples/` directory
- **Requirements:** Tagged with "examples", requires live databases
Expand Down Expand Up @@ -183,6 +196,9 @@ MYSQL_SERVER_URI="root:mysecretpassword@tcp(127.0.0.1:3306)/mysql?multiStatement
- `/postgresql/` - PostgreSQL parser and converter
- `/dolphin/` - MySQL parser (uses TiDB parser)
- `/sqlite/` - SQLite parser
- `<engine>/dialect/` - The engine's type system and standard library, as
JSONL read by `/internal/core/seed`
- `/internal/core/` - The analysis core: catalog, analyzer and dialect seeds
- `/internal/compiler/` - Query compilation logic
- `/internal/codegen/` - Code generation for different languages
- `/internal/config/` - Configuration file parsing
Expand Down Expand Up @@ -232,9 +248,10 @@ go run ./cmd/sqlc-test-setup start
## Tips for Contributors

1. **Run tests before committing:** `go test --tags=examples -timeout 20m ./...`
2. **Check for race conditions:** Use `-race` flag when testing concurrent code
3. **Use specific package tests:** Faster iteration during development
4. **Read existing tests:** Good examples in `/internal/engine/postgresql/*_test.go`
2. **Cover new behavior end to end:** Add a case under `/internal/endtoend/testdata/`
3. **Check for race conditions:** Use `-race` flag when testing concurrent code
4. **Iterate on one case:** `go test ./internal/endtoend -run 'TestReplay/base/<case>'`
5. **Read existing cases:** `/internal/endtoend/testdata/` has one per feature

## Git Workflow

Expand Down
7 changes: 7 additions & 0 deletions docs/howto/analyze.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ Unlike [`generate`](generate.md), this command does not require a configuration
file and does not connect to a database. It uses sqlc's native static analysis
to infer types directly from the provided schema.

Every dialect is analyzed by the same engine-neutral analysis core: the schema
is loaded into a catalog seeded with the dialect's types, operators and
functions, and each query is resolved against it. `generate` still uses each
engine's own analysis path, so the two can report a type differently — most
visibly, `analyze` reports type names as the catalog stores them, in lower
case.

## Usage

```sh
Expand Down
13 changes: 13 additions & 0 deletions internal/cache/cas.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@ func (c *CAS) path(d Digest) string {
return filepath.Join("cas", d.Hash[:2], d.Hash)
}

// Filename returns the path of a stored blob, for a consumer that needs the
// file rather than its bytes — SQLite, for one, opens a database by name. A
// blob is named after the hash of its contents, so the file at this path never
// changes and any number of processes may read it at once.
//
// It reports false when the blob is not stored.
func (c *CAS) Filename(d Digest) (string, bool) {
if !c.Contains(d) {
return "", false
}
return filepath.Join(c.root.Name(), c.path(d)), true
}

// createTemp creates a staging file under tmp/ in the cache root, returning
// the open file and its root-relative name.
func (c *CAS) createTemp(prefix string) (*os.File, string, error) {
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ Examples:
parserOpts := opts.Parser{}

ctx := cmd.Context()
c, err := compiler.NewCompiler(sql, combo, parserOpts)
c, err := compiler.NewCompiler(sql, combo, parserOpts, compiler.WithCoreAnalysis())
if err != nil {
return fmt.Errorf("error creating compiler: %w", err)
}
Expand Down
Loading
Loading