Skip to content

Operator Support

This page is the complete, static reference for the condition operators the XrmGhost mock QueryEngine answers. It covers all 89 members of the condition-operator inventory the pinned Dataverse SDK declares — nothing is filtered out and nothing is summarised away.

It is transcribed by hand from frozen snapshots of xrmghost-framework-host that live in this repository next to the page. See Provenance for the commit, the SDK pin and the date.

“Supported” is not a yes/no here. It is one of three dispositions the registry declares, and the difference between the two negative ones is the point:

DispositionMeaning
yesAn evaluator is registered for that canonical key and the engine can answer the operator.
no (deliberate)The engine refuses the operator on purpose, and the Reason says why — a decision with a cause behind it.
noNo evaluator is registered and nothing has been decided: a gap, not a judgement.

Both frontends resolve a condition to the same canonical key and consult the same registry, and the reason behind a refusal travels with it into the NotSupportedQueryOperatorException message.

That is a statement about how this engine dispatches, and not about what the Dataverse platform accepts on either path. It is also a different question from whether a FetchXML query can name the operator: that one is asked of FetchXmlOperatorMap and answered in its own column, Reachable from FetchXML. The two are independent in both directions, and this page carries a case of each — childof has a FetchXML spelling and is refused, masksselect is fully supported and has no FetchXML spelling at all.

Nothing on this page is in the third disposition. Every operator is either answered or refused with a stated reason:

Operators in the SDK inventory89
Supported88
Not supported1
— of those, deliberately rejected with a reason1
— of those, not covered and not classified0
Supported but not reachable from FetchXML1
  • Canonical key is the name every part of the engine uses for the operator: the SDK member name lowercased, derived the way QueryExpressionFrontend derives it.
  • SDK member and SDK value are the Microsoft.Xrm.Sdk.Query.ConditionOperator member and its integer value in the pinned inventory.
  • FetchXML spelling is what you write in a <condition operator="...">. It is not a separate operator: spellings such as not-begin-with or last-seven-days are translated to the canonical key before anything is looked up. Equal and NotEqual each accept more than one spelling, which is why 91 spellings cover 88 of the 89 operators. The 89th is masksselect, which has no FetchXML spelling — the cell is — because there is nothing to write, not because nobody wrote it down.
  • Reachable from FetchXML is the separate question of whether a FetchXML query can name the operator at all. yes means the frontend resolves some spelling onto the canonical key; no (no FetchXml grammar) means it resolves none. It is a statement about the dialect and never about coverage.
  • Context required is what the evaluator declares it needs beyond the record itself. See Execution Context.
  • Reason is filled in only for a deliberate refusal.

Resolving a spelling and supporting an operator are two different questions with two different answers. The spelling table answers “which operator is this”; whether the engine can evaluate it is IConditionEvaluatorRegistry.IsSupported’s answer — and the two come apart in both directions, which is why Supported and Reachable from FetchXML are separate columns:

  • A spelling without support. child-of is parsed by the FetchXML frontend and the identity is then declined: the operator is no (deliberate) under Supported and yes here. Never read the presence of a spelling as evidence of support.
  • Support without a spelling. masksselect is answered by the registry and reachable from a QueryExpression, and no FetchXML spelling resolves onto it. It is yes under Supported and no (no FetchXml grammar) here. Never read the absence of a spelling as a gap in coverage.
