Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions changes/unreleased/wheretype-exact.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- `WhereType` takes `exact = true` to keep only the elements whose own metaclass is named — neither its subclasses nor the model's own types — as a reflective walk over the metamodel needs (`DocumentQueries` from the Extensions Library pin that declares it).
4 changes: 3 additions & 1 deletion docs/manual/query-cookbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,9 @@ v1 migration roots every table scope this way.
`WhereType` keeps elements whose *metamodel* type matches — `"PartUsage"`,
`"ConnectionUsage"`, `"RequirementUsage"`, `"AttributeUsage"`, `"PortUsage"`,
`"PartDefinition"` and so on — including metaclass conformance, so
`type = "Usage"` keeps every kind of usage. A metamodel type name means the
`type = "Usage"` keeps every kind of usage; `exact = true` keeps only the elements whose own
metaclass is named (`type = "PartUsage", exact = true` keeps the part usages and nothing that
merely conforms), which is what a walk per metaclass over the metamodel needs. A metamodel type name means the
metaclass even when an element of the model bears the same name; qualify the
element's name to mean the element. Several names keep the elements of any of
them: `type = ("PartUsage", "PortUsage")`. A name that is neither a
Expand Down
39 changes: 38 additions & 1 deletion internal/doc/queryexec/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,12 @@ func (e *executor) evaluateWhereType(expression queryplan.Expression) (sequence,
if len(typeNames) == 0 {
return sequence{}, e.invalidArgument(expression, "type", "0")
}
exact := false
if hasArgument(expression, "exact") {
if exact, err = e.booleanArgument(expression, "exact"); err != nil {
return sequence{}, err
}
}
tests := make([]typeTest, len(typeNames))
matched := make([]bool, len(typeNames))
for i, typeName := range typeNames {
Expand All @@ -247,7 +253,7 @@ func (e *executor) evaluateWhereType(expression queryplan.Expression) (sequence,
result := filtered(source)
for i, value := range source.values {
for j, test := range tests {
if e.valueIsA(value, test) {
if (exact && e.valueIsExactly(value, test)) || (!exact && e.valueIsA(value, test)) {
matched[j] = true
appendSelected(&result, source, i)
break
Expand All @@ -269,6 +275,37 @@ func (e *executor) evaluateWhereType(expression queryplan.Expression) (sequence,
}

// valueIsA reports whether a row is an object or declaration of the type.
// valueIsExactly is WhereType's exact = true: the row's own metaclass (Model.MetaclassOf, so
// a transition is a TransitionUsage) is the named one, by identity; a type of the model never is.
func (e *executor) valueIsExactly(value Value, test typeTest) bool {
meta := e.exactMetaclass(test)
if meta == nil {
return false
}
if inst, _, isObject := value.Object(); isObject {
for _, typ := range objectTypes(inst) {
if symbols.SameElement(e.context.Model.MetaclassOf(typ), meta) {
return true
}
}
return false
}
sym := value.Declaration()
return sym != nil && symbols.SameElement(e.context.Model.MetaclassOf(sym), meta)
}

// exactMetaclass is the metaclass a WhereType name denotes — simple or qualified — and nil
// for a type of the model, which an exact test never keeps.
func (e *executor) exactMetaclass(test typeTest) *symbols.Symbol {
if test.target == nil {
return nil
}
if meta := e.context.Model.Metaclass(test.target.Name); meta != nil && symbols.SameElement(meta, test.target) {
return meta
}
return nil
}

func (e *executor) valueIsA(value Value, test typeTest) bool {
if _, _, isObject := value.Object(); isObject {
return e.objectIsA(value, test.name, test.target)
Expand Down
101 changes: 101 additions & 0 deletions internal/doc/queryexec/wheretype_exact_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package queryexec

import (
"strings"
"testing"
)

// TestExecuteWhereTypeExactKeepsOnlyTheNamedMetaclass: by default WhereType keeps
// every element conforming to the type; exact = true keeps those whose own
// metaclass is named, so a reflective walk mirrors each element once.
func TestExecuteWhereTypeExactKeepsOnlyTheNamedMetaclass(t *testing.T) {
fixture := loadExecutionFixture(t, `
part def Heavy;
part root {
part a : Heavy;
item b;
attribute c = 1.0;
port d;
}
calc def Usages :> Query {
in source : Element;
WhereType(source = OwnedElements(source = source), type = "Usage")
}
calc def ExactUsages :> Query {
in source : Element;
WhereType(source = OwnedElements(source = source), type = "Usage", exact = true)
}
calc def ExactParts :> Query {
in source : Element;
WhereType(source = OwnedElements(source = source), type = ("PartUsage", "ItemUsage"), exact = true)
}
calc def ExactHeavy :> Query {
in source : Element;
WhereType(source = OwnedElements(source = source), type = "Heavy", exact = true)
}
calc def ExactQualified :> Query {
in source : Element;
WhereType(source = OwnedElements(source = source), type = "SysML::Systems::PartUsage", exact = true)
}
state def Machine {
state first;
state second;
transition go first first then second;
}
calc def ExactTransitions :> Query {
in source : Element;
WhereType(source = Descendants(source = source), type = "TransitionUsage", exact = true)
}
calc def ExactStates :> Query {
in source : Element;
WhereType(source = Descendants(source = source), type = "StateUsage", exact = true)
}
`)
bindings := Bindings{"source": {ElementValue(fixture.symbol(t, "root"))}}
names := func(query string) string {
result, err := fixture.execute(t, query, bindings, Options{})
if err != nil {
t.Fatalf("%s: %v", query, err)
}
var out []string
for _, row := range result.Rows() {
sym, _ := row.Element().Element()
out = append(out, sym.Name)
}
return strings.Join(out, " ")
}
if got := names("Usages"); got != "a b c d" {
t.Fatalf("Usages = %q, want every usage", got)
}
if got := names("ExactUsages"); got != "" {
t.Fatalf("ExactUsages = %q, want none: no element's own metaclass is Usage", got)
}
if got := names("ExactParts"); got != "a b" {
t.Fatalf("ExactParts = %q, want the part and the item", got)
}
if got := names("ExactHeavy"); got != "" {
t.Fatalf("ExactHeavy = %q, want none: a model type is not a metaclass", got)
}
if got := names("ExactQualified"); got != "a" {
t.Fatalf("ExactQualified = %q, want the part: a qualified metaclass name is the metaclass", got)
}
machine := Bindings{"source": {ElementValue(fixture.symbol(t, "Machine"))}}
exact := func(query string) string {
result, err := fixture.execute(t, query, machine, Options{})
if err != nil {
t.Fatalf("%s: %v", query, err)
}
var out []string
for _, row := range result.Rows() {
sym, _ := row.Element().Element()
out = append(out, sym.Name)
}
return strings.Join(out, " ")
}
if got := exact("ExactTransitions"); got != "go" {
t.Fatalf("ExactTransitions = %q, want the transition: its metaclass is TransitionUsage", got)
}
if got := exact("ExactStates"); got != "first second" {
t.Fatalf("ExactStates = %q, want the two states and not the transition", got)
}
}
4 changes: 2 additions & 2 deletions internal/workspace/libs/extensions-project/.meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
},
"libraries/DocumentQueries.sysml": {
"algorithm": "SHA256",
"value": "370b6f880bb2dc375b534f388e3c802c5820cc45e13977c38b3d36a9d1710233"
"value": "7cad8b83772182db5aa917ed36c19feb2e0796ecd6cfbc3b6b1a4c33729b11a7"
},
"libraries/IdentityMetadata.sysml": {
"algorithm": "SHA256",
Expand Down Expand Up @@ -69,7 +69,7 @@
"value": "9e00f049209584092dc8bcb398b736c08e6a39b1db667a8a744de3df72a543bf"
}
},
"created": "2026-10-27T13:19:03Z",
"created": "2026-10-29T15:08:48Z",
"index": {
"AnalysisRecords": "libraries/AnalysisRecords.sysml",
"DiagramLayout": "libraries/DiagramLayout.sysml",
Expand Down
Binary file modified internal/workspace/libs/stdlib.snapshot
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,14 @@ library package DocumentQueries {
return result : Element[0..*] ordered;
}

/* WhereType keeps the elements that are of any of the named types. */
/* WhereType keeps the elements that are of any of the named types: a metaclass
* (PartUsage) or a definition/usage of the model, subtypes included. */
calc def WhereType {
in source : Element[0..*] ordered;
in type : String[1..*] ordered;
/* exact = true keeps only the elements whose own metaclass is one of the named
* ones, neither its subclasses nor the model's types: the reflective walk. */
in exact : Boolean[1] default false;
return result : Element[0..*] ordered;
}

Expand Down
2 changes: 1 addition & 1 deletion scripts/extension-libraries-pin.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
# verifies, because a ref is mutable. Change them together.
EXTENSIONS_REPO="${EXTENSIONS_REPO:-https://github.com/Open-MBEE/OpenSysML-Extensions-Library.git}"
EXTENSIONS_REF="${EXTENSIONS_REF:-main}"
EXTENSIONS_COMMIT="${EXTENSIONS_COMMIT:-745963b151f925e4543b03d5bb09b978aa492e12}"
EXTENSIONS_COMMIT="${EXTENSIONS_COMMIT:-1ef079deaa98ff8be988ab7dea2f7a8c0640fb22}"
Loading