Skip to content
Settings

predicates — search & criteria condition semantics

cyoda-go version 0.8.3

predicates — operator catalog and evaluation semantics for the Condition DSL used by search (cyoda help search) and workflow/transition criteria (cyoda help workflows). Both consume the same kernel, so everything here applies identically to both.

Unary (test presence, no operand type constraint):

  • IS_NULL — the leaf is absent or JSON null.
  • NOT_NULL — the leaf is present and non-null.

Range (operand is a two-element [low, high] array):

  • BETWEEN — exclusive bounds.
  • BETWEEN_INCLUSIVE — inclusive bounds.

Binary — comparison (same-type compareTo):

  • EQUALS, NOT_EQUAL, GREATER_THAN, GREATER_OR_EQUAL, LESS_THAN, LESS_OR_EQUAL.
  • IEQUALS (case-insensitive equals), INOT_EQUAL (case-insensitive not-equal).

Binary — string (case-sensitive; I*/INOT_* fold both sides):

  • CONTAINS, STARTS_WITH, ENDS_WITH, and their NOT_* inverses.
  • ICONTAINS/INOT_CONTAINS, ISTARTS_WITH/INOT_STARTS_WITH, IENDS_WITH/INOT_ENDS_WITH.

Binary — pattern:

  • LIKE — anchored glob, see LIKE GRAMMAR below.
  • MATCHES_PATTERN — anchored regular expression, see MATCHES_PATTERN below.

Not supported: IS_CHANGED/IS_UNCHANGED are change-generation operators, not search predicates — cyoda-go does not implement them.

A condition’s operand is compared against the target field’s declared type(s) (a leaf may carry more than one, e.g. a field seen as both an integer and a string across entities). The operand is treated as a string and parse-tested against every declared type — a numeric-looking string operand ("30") and a JSON number operand (30) are parsed identically and are treated the same. There is no cross-type coincidental matching: an operand that parses as a number only compares against numerically-stored values; an operand that parses only as a string compares against string-stored values. Numbers compare precisely (arbitrary-precision, not float64 — correct beyond 2^53). Comparing against MATCHES_PATTERN/LIKE and string ops evaluate against textual stored values only; a string op against a non-textual stored value is a non-match, not an error.

A missing (absent) or JSON-null leaf never matches any binary operator — including negatives. NOT_EQUAL, NOT_CONTAINS, INOT_*, and every other negated op are null-guarded to non-match, not !positive — a null/absent field does not satisfy a negative condition just because it fails the positive one. IS_NULL / NOT_NULL are the only operators that test presence directly.

LIKE compiles its operand to an anchored regular expression:

  • % — matches any sequence of characters (including empty).
  • _ — matches exactly one character.
  • \ — escapes a following %, _, or \ to its literal form.
  • The match is whole-string anchored (the entire stored value must match, not a substring) and case-sensitive.

MATCHES_PATTERN compiles the operand as a Go RE2 regular expression, whole-string anchored (equivalent to Java’s Pattern.matches), case-sensitive. RE2 and the Java regex dialect diverge on some constructs (e.g. backreferences, some lookaround); this is an accepted, bounded divergence — not reconciled.

Validation is parse-based, evaluated at request time against the target model:

  • 400 CONDITION_TYPE_MISMATCH — the operand parses into none of the field’s declared types (comparison and range operators only; string operators and unary presence tests carry no operand-type constraint and are always accepted).
  • 400 INVALID_FIELD_PATH — the field path is unknown to the model, or it names a pure-container (object) path: a scalar operator cannot compare against structure — navigate to a scalar leaf sub-path instead. (A path observed as both an object and a scalar across entities remains searchable via its scalar type — see cyoda help search.) IS_NULL/NOT_NULL are exempt from the container-path rejection since they test presence, not a value.
  • 400 INVALID_CONDITION — the operand is null on a binary/range operator, a range operator’s value is not a two-element array, or the operand is an object/complex value.

There is no operator-versus-field-type rejection: CONTAINS on a numeric field or GREATER_THAN on a boolean field are accepted requests — they parse and simply evaluate to a (non-)match.

  • search
  • workflows
  • errors.CONDITION_TYPE_MISMATCH
  • errors.INVALID_FIELD_PATH
  • errors.INVALID_CONDITION
  • cyoda help search — Search operates against a specific entity model (entityName, modelVersion). Two modes are supported:
  • cyoda help workflows — A workflow definition is a named finite state machine attached to an entity model. Workflows are stored per model reference (entityName, modelVersion). A model may have multiple workflow definitions; the engine selects the matching one per entity using the workflow-level criterion field evaluated at entity creation time. When no criterion matches, the engine uses the default built-in workflow.
  • cyoda help errors CONDITION_TYPE_MISMATCH — Validation is parse-based: a comparison or range operand is rejected only when it parses into none of the field’s declared DataTypes. For example "abc" against a DOUBLE field is rejected — it is not a number. A numeric-looking string against a polymorphic [INTEGER, STRING] field is accepted (it parses as STRING).
  • cyoda help errors INVALID_FIELD_PATH — Before executing a search, the server validates that every data-field path referenced by the condition (e.g. $.price, $.profile.email) resolves against the target model’s locked schema. Lifecycle paths (state, previousTransition, etc.) and meta paths ($._meta.*) bypass this check.
  • cyoda help errors INVALID_CONDITION — Endpoints that accept a search-style condition in the request body — grouped statistics and the conditional form of delete-by-model — reject a body whose condition cannot be parsed or is otherwise structurally invalid. The condition type is unrecognised, a nested clause is malformed, the JSON does not match the expected condition envelope, an operatorType is not one of the canonical operators, a MATCHES_PATTERN regex is malformed, or a BETWEEN/BETWEEN_INCLUSIVE operator’s value is not a two-element array.