From c449e1c2b0313ade3e6bbd0405a538d6988bb0a7 Mon Sep 17 00:00:00 2001 From: claude Date: Mon, 28 Sep 2026 06:20:56 +0000 Subject: [PATCH] fix(catalog): bump the schema version for the two columns that skipped it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit permissions_data.DefaultMemberAccessRights (#1179) and activities_data.UseRequestTimeout / TimeoutExpression (#1181) were both added without bumping CatalogSchemaVersion. CREATE TABLE IF NOT EXISTS does not add a column to a table that already exists, and the cache is only dropped when the recorded version differs, so a cached full-mode catalog keeps the old shape. The activity pair was rescued by accident: 14 landed after #1181 for an unrelated reason and rebuilt those caches. The permissions column landed after 14 and is still live — PermissionsFor and Permissions both SELECT it, so on any catalog built at 14 every permissions query fails with "no such column: DefaultMemberAccessRights". mxcli lint surfaces that through QueryErrors; mxcli report builds the same LintContext, never calls it, and scores the project with every entity access rule silently finding nothing. Also adds the builder test the activities columns should have shipped with, asserting both the toggle and the expression reach the row. It covers the case that matters: unticking "Use a timeout" in Studio Pro leaves the seconds in place, so a rule keyed on the expression rather than the toggle passes a call that has no timeout. --- mdl/catalog/builder_rest_timeout_test.go | 124 +++++++++++++++++++++++ mdl/catalog/tables.go | 12 ++- 2 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 mdl/catalog/builder_rest_timeout_test.go diff --git a/mdl/catalog/builder_rest_timeout_test.go b/mdl/catalog/builder_rest_timeout_test.go new file mode 100644 index 000000000..4011a926e --- /dev/null +++ b/mdl/catalog/builder_rest_timeout_test.go @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: Apache-2.0 + +package catalog + +import ( + "testing" + + "github.com/mendixlabs/mxcli/model" + "github.com/mendixlabs/mxcli/sdk/microflows" +) + +// A Call REST service activity carries Studio Pro's "Use a timeout" toggle and +// the seconds beside it. Mendix stores them as UseRequestTimeOut (bool) and +// TimeOutExpression (a STRING, not an int — "300", not 300), and before these +// columns existed neither reached the catalog, so the obvious rule +// +// every RestCallAction must have a timeout +// +// could not be written at all: a rule could find the activity and learn +// nothing about how it was configured. +// +// The toggle is the part that matters. Unticking "Use a timeout" leaves the +// seconds in place, so a rule keyed on the expression being non-empty passes a +// call that has no timeout — which is why both columns are asserted here, and +// why the "off" case keeps its expression. +func TestActivitiesCarryTheRestCallTimeout(t *testing.T) { + cat, err := New() + if err != nil { + t.Fatal(err) + } + defer cat.Close() + + const modID = model.ID("mod-sales") + + act := func(id string, use bool, expr string) *microflows.ActionActivity { + a := µflows.ActionActivity{ + Action: µflows.RestCallAction{ + UseRequestTimeOut: use, + TimeoutExpression: expr, + }, + } + a.ID = model.ID(id) + return a + } + + mf := µflows.Microflow{ + ContainerID: modID, + Name: "ACT_Rest_Calls", + ObjectCollection: µflows.MicroflowObjectCollection{ + Objects: []microflows.MicroflowObject{ + act("a-on", true, "300"), + act("a-off", false, "300"), + }, + }, + } + mf.ID = model.ID("mf-rest") + + b := &Builder{ + catalog: cat, + snapshot: &Snapshot{ID: "snap"}, + hierarchy: &hierarchy{moduleIDs: map[model.ID]bool{modID: true}, moduleNames: map[model.ID]string{modID: "Sales"}}, + fullMode: true, + // Every cache is set so the builder never reaches for a reader. + microflowCache: []*microflows.Microflow{mf}, + nanoflowCache: []*microflows.Nanoflow{}, + ruleCache: []*microflows.Rule{}, + } + + tx, err := cat.CatalogDB().Begin() + if err != nil { + t.Fatal(err) + } + b.tx = tx + if err := b.buildMicroflows(); err != nil { + t.Fatalf("buildMicroflows: %v", err) + } + if err := tx.Commit(); err != nil { + t.Fatal(err) + } + + res, err := cat.Query(`SELECT Id, ActionType, UseRequestTimeout, TimeoutExpression + FROM activities_data ORDER BY Id`) + if err != nil { + t.Fatalf("query: %v -- the columns must exist, or no rule can read a timeout at all", err) + } + if res.Count != 2 { + t.Fatalf("got %d activity rows, want 2", res.Count) + } + + type row struct { + actionType string + use int64 + expr string + } + got := map[string]row{} + for _, r := range res.Rows { + id, _ := r[0].(string) + at, _ := r[1].(string) + use, _ := r[2].(int64) + expr, _ := r[3].(string) + got[id] = row{at, use, expr} + } + + if got["a-off"].actionType != "RestCallAction" || got["a-on"].actionType != "RestCallAction" { + t.Fatalf("action types = %q / %q, want RestCallAction -- the switch never "+ + "matched, so the timeout could not have been read either", + got["a-on"].actionType, got["a-off"].actionType) + } + if got["a-on"].use != 1 { + t.Errorf("UseRequestTimeout for the ticked activity = %d, want 1 -- "+ + "the toggle is not reaching the row", got["a-on"].use) + } + if got["a-off"].use != 0 { + t.Errorf("UseRequestTimeout for the unticked activity = %d, want 0 -- "+ + "a call with no timeout must not claim one", got["a-off"].use) + } + for id, want := range map[string]string{"a-on": "300", "a-off": "300"} { + if got[id].expr != want { + t.Errorf("TimeoutExpression for %s = %q, want %q -- Studio Pro keeps "+ + "the seconds when the toggle is off, which is exactly why a rule "+ + "must key on the toggle and not on this", id, got[id].expr, want) + } + } +} diff --git a/mdl/catalog/tables.go b/mdl/catalog/tables.go index 82bed23f1..231089272 100644 --- a/mdl/catalog/tables.go +++ b/mdl/catalog/tables.go @@ -7,6 +7,16 @@ package catalog // // History: // +// 15 — permissions_data.DefaultMemberAccessRights, and the belated bump for +// activities_data.UseRequestTimeout / TimeoutExpression. Both columns +// were added without a bump. The activity pair happened to be rescued by +// 14, which landed after it for an unrelated reason; the permissions +// column landed after 14 and so is still unreachable on any cache built +// at 14. PermissionsFor and Permissions both SELECT it, so on such a +// cache every permissions query fails with "no such column" — mxcli lint +// says so via QueryErrors, mxcli report builds the same LintContext, +// never calls it, and silently scores the project with every entity +// access rule finding nothing. // 14 — import_mappings_data / export_mappings_data: Id is the document's ID // (was an AUTOINCREMENT integer) and Excluded is recorded; source gains // ElementId. Two mappings may share a name when one is excluded, and the @@ -46,7 +56,7 @@ package catalog // SnapshotSource / SourceId / SourceBranch / SourceRevision columns // from every row (issue #576). // 1 — initial flat schema with denormalized snapshot columns on every row. -const CatalogSchemaVersion = "14" +const CatalogSchemaVersion = "15" // MetaSchemaVersion is the catalog_meta key that records the schema version // the cache was built against.