Files
relspecgo/pkg/assetloader/executor.go
T
warkanum 1d93255575 fix(assets): improve error reporting for asset failures
fix(embed): update embedDirectives to return result directly

fix(executor): clean up import order and formatting

fix(syntax_test): change file permission syntax to octal

fix(mysql): update file permission syntax to octal

fix(pgsql): update file permission syntax to octal

fix(sqlex): simplify Walk function parameter handling

fix(transform): adjust identifier comparison logic

fix(template): clean up function formatting and error handling
2026-10-12 22:04:40 +02:00

94 lines
2.5 KiB
Go

package assetloader
import (
"context"
"fmt"
"path/filepath"
"git.warky.dev/wdevs/relspecgo/pkg/sqllex"
"github.com/jackc/pgx/v5"
)
// BuildQuery converts a SQL call that uses :name named placeholders into a
// pgx-compatible positional-parameter query ($1, $2, …) and returns the
// corresponding argument slice.
//
// Built-in placeholders:
// - :bytes → fileBytes ([]byte)
// - :filename → filename (string, base name only)
// - :any_key → staticParams["any_key"] (string)
//
// A placeholder that appears more than once maps to the same $N. An unknown
// placeholder (not built-in and not in staticParams) returns an error.
// PostgreSQL cast syntax (::type) is left untouched.
func BuildQuery(call string, fileBytes []byte, filename string, staticParams map[string]string) (query string, args []any, err error) {
paramIndex := map[string]int{} // name → 1-based position
var firstErr error
query = sqllex.Walk(call, func(match string) string {
if firstErr != nil {
return match
}
name := match[1:] // strip leading ':'
// Return existing positional param for repeated placeholders.
if idx, seen := paramIndex[name]; seen {
return fmt.Sprintf("$%d", idx)
}
// Resolve the placeholder value.
var val any
switch name {
case "bytes":
val = fileBytes
case "filename":
val = filename
default:
if staticParams != nil {
if v, ok := staticParams[name]; ok {
val = v
}
}
if val == nil {
firstErr = fmt.Errorf("unknown placeholder %q in SQL call (not a built-in and not listed in params)", match)
return match
}
}
idx := len(args) + 1
paramIndex[name] = idx
args = append(args, val)
return fmt.Sprintf("$%d", idx)
}, nil)
if firstErr != nil {
return "", nil, firstErr
}
return query, args, nil
}
// ExecuteItem reads the asset file referenced by item.Entry.File (which is the
// absolute path set by ScanDir) and executes the configured SQL call via conn.
// The file's raw bytes are bound as a []byte parameter — no encoding or escaping.
func ExecuteItem(ctx context.Context, conn *pgx.Conn, item Item) error {
data, err := readItemAsset(item)
if err != nil {
return fmt.Errorf("reading asset file %s: %w", item.Entry.File, err)
}
filename := filepath.Base(item.Entry.File)
sql, args, err := BuildQuery(item.Entry.Call, data, filename, item.Entry.Params)
if err != nil {
return fmt.Errorf("building query for %s: %w", filename, err)
}
if _, err := conn.Exec(ctx, sql, args...); err != nil {
return fmt.Errorf("executing asset %s: %w", filename, err)
}
return nil
}