Canonical keySDK memberSDK valueFetchXML spellingSupportedReachable from FetchXMLContext requiredReason
equalEqual0eq, equalyesyesnone—
notequalNotEqual1ne, neq, notequalyesyesnone—
greaterthanGreaterThan2gtyesyesnone—
greaterequalGreaterEqual4geyesyesnone—
lessthanLessThan3ltyesyesnone—
lessequalLessEqual5leyesyesnone—
inIn8inyesyesnone—
notinNotIn9not-inyesyesnone—
betweenBetween10betweenyesyesnone—
notbetweenNotBetween11not-betweenyesyesnone—
nullNull12nullyesyesnone—
notnullNotNull13not-nullyesyesnone—
Canonical keySDK memberSDK valueFetchXML spellingSupportedReachable from FetchXMLContext requiredReason
likeLike6likeyesyesnone—
notlikeNotLike7not-likeyesyesnone—
beginswithBeginsWith54begins-withyesyesnone—
doesnotbeginwithDoesNotBeginWith55not-begin-withyesyesnone—
endswithEndsWith56ends-withyesyesnone—
doesnotendwithDoesNotEndWith57not-end-withyesyesnone—
containsContains49containsyesyesnone—
doesnotcontainDoesNotContain50not-containyesyesnone—

Multi-select choice membership — 2 operators

Section titled “Multi-select choice membership — 2 operators”
Canonical keySDK memberSDK valueFetchXML spellingSupportedReachable from FetchXMLContext requiredReason
containvaluesContainValues87contain-valuesyesyesnone—
doesnotcontainvaluesDoesNotContainValues88not-contain-valuesyesyesnone—
Canonical keySDK memberSDK valueFetchXML spellingSupportedReachable from FetchXMLContext requiredReason
maskMask46maskyesyesnone—
masksselectMasksSelect48—yesno (no FetchXml grammar)none—
notmaskNotMask47not-maskyesyesnone—

masksselect is answered by the registry and reached by a QueryExpression; the dash under FetchXML spelling is the absence of a keyword and not a missing transcription. See The Three Bitwise Operators Are Not Privilege Masks for the predicate, and Why masksselect has no FetchXML spelling for the measurement behind the dash.

