Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ linters:
path: _test\.go
- linters:
- mnd
path: ^internal/workloads/
path: ^workloads/(execute_sql|simple|tpcb|tpcc|tpcds|tpch)/
- linters:
- unused
path: bind.go
Expand Down
15 changes: 10 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ with `go generate ./pkg/config`. There is no application protobuf generation ste
and protobuf remains only as an indirect dependency of external SDKs. Load-time
generation is plain Go under `pkg/gen/` (see `docs/parallelism.md`).

**Embedded FS rebuild rule:** `workloads/` is `//go:embed *` (SQL/JSON/MD only).
**Embedded FS rebuild rule:** each asset-bearing package under `workloads/<name>/`
embeds its own SQL/JSON/README files and registers them with the shared catalog.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
If you pass a workload by short name (`tpcc/tx`), the binary serves its SQL from
the embedded snapshot. Edits to `workloads/*.sql` on disk have **no effect** until
`make build` reruns.
Expand All @@ -79,15 +80,16 @@ Resolution order for SQL files: **cwd → `~/.stroppy/` → embedded**.
| `cmd/stroppy/` | entrypoint (`main.go` blank-imports the drivers) + cobra subcommands: run, probe, version |
| `cmd/stroppy/commands/run/` | arg parsing, driver/env/step resolution, dispatch to `bench.Run` |
| `pkg/bench/` | Go-native engine: `Workload` interface, `Run`, scenario executor, VU/Bench SDK, metrics sink + summary |
| `internal/workloads/` | the Go workloads (simple, tpcb, tpcc, tpch, tpcds, execute_sql); aggregated by blank import |
| `workloads/<name>/` | one package per built-in workload, containing Go implementation/tests plus owned SQL/JSON/README assets |
| `workloads/all/` | explicit blank-import aggregation for built-in workload registration |
| `pkg/driver/dispatcher.go` | driver registry: `RegisterDriver()` + `Dispatch()` |
| `pkg/driver/{postgres,mysql,picodata,ydb,noop,csv}/` | driver implementations |
| `pkg/driver/sqldriver/` | shared sql.DB-backed base (mysql, ydb use this) |
| `pkg/gen/` | imperative generation primitives: Root/Domain/Field scalars, reusable typed Batches, IndexedSource, Permute/SplitMix64 |
| `pkg/datagen/` | row-production seam: `source` (Partitionable/RowSource) + canonical TPC-DS/TPC-H generator adapters (`tpcdsgen`, `tpchgen`) |
| `internal/runner/` | run-config merge, env override parsing, driver presets, config-file load |
| `pkg/config/` | plain-Go application config types + strict recursive JSON normalizer; schema source for `docs/jsonschema/run.schema.json` |
| `workloads/` | embedded SQL/JSON workloads: tpcb, tpcc, tpch, tpcds |
| `workloads/` | shared embedded-asset registry and catalog |
| `docs/parallelism.md` | InsertRequest parallelism contract and tuning |

## Drivers
Expand Down Expand Up @@ -220,8 +222,11 @@ Section layout (must be identical across dialects):
```

Each Go workload implements the `bench.Workload` interface (`Setup`, `Iterate`,
`Teardown`) in `internal/workloads/<name>/`. TPC-B and TPC-C each ship two
registered variants:
`Teardown`) in `workloads/<name>/`, beside its tests and owned assets. Asset-bearing
packages embed only their local SQL/JSON/README files, register the filesystem with
`workloads.Register`, and add a blank import to `workloads/all/import.go`. Contract
tests in each package pin required files, sections, and named queries. TPC-B and
TPC-C each ship two registered variants:
- `procs` — calls stored procs via the `workload_procs` section; pg + mysql only
- `tx` — runs ordered DML steps inside `driver.beginTx()`; all SQL drivers (pg/mysql/pico/ydb)

Expand Down
6 changes: 3 additions & 3 deletions cmd/stroppy/commands/help/topic_datagen.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ CSV OUTPUT
REFERENCES

docs/parallelism.md InsertRequest parallelism contract and tuning
internal/workloads/simple/ Minimal Go workload
internal/workloads/tpcb/ Small relational workload
internal/workloads/tpch/ Stateful canonical generator
workloads/simple/ Minimal Go workload
workloads/tpcb/ Small relational workload
workloads/tpch/ Stateful canonical generator
pkg/gen/ Deterministic generation primitives
pkg/bench/query.go b.Insert entrypoint
`,
Expand Down
2 changes: 1 addition & 1 deletion cmd/stroppy/commands/probe/probe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
"strings"
"testing"

