From 20700f6df3e42f9162827a3121744eda525e1ab1 Mon Sep 17 00:00:00 2001 From: Alan Lee Date: Wed, 12 Aug 2026 15:56:13 -0700 Subject: [PATCH 1/2] fix: validate action `queries` instead of failing on empty `query` `ActionConfig.staticValidate` only ever inspected the singular `Query` field, so any action defining the plural `queries` form failed startup validation with "query is required" and blocked the sync entirely. Both forms are already accepted by `ActionConfig.Validate` (which enforces exactly-one-of) and by `handleQueryAction`, which executes `Queries` when present. Only the static validator was out of step, so this was a false positive on configs that would otherwise run fine. Regression introduced in v0.5.0 by c3f62e17 (#98); v0.4.4 had no `ActionConfig.staticValidate` at all, so the plural form worked by never being checked. Co-Authored-By: Claude Opus 5 --- pkg/bsql/validate.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/bsql/validate.go b/pkg/bsql/validate.go index 5ab3da1d..86ff5bd7 100644 --- a/pkg/bsql/validate.go +++ b/pkg/bsql/validate.go @@ -212,5 +212,16 @@ func (l *ActionConfig) staticValidate(ctx context.Context, s *SQLSyncer) error { availableVars[k] = config.Type } + // An action defines either `query` or `queries`; both forms are accepted by + // ActionConfig.Validate and by the action handler, so validate whichever is set. + if len(l.Queries) > 0 { + for _, query := range l.Queries { + if err := validateVarsInQuery(s, query, availableVars); err != nil { + return err + } + } + return nil + } + return validateVarsInQuery(s, l.Query, availableVars) } From 7a7bbd55ee329f884160bfbd08314d0a9c603e2e Mon Sep 17 00:00:00 2001 From: Alan Lee Date: Wed, 12 Aug 2026 16:16:25 -0700 Subject: [PATCH 2/2] test: cover action `queries` in staticValidate Pins the regression fixed in the previous commit. `action with queries` fails against the prior implementation with the exact error customers hit ("query is required") and passes with the fix. Also covers the singular `query` path, which the new branch could have bypassed, and a `queries` entry referencing an undefined var to confirm the loop still rejects each statement individually. Co-Authored-By: Claude Opus 5 --- pkg/bsql/validate_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/pkg/bsql/validate_test.go b/pkg/bsql/validate_test.go index 89eb13e8..5c210e37 100644 --- a/pkg/bsql/validate_test.go +++ b/pkg/bsql/validate_test.go @@ -48,6 +48,44 @@ func TestValidate(t *testing.T) { }, expectErr: true, }, + { + name: "action with singular query", + validator: &ActionConfig{ + Query: "UPDATE users SET disabled = 1 WHERE id = ?", + Arguments: map[string]ArgumentConfig{ + "userid": {Type: "string"}, + }, + }, + expectErr: false, + }, + { + // Actions may define `queries` instead of `query`; validating only the + // singular field rejected every multi-statement action outright. + name: "action with queries", + validator: &ActionConfig{ + Queries: []string{ + "UPDATE users SET disabled = 1 WHERE id = ?", + "DELETE FROM user_sessions WHERE user_id = ?", + }, + Arguments: map[string]ArgumentConfig{ + "userid": {Type: "string"}, + }, + }, + expectErr: false, + }, + { + name: "action with queries referencing undefined var", + validator: &ActionConfig{ + Queries: []string{ + "UPDATE users SET disabled = 1 WHERE id = ?", + "DELETE FROM user_sessions WHERE user_id = ?", + }, + Arguments: map[string]ArgumentConfig{ + "userid": {Type: "string"}, + }, + }, + expectErr: true, + }, } for _, tc := range tcases {