Canonical keySDK memberSDK valueFetchXML spellingSupportedReachable from FetchXMLContext requiredReason
onOn25onyesyesUserTimeZone—
notonNotOn52not-onyesyesUserTimeZone—
onorbeforeOnOrBefore26on-or-beforeyesyesUserTimeZone—
onorafterOnOrAfter27on-or-afteryesyesUserTimeZone—
Canonical keySDK memberSDK valueFetchXML spellingSupportedReachable from FetchXMLContext requiredReason
yesterdayYesterday14yesterdayyesyesCurrentInstant, UserTimeZone—
todayToday15todayyesyesCurrentInstant, UserTimeZone—
tomorrowTomorrow16tomorrowyesyesCurrentInstant, UserTimeZone—
last7daysLast7Days17last-seven-daysyesyesCurrentInstant, UserTimeZone—
next7daysNext7Days18next-seven-daysyesyesCurrentInstant, UserTimeZone—
lastweekLastWeek19last-weekyesyesCurrentInstant, UserTimeZone, WeekStart—
thisweekThisWeek20this-weekyesyesCurrentInstant, UserTimeZone, WeekStart—
nextweekNextWeek21next-weekyesyesCurrentInstant, UserTimeZone, WeekStart—
lastmonthLastMonth22last-monthyesyesCurrentInstant, UserTimeZone—
thismonthThisMonth23this-monthyesyesCurrentInstant, UserTimeZone—
nextmonthNextMonth24next-monthyesyesCurrentInstant, UserTimeZone—
lastyearLastYear28last-yearyesyesCurrentInstant, UserTimeZone—
thisyearThisYear29this-yearyesyesCurrentInstant, UserTimeZone—
nextyearNextYear30next-yearyesyesCurrentInstant, UserTimeZone—
Canonical keySDK memberSDK valueFetchXML spellingSupportedReachable from FetchXMLContext requiredReason
lastxhoursLastXHours31last-x-hoursyesyesCurrentInstant, UserTimeZone—
nextxhoursNextXHours32next-x-hoursyesyesCurrentInstant, UserTimeZone—
lastxdaysLastXDays33last-x-daysyesyesCurrentInstant, UserTimeZone—
nextxdaysNextXDays34next-x-daysyesyesCurrentInstant, UserTimeZone—
lastxweeksLastXWeeks35last-x-weeksyesyesCurrentInstant, UserTimeZone—
nextxweeksNextXWeeks36next-x-weeksyesyesCurrentInstant, UserTimeZone—
lastxmonthsLastXMonths37last-x-monthsyesyesCurrentInstant, UserTimeZone—
nextxmonthsNextXMonths38next-x-monthsyesyesCurrentInstant, UserTimeZone—
lastxyearsLastXYears39last-x-yearsyesyesCurrentInstant, UserTimeZone—
nextxyearsNextXYears40next-x-yearsyesyesCurrentInstant, UserTimeZone—
Canonical keySDK memberSDK valueFetchXML spellingSupportedReachable from FetchXMLContext requiredReason
olderthanxminutesOlderThanXMinutes86olderthan-x-minutesyesyesCurrentInstant, UserTimeZone—
olderthanxhoursOlderThanXHours85olderthan-x-hoursyesyesCurrentInstant, UserTimeZone—
olderthanxdaysOlderThanXDays84olderthan-x-daysyesyesCurrentInstant, UserTimeZone—
olderthanxweeksOlderThanXWeeks83olderthan-x-weeksyesyesCurrentInstant, UserTimeZone—
olderthanxmonthsOlderThanXMonths53olderthan-x-monthsyesyesCurrentInstant, UserTimeZone—
olderthanxyearsOlderThanXYears82olderthan-x-yearsyesyesCurrentInstant, UserTimeZone—
Canonical keySDK memberSDK valueFetchXML spellingSupportedReachable from FetchXMLContext requiredReason
thisfiscalyearThisFiscalYear58this-fiscal-yearyesyesCurrentInstant, UserTimeZone, FiscalCalendar—
thisfiscalperiodThisFiscalPeriod59this-fiscal-periodyesyesCurrentInstant, UserTimeZone, FiscalCalendar—
nextfiscalyearNextFiscalYear60next-fiscal-yearyesyesCurrentInstant, UserTimeZone, FiscalCalendar—
nextfiscalperiodNextFiscalPeriod61next-fiscal-periodyesyesCurrentInstant, UserTimeZone, FiscalCalendar—
lastfiscalyearLastFiscalYear62last-fiscal-yearyesyesCurrentInstant, UserTimeZone, FiscalCalendar—
lastfiscalperiodLastFiscalPeriod63last-fiscal-periodyesyesCurrentInstant, UserTimeZone, FiscalCalendar—
lastxfiscalyearsLastXFiscalYears64last-x-fiscal-yearsyesyesCurrentInstant, UserTimeZone, FiscalCalendar—
lastxfiscalperiodsLastXFiscalPeriods65last-x-fiscal-periodsyesyesCurrentInstant, UserTimeZone, FiscalCalendar—
nextxfiscalyearsNextXFiscalYears66next-x-fiscal-yearsyesyesCurrentInstant, UserTimeZone, FiscalCalendar—
nextxfiscalperiodsNextXFiscalPeriods67next-x-fiscal-periodsyesyesCurrentInstant, UserTimeZone, FiscalCalendar—
infiscalyearInFiscalYear68in-fiscal-yearyesyesCurrentInstant, UserTimeZone, FiscalCalendar—
infiscalperiodInFiscalPeriod69in-fiscal-periodyesyesCurrentInstant, UserTimeZone, FiscalCalendar—
infiscalperiodandyearInFiscalPeriodAndYear70in-fiscal-period-and-yearyesyesCurrentInstant, UserTimeZone, FiscalCalendar—
inorbeforefiscalperiodandyearInOrBeforeFiscalPeriodAndYear71in-or-before-fiscal-period-and-yearyesyesCurrentInstant, UserTimeZone, FiscalCalendar—
inorafterfiscalperiodandyearInOrAfterFiscalPeriodAndYear72in-or-after-fiscal-period-and-yearyesyesCurrentInstant, UserTimeZone, FiscalCalendar—
Canonical keySDK memberSDK valueFetchXML spellingSupportedReachable from FetchXMLContext requiredReason
equaluseridEqualUserId41eq-useridyesyesUserMembership—
notequaluseridNotEqualUserId42ne-useridyesyesUserMembership—
equalbusinessidEqualBusinessId43eq-businessidyesyesUserMembership—
notequalbusinessidNotEqualBusinessId44ne-businessidyesyesUserMembership—
equaluserlanguageEqualUserLanguage51eq-userlanguageyesyesUserLanguage—
equaluserteamsEqualUserTeams73eq-userteamsyesyesUserTeams—
equaluseroruserteamsEqualUserOrUserTeams74eq-useroruserteamsyesyesUserMembership, UserTeams—
equaluseroruserhierarchyEqualUserOrUserHierarchy80eq-useroruserhierarchyyesyesUserHierarchy—
equaluseroruserhierarchyandteamsEqualUserOrUserHierarchyAndTeams81eq-useroruserhierarchyandteamsyesyesUserHierarchy—
Canonical keySDK memberSDK valueFetchXML spellingSupportedReachable from FetchXMLContext requiredReason
aboveAbove78aboveyesyesnone—
aboveorequalAboveOrEqual79eq-or-aboveyesyesnone—
underUnder75underyesyesnone—
underorequalUnderOrEqual77eq-or-underyesyesnone—
notunderNotUnder76not-underyesyesnone—
Canonical keySDK memberSDK valueFetchXML spellingSupportedReachable from FetchXMLContext requiredReason
childofChildOf45child-ofno (deliberate)yes—see note