_ "github.com/stroppy-io/stroppy/internal/workloads"
"github.com/stroppy-io/stroppy/pkg/bench"
_ "github.com/stroppy-io/stroppy/workloads/all"
)

func TestJSONCatalogIncludesWorkloadSchemas(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion cmd/stroppy/commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import (
"github.com/stroppy-io/stroppy/cmd/stroppy/commands/probe"
"github.com/stroppy-io/stroppy/cmd/stroppy/commands/run"
"github.com/stroppy-io/stroppy/internal/version"
_ "github.com/stroppy-io/stroppy/internal/workloads"
"github.com/stroppy-io/stroppy/pkg/common/shutdown"
_ "github.com/stroppy-io/stroppy/workloads/all"
)

// appName is the binary / command name.
Expand Down
2 changes: 1 addition & 1 deletion cmd/stroppy/commands/run/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import (
"github.com/stretchr/testify/require"

"github.com/stroppy-io/stroppy/internal/runner"
_ "github.com/stroppy-io/stroppy/internal/workloads/simple"
"github.com/stroppy-io/stroppy/pkg/bench"
"github.com/stroppy-io/stroppy/pkg/config"
_ "github.com/stroppy-io/stroppy/pkg/driver/noop"
_ "github.com/stroppy-io/stroppy/workloads/simple"
)

func unsetLoggerEnv(t *testing.T) {
Expand Down
13 changes: 0 additions & 13 deletions internal/workloads/import.go

This file was deleted.

54 changes: 54 additions & 0 deletions pkg/bench/sql_file_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package bench

import (
"os"
"path/filepath"
"testing"
"testing/fstest"

"github.com/stroppy-io/stroppy/workloads"
)

func TestLoadSQLUsesLocalOverrideBeforeEmbeddedFallback(t *testing.T) {
const (
preset = "bench-sql-resolution-test"
fileName = "dialect.sql"
embeddedBody = "--+ query\n--= body\nSELECT 'embedded';\n"
localBody = "--+ query\n--= body\nSELECT 'local';\n"
)

workloads.Register(preset, fstest.MapFS{
fileName: &fstest.MapFile{Data: []byte(embeddedBody)},
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

t.Run("embedded fallback", func(t *testing.T) {
t.Chdir(t.TempDir())

assertSQLBody(t, preset, fileName, "SELECT 'embedded';")
})

t.Run("local workload file", func(t *testing.T) {
directory := t.TempDir()
t.Chdir(directory)
if err := os.MkdirAll(filepath.Join("workloads", preset), 0o755); err != nil {

Check failure on line 33 in pkg/bench/sql_file_test.go

View workflow job for this annotation

GitHub Actions / golang-ci

missing whitespace above this line (invalid statement above if) (wsl_v5)

Check failure on line 33 in pkg/bench/sql_file_test.go

View workflow job for this annotation

GitHub Actions / golang-ci

missing whitespace above this line (invalid statement above if) (wsl_v5)
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join("workloads", preset, fileName), []byte(localBody), 0o600); err != nil {

Check failure on line 36 in pkg/bench/sql_file_test.go

View workflow job for this annotation

GitHub Actions / golang-ci

missing whitespace above this line (invalid statement above if) (wsl_v5)

Check failure on line 36 in pkg/bench/sql_file_test.go

View workflow job for this annotation

GitHub Actions / golang-ci

missing whitespace above this line (invalid statement above if) (wsl_v5)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
t.Fatal(err)
}

assertSQLBody(t, preset, fileName, "SELECT 'local';")
})
}

func assertSQLBody(t *testing.T, preset, fileName, want string) {
t.Helper()

sql, err := LoadSQL(preset, fileName)
if err != nil {
t.Fatal(err)
}
if got, ok := sql.Query("query", "body"); !ok || got != want {

Check failure on line 51 in pkg/bench/sql_file_test.go

View workflow job for this annotation

GitHub Actions / golang-ci

missing whitespace above this line (invalid statement above if) (wsl_v5)

Check failure on line 51 in pkg/bench/sql_file_test.go

View workflow job for this annotation

GitHub Actions / golang-ci

missing whitespace above this line (invalid statement above if) (wsl_v5)
t.Fatalf("query body = %q, %v; want %q, true", got, ok, want)
}
}
11 changes: 11 additions & 0 deletions workloads/all/import.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Package all registers every built-in workload through blank imports.
package all

import (
_ "github.com/stroppy-io/stroppy/workloads/execute_sql"
_ "github.com/stroppy-io/stroppy/workloads/simple"
_ "github.com/stroppy-io/stroppy/workloads/tpcb"
_ "github.com/stroppy-io/stroppy/workloads/tpcc"
_ "github.com/stroppy-io/stroppy/workloads/tpcds"
_ "github.com/stroppy-io/stroppy/workloads/tpch"
)
10 changes: 8 additions & 2 deletions workloads/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package workloads

import (
"fmt"
"io/fs"
"sort"
"strings"
)
Expand All @@ -22,9 +23,14 @@ func Catalog() ([]PresetInfo, error) {
out := make([]PresetInfo, 0, len(presets))

for _, name := range presets {
entries, err := Content.ReadDir(name)
files, err := presetFiles(Preset(name))
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrUnknownPreset, name)
return nil, err
}

entries, err := fs.ReadDir(files, ".")
if err != nil {
return nil, fmt.Errorf("read preset %q: %w", name, err)
}

info := PresetInfo{Name: name}
Expand Down
21 changes: 13 additions & 8 deletions workloads/catalog_test.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
package workloads
package workloads_test

import "testing"
import (
"testing"

"github.com/stroppy-io/stroppy/workloads"
_ "github.com/stroppy-io/stroppy/workloads/all"
)

func TestCatalog(t *testing.T) {
catalog, err := Catalog()
catalog, err := workloads.Catalog()
if err != nil {
t.Fatalf("Catalog() error: %v", err)
}

if len(catalog) != len(AvailablePresets()) {
t.Fatalf("got %d presets, want %d", len(catalog), len(AvailablePresets()))
if len(catalog) != len(workloads.AvailablePresets()) {
t.Fatalf("got %d presets, want %d", len(catalog), len(workloads.AvailablePresets()))
}

byName := make(map[string]PresetInfo, len(catalog))
for _, p := range catalog {
byName[p.Name] = p
byName := make(map[string]workloads.PresetInfo, len(catalog))
for _, preset := range catalog {
byName[preset.Name] = preset
}

if len(byName["tpcc"].SQL) == 0 {
Expand Down
95 changes: 61 additions & 34 deletions workloads/embed.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
// Package workloads provides embedded SQL/JSON workload files.
// Package workloads provides workload-owned embedded SQL, JSON, and documentation files.
package workloads

import (
"embed"
"errors"
"fmt"
"io"
"io/fs"
"os"
"path"
"path/filepath"
"sort"
)

// Preset represents an available example preset.
// Preset identifies an embedded workload asset set.
type Preset string

const (
Expand All @@ -23,67 +24,93 @@
// ErrUnknownPreset is returned when an unknown preset name is requested.
var ErrUnknownPreset = errors.New("unknown preset")

//go:embed *
var Content embed.FS
var registry = make(map[Preset]fs.FS)

// AvailablePresets returns list of available preset names.
// Register associates a preset with assets embedded during package initialization.
func Register(preset Preset, files fs.FS) {
if files == nil {
panic(fmt.Sprintf("workloads: register %q with nil filesystem", preset))
}
if _, exists := registry[preset]; exists {

Check failure on line 34 in workloads/embed.go

View workflow job for this annotation

GitHub Actions / golang-ci

missing whitespace above this line (invalid statement above if) (wsl_v5)

Check failure on line 34 in workloads/embed.go

View workflow job for this annotation

GitHub Actions / golang-ci

missing whitespace above this line (invalid statement above if) (wsl_v5)
panic(fmt.Sprintf("workloads: preset %q already registered", preset))
}

registry[preset] = files
}

// AvailablePresets returns registered preset names in sorted order.
func AvailablePresets() []string {
return []string{
string(PresetTPCC),
string(PresetTPCB),
string(PresetTPCDS),
string(PresetTPCH),
presets := make([]string, 0, len(registry))
for preset := range registry {
presets = append(presets, string(preset))
}
sort.Strings(presets)

Check failure on line 47 in workloads/embed.go

View workflow job for this annotation

GitHub Actions / golang-ci

missing whitespace above this line (invalid statement above expr) (wsl_v5)

Check failure on line 47 in workloads/embed.go

View workflow job for this annotation

GitHub Actions / golang-ci

missing whitespace above this line (invalid statement above expr) (wsl_v5)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

return presets
}

// CopyPresetToPath copies preset files to the target directory.
// CopyPresetToPath copies preset files to target directory.
func CopyPresetToPath(targetPath string, preset Preset, perm os.FileMode) error {
entries, err := Content.ReadDir(string(preset))
files, err := presetFiles(preset)
if err != nil {
return err
}

entries, err := fs.ReadDir(files, ".")
if err != nil {
return fmt.Errorf("%w: %s", ErrUnknownPreset, preset)
return fmt.Errorf("read preset %q: %w", preset, err)
}

for _, entry := range entries {
if entry.IsDir() {
continue
}

err = copyFileToPath(targetPath, string(preset), entry.Name(), perm)
if err != nil {
return fmt.Errorf("preset '%s' file copy error: %w", preset, err)
if err := copyFileToPath(files, targetPath, entry.Name(), perm); err != nil {
return fmt.Errorf("preset %q file copy: %w", preset, err)
}
}

return nil
}

// ReadPresetFile reads a single file from an embedded preset.
// presetName is the preset directory (e.g., "tpcc"), fileName is the file within it (e.g., "tpcc.ts").
// ReadPresetFile reads one file embedded by a workload package.
func ReadPresetFile(presetName, fileName string) ([]byte, error) {
return Content.ReadFile(path.Join(presetName, fileName))
files, err := presetFiles(Preset(presetName))
if err != nil {
return nil, err
}

return fs.ReadFile(files, fileName)
}

// copyFileToPath copies a single file from examples to the target directory.
func copyFileToPath(targetPath, preset, fileName string, perm os.FileMode) error {
file, err := Content.Open(path.Join(preset, fileName))
func presetFiles(preset Preset) (fs.FS, error) {
files, exists := registry[preset]
if !exists {
return nil, fmt.Errorf("%w: %s", ErrUnknownPreset, preset)
}

return files, nil
}

func copyFileToPath(files fs.FS, targetPath, fileName string, perm os.FileMode) error {
source, err := files.Open(fileName)
if err != nil {
return fmt.Errorf("failed to open file %s: %w", fileName, err)
return fmt.Errorf("open %s: %w", fileName, err)
}
defer file.Close()
defer source.Close()

destFile, err := os.OpenFile(
path.Join(targetPath, fileName),
destination, err := os.OpenFile(
filepath.Join(targetPath, fileName),
os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
perm,
)
if err != nil {
return fmt.Errorf("failed to open file %s: %w", fileName, err)
return fmt.Errorf("open %s: %w", fileName, err)
}
defer destFile.Close()
defer destination.Close()

_, err = io.Copy(destFile, file)
if err != nil {
return fmt.Errorf("failed to copy file %s to %s: %w", fileName, targetPath, err)
if _, err := io.Copy(destination, source); err != nil {
return fmt.Errorf("copy %s to %s: %w", fileName, targetPath, err)
}

return nil
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Package execute_sql is the Go-native port of workloads/execute_sql/execute_sql.ts:
// a generic runner that executes every query in a SQL file (or inline SQL string) once.
// The SQL source is one of two typed workload parameters: --sql-file (a path resolved
// cwd → workloads/execute_sql/ → embedded) or --sql-body (inline SQL text). Queries are
// delimited by `--= name` markers, matching parse_sql.ts — a markerless source yields none.
// Package execute_sql runs every named query from an inline or file SQL source.
// Files resolve from cwd before embedded workload assets; markerless sources contain
// no executable named queries.
package execute_sql

import (
Expand Down
Loading