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
94 lines
2.5 KiB
Go
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
|
|
}
|