childof is the only operator this engine refuses, and it is not unfinished: it is out for a fact somebody measured. An evaluator written anyway would answer a query Dataverse itself declines.

The SDK reference marks ChildOf “for internal use only”, and the platform enforces that as a refusal of use rather than of the name — which is why the operator keeps its FetchXML spelling and is still no (deliberate):

  • Through FetchXML, the frontend parses child-of and the platform then rejects the attribute: “The parentbusinessunitid attribute is not a valid attribute for the child-of operator” — ADO-2307 recorded eight rejections out of eight.
  • Through QueryExpression, at the operator’s own arity of zero values, the platform answers “The child-of operator is not valid for standard queries. It is for internal crm infrastructure use only via OutlookSync queries.” — ADO-2498, six targets across three hierarchies (businessunit.parentbusinessunitid, businessunit.businessunitid, systemuser.businessunitid, systemuser.parentsystemuserid, role.parentrootroleid, team.businessunitid).

So childof is a reserved operator and not an unknown one, and there is no observed behaviour an evaluator could mirror. What that measurement did not establish is what ChildOf does inside OutlookSync, which is a path RetrieveMultiple does not reach.

masksselect is supported and it is unreachable from FetchXML, and the two halves have to be read together because either alone reads as the opposite state.

  • Every FetchXML spelling tried — masks-select, masksselect, maskselect and masks_select — is answered by the Dataverse server with HTTP 400 Unknown Condition Operator. That measurement was true, and this engine’s FetchXML frontend mirrors it: none of the spellings that normalise onto the canonical key (masks-select, masksselect, masks_select, MasksSelect, Masks-Select) resolves, so a FetchXML query cannot name the operator under any of them.
  • A QueryExpression reaches it. The QueryExpression probe of ADO-2500, run on 18 September 2026, sent ConditionOperator.MasksSelect through RetrieveMultiple on four tables (webresource.webresourcetype, roleprivileges.privilegedepthmask, principalobjectaccess.accessrightsmask, systemuser.accessmode) and the server answered 200 with rows on all four, as (value & operand) != 0.

The two measurements do not contradict each other. Together they say the operator exists server-side and has no FetchXML grammar, which is a fact about a frontend and not a gap in coverage — so masksselect counts as covered, and its refusal on the FetchXML path is a refusal of the spelling, not of the operator.

That the server knows the operator rather than ignoring it is visible in its own error messages, which talk about the operand and never about the operator: the wrong number of values answers “The ConditonOperator.MasksSelect requires 1 value/s, not 2” — and “not 0” for none — with Microsoft’s typo included, and a string column answers Sql Number: 402, SQL Server’s operand type clash, raised by a bitwise AND that reached the SQL generator. An operand of 0 is accepted and matches nothing, which is what (value & 0) != 0 says.

The Three Bitwise Operators Are Not Privilege Masks

Section titled “The Three Bitwise Operators Are Not Privilege Masks”

mask, masksselect and notmask are generic bitwise AND predicates over any integer attribute, and they are worth stating on their own because the obvious reading of the name is the wrong one:

OperatorPredicateIn words
mask(value & operand) == operandevery bit of the operand is set
masksselect(value & operand) != 0at least one bit of the operand is set
notmask(value & operand) == 0no bit of the operand is set

They are three different predicates, and a single-bit operand hides it. At operand 1 the readings == operand, != 0 and == bit 0 all coincide, so a counterexample built on a one-bit operand stays true under any of the three and proves nothing about which one the platform implements. The discriminating operand is a multi-bit one.

The measured counterexample, on webresource.webresourcetype over the domain 1, 2, 3, 4, 5, 6, 7, 11, 12 at operand 3 (0011):

OperatorValues the platform matches at operand 3
mask3, 7, 11 — the only values with both bit 0 and bit 1 set
masksselect1, 2, 3, 5, 6, 7, 11 — every value with either bit set
notmask4, 12 — the values with neither bit set

Two things follow from that one row, and both correct a reading that a single-bit probe had made plausible:

  • mask is not != 0. 1, 2, 5 and 6 each share a bit with 3 and are matched by masksselect, and none of them is matched by mask. (value & operand) != 0 is masksselect’s predicate, never mask’s.
  • mask and notmask are not complements. 1, 2, 5, 6 are in neither. The complement of notmask is masksselect: masksselect is “at least one bit”, notmask is “no bit”, and together they exhaust the domain.

And the privilege mask is the most famous use of these predicates, not their semantics. The counterproof is systemuser.accessmode, which is not a privilege column: masksselect 1 returns two rows, with accessmode values 3 and 5 — each has bit 0 set — and nothing in answering it reads a privilege, a role or an entitlement. Note that this example is at operand 1, so it says what the operator does not read; it does not discriminate between the three predicates, and the operand-3 table above is what does.

All three declare no execution-context requirement: none asks for anything the row cannot supply.

What the bitwise measurement covers, and what it does not

Section titled “What the bitwise measurement covers, and what it does not”

The three predicates were confirmed on three columns (webresource.webresourcetype, principalobjectaccess.accessrightsmask, savedquery.querytype) across ten column/operand combinations — thirty comparisons between the observed value set and the set the formula predicts, with zero discrepancies. The bound of that confirmation is stated rather than left to be assumed:

  • Types. Only Integer and Picklist columns were queried. BigInt, Decimal and Money were not. A string column answers Sql Number: 402, which says there is no implicit conversion but not where the boundary between numeric types lies.
  • System tables only. No custom attribute was queried, so a SQL generator that treated one differently would not have been seen.
  • Range and sign. Every value in every domain fits in a positive Int32, and the largest operand queried is 852023 — bit 19. A negative operand, one beyond 2^31, and the difference between a 32-bit and a 64-bit AND are all outside what was observed.
  • One organisation, one instant. The probe ran against a single Dataverse organisation on 18 September 2026, and the server’s own version was not recorded. If the platform changed this behaviour, this measurement would not predict it.

What is outside those bounds is not observed, and this page makes no claim about it either way.

An operator that cannot be answered from the record alone declares what it needs to know. The host resolves exactly the union of what the plan’s operators asked for — a plan whose operators need nothing causes no lookup at all.

These are the eight flags of OperatorContextRequirement. They are requirements, not entitlements — see Not a Licence Surface.

RequirementWhat the operator needsWhere it is read from
CurrentInstant”Now” — and every condition of the query agreeing on itThe injected clock, not a record
UserTimeZoneThe caller’s time zoneusersettings (timezonecode, timezonebias)
WeekStartThe organisation’s first day of the weekorganization (weekstartdaycode)
UserMembershipWho is asking: the caller and the business unit they belong tousersettings (systemuserid, businessunitid) and systemuser (businessunitid)
FiscalCalendarWhere the organisation’s fiscal year starts and how long a fiscal period isorganization (fiscalcalendarstart, fiscalperiodtype), plus usersettings for the caller’s offset
UserLanguageThe caller’s interface language, as an LCIDusersettings (uilanguageid)
UserTeamsThe teams the caller belongs toteammembership, on systemuserid and teamid and nothing else
UserHierarchyThe caller and every user below them, plus the teams that whole set belongs tosystemuser (self-referential lookup) and teammembership

Three separations in that table are load-bearing and are not tidiness:

  • FiscalCalendar is its own flag, not a widening of WeekStart, even though both land on the organization record. thisweek wants the first day of the week and nothing else; thisfiscalyear wants the fiscal anchor and nothing else. That they share a record is the resolver’s business, and it reads that record once for both.
  • FiscalCalendar carries the caller’s offset with it. organization.fiscalcalendarstart is a date in the user’s local time, so resolving it without an offset would silently read it as UTC. An operator that needs the fiscal calendar does not have to also declare UserTimeZone to be answered in the right zone.
  • UserHierarchy (bit 128) is not a widening of UserTeams (bit 64). 64 is already “the teams of the caller”; the hybrid family needs the teams of the caller and of every descendant. Folding them together would make equaluserteams enumerate a hierarchy it never reads, or make the hierarchy answer with the caller’s teams alone. Both keep working and both answer wrongly.

UserLanguage is uilanguageid and not localeid. The two sit side by side on the same record and both read as “the user’s language”: localeid is the formatting locale, uilanguageid is the language the interface — and the operator — speaks. The resolver deliberately does not read localeid. Reading the wrong one answers with a plausible number Dataverse would never have matched on, which is why the attribute is named here rather than inferred.

UserHierarchy is descendants, never ancestors. The two operators behind it answer “mine and my reports’”, so the traversal runs down the manager chain from the caller. Walking it upwards would return the caller’s managers, which is a different and plausible-looking answer nothing would flag; and the business-unit hierarchy is a third thing again, belonging to UserMembership’s table and not to this one.

The eight requirements above are resolved from four records, which a scenario declares the way it declares any other record. The requirements are the whole input: there is no second list of “context operators” to keep in step with the registry and no typed scenario contract to fill in.

RecordWhat it answersAttributes read
usersettingsThe caller’s time zone, business unit and languagesystemuserid, businessunitid, uilanguageid, timezonecode, timezonebias
systemuserThe caller’s business unit when the execution context did not state one, and the user hierarchy below themsystemuserid (primary key), businessunitid; the self-referential lookup Dataverse calls parentsystemuserid
teammembershipThe teams of the caller and of that hierarchy — the platform intersect, one row per (user, team) pairsystemuserid, teamid
organizationThe organisation’s first day of the week and fiscal calendarorganizationid, weekstartdaycode, fiscalcalendarstart, fiscalperiodtype

Two notes on how records are picked, because both are easy to get wrong:

  • The caller is stated, not guessed. The caller is the caller of the organization service the query goes through: ExecutionContext.UserId — the user the plugin runs as, not InitiatingUserId — for the service the context provides; the requested user for a service from CreateOrganizationService(userId); and a fixed internal SYSTEM identity for CreateOrganizationService(null), which has no business unit, no teams and no usersettings record, so its queries run as UTC with no membership. Every per-user record is picked by matching that caller. Reading “the first usersettings record” instead is correct while a scenario declares one user and quietly answers with a different person’s settings as soon as it declares two.
  • The systemuser hierarchy lookup is deduced, not wired. The attribute followed is the single one the registered records carry whose value is an EntityReference naming systemuser. parentsystemuserid is what the platform calls it and is named here because the diagnostics name it — but matching on that name is exactly what the resolver does not do: a scenario declares records, not relationship metadata, and a bare Guid under that name is an identifier and not a link.

A Missing Record Is a Documented Default, Traced

Section titled “A Missing Record Is a Documented Default, Traced”

Nothing here refuses a query for lack of context. A missing record produces a documented default, and each fallback names which record was missing — “some context was defaulted” is not a fix anybody can act on.

Every one of these lines is traced through IEnhancedTracingService under TraceSource.OperationTracker, so it reaches you as a TraceEntry of the execution response rather than sitting in a field somebody has to know to look at. Every line carries the prefix XrmGhost.FrameworkHost.QueryEngine: , so the lines are greppable.

What was missingThe documented default
No usersettings record at allThe query runs as UTC with no membership
No usersettings record names the stated callerThe query runs as UTC with no language
No caller stated and more than one usersettings recordNone is chosen; the query runs as UTC with no language
The usersettings record carries no timezonebiasThe query runs as UTC
The usersettings record carries no usable uilanguageidThe query runs with no user language
The usersettings record names neither systemuserid nor businessunitidThe query runs with no membership
No systemuser record for the callerThe caller’s businessunitid is read from usersettings instead
No source stated a business unit — not the execution context, not systemuser, not usersettingsThe query runs with none — and each of those records that was read and stayed silent is named, with the caller it was read for and what was wrong with it
No teammembership record at allThe caller belongs to no team
No teammembership row names the caller with a readable teamidThe caller belongs to no team
No caller could be established, for UserTeamsThe teammembership rows cannot be scoped to anyone; the query runs with no teams
No organization record, for WeekStartThe week starts on Monday
No organization record, for FiscalCalendarThe fiscal year starts on 1 January and the fiscal period is monthly
The organization record carries no usable weekstartdaycodeThe week starts on Monday
The organization record carries no usable fiscalcalendarstart or fiscalperiodtype1 January, and monthly — each field falls back on its own, so an unreadable start date does not drag a good period length down with it
No systemuser record carries an EntityReference to systemuserNo hierarchy could be deduced; the query runs with the caller alone
No caller could be established, for UserHierarchyThe systemuser hierarchy has nobody to descend from; the query runs with an empty user hierarchy

One case is a deliberate half-measure and is worth reading twice. When a caller is stated and no usersettings record names them, but the registry holds exactly one such record, its time zone is read as the caller’s — and its businessunitid and its uilanguageid are not. The criterion is what being wrong does: a foreign zone shifts the rows an operator returns, whereas a foreign business unit or language selects different ones, and a wrong row set never announces itself. Two or more records and nothing is taken at all. The mismatch is warned about, never hidden.

One missing organization record produces two independent warnings, one per requirement: a plan that asked only for the fiscal calendar is not told about a week start it never reads, and one that asked for both is told about both.

A missing record has a documented answer and a defaulted one is still an answer. An unanswerable hierarchy has none, and inventing one would return rows about a different tree. Three cases therefore refuse, with a MockHierarchyResolutionException:

  1. The systemuser records carry two or more self-referential lookups, so which one is the user hierarchy cannot be deduced from them. The refusal names every candidate it found, and the way out is stating the hierarchy in explicit relationship metadata. There is no tie-break here that is not a guess.
  2. The manager chain loops. The refusal names the record that closes the cycle.
  3. The descent goes deeper than the configured cap. The refusal names the last record reached — never a truncated set. The default cap is MockHierarchyResolver.DefaultDepthCap, and the traversal that applies it owns it.

Three things bound that exception, and all three matter:

  • It happens only for a plan that declared UserHierarchy. No other requirement can raise it.
  • It happens in step 0-bis of InMemoryExecutor, which is before the first row of the query is read. A refusal raised per row would depend on what the store held, and an empty store would answer an unanswerable query with an empty result set.
  • It is an InvalidOperationException underneath — what the QueryEngine already throws for a structurally unanswerable query — and it is the only way the context resolver ever throws.

Note that zero self-referential lookups is not one of the three. A flat scenario is not a broken one: the answer degrades to the caller alone, traced.

above, aboveorequal, under, underorequal and notunder say none in the Context required column, and that is the right answer: where a record sits in a tree has nothing to do with who is asking, and an operator of this family that declared a context requirement would make every hierarchy query read usersettings to no purpose.

What it must not be read as is “answerable from the row alone.” What these five need is a traversal of the records the query itself names, resolved once per plan by InMemoryExecutor before the first row. They declare it through IHierarchyAwareConditionEvaluator — a separate interface rather than a member of OperatorContextRequirement, because that enum is the input of the context resolver, which never sees a condition. “What does the caller’s context hold” is a different question, and it is the only one the Context required column asks.

They also validate the shape of the condition before the first row: the pivot is a value the condition carries, so “exactly one value, readable as an identifier” is a refusal no store can rescue.

The two hybrid identity operators — equaluseroruserhierarchy and equaluseroruserhierarchyandteams — are not in this family. They answer about the caller’s hierarchy and read it off the execution context under UserHierarchy, and they declare no traversal capability.

A context requirement says what an operator needs to know, never what the caller is entitled to. Nothing in the requirements, the records or the resolver consults a tier, a licence flag or an entitlement, and nothing downstream turns a requirement into a refusal: an operator that needs context and finds none is evaluated against documented defaults, not gated. Keeping the two apart is deliberate — the moment “this operator needs the caller’s identity” can be read as “this operator is Pro”, a mock query becomes a paywall.

Repoxrmghost-framework-host
Branchdev
Commitff32df7
Snapshot taken18 September 2026
SDK pinMicrosoft.CrmSdk.CoreAssemblies 9.0.2.45
AssemblyVersion of that assembly9.0.0.0 — not the pin
FileVersion of that assembly9.2.47.9489
Inventory transcribed fromdocs/query-engine/operator-support-matrix.generated.md

AssemblyVersion 9.0.0.0 is the version the shipped Microsoft.Xrm.Sdk assembly carries whatever package version brought it in: it is 9.0.0.0 for every 9.x package, so it does not identify the operator inventory this engine is written against. Citing it as the pin would make this reference non-reproducible, because two different inventories would present themselves under the same number. The number that does identify the inventory is the package pin, Microsoft.CrmSdk.CoreAssemblies 9.0.2.45 — and the only place it is visible is docs/query-engine/sources/OperatorContractTests.cs.txt. The generated matrix does not carry it and says so: its header states that the shipped assembly carries version 9.0.0.0 whatever package version brought it in, and refers to that test for the pin. So the pin cannot be taken from the matrix, and a reader who takes a version number from there is reading the AssemblyVersion.

89 is the size of the condition-operator inventory of the pinned package. The SDK’s inventory grew to 90 members from 9.0.2.49 (May 2023), which added EqualRoleBusinessId. This engine stays pinned to 9.0.2.45, so 89 is the right number for this page — and 88 of 89 is the right coverage claim. A page that quoted 90 would be describing an SDK this engine is not written against.

This page is transcribed by hand; there is no generator behind it, no CI comparison between the two repositories and no automatic cross-repo link. When the engine changes, the snapshots are replaced whole from a new commit and this page is re-transcribed and re-reviewed against them; that comparison against the sources is the review, and no tool performs it. The only automated gate on this flow is npm run build, which proves the site compiles — not that the page tells the truth.

The authoritative procedure — the order the snapshots must be replaced in, and the failure mode to avoid — is the contributor guide, How to Add a New Docs Page: that is the published one to follow. The snapshots themselves are documented next to them, in docs/query-engine/sources/README.md, which records what each copy answers for and the provenance it was taken at.