Repository navigation
SQL Queries
The emulator includes a full SQL query engine built with Superpower parser combinators. It parses and executes Cosmos DB SQL queries against in-memory data, supporting 125+ built-in functions.
See also: Features for higher-level feature descriptions · API Reference for query method signatures · Known Limitations for behavioural differences from real Cosmos DB
-
Integer arithmetic overflow fix:
9223372036854775807 + 1(long.MaxValue + 1) and similar overflows now correctly return a double instead of silently wrapping to a negative number. TheArithmeticOphelper now checks that the result fits withinlongrange before casting back fromdouble. -
DATETIMEADD overflow fix:
DATETIMEADD('yyyy', 100000, '2000-01-01T00:00:00Z')now returnsundefinedinstead of throwing an unhandledArgumentOutOfRangeException. -
CHOOSE out-of-bounds fix:
CHOOSE(0, 'a', 'b')andCHOOSE(5, 'a', 'b')now returnundefined(field omitted from projection) instead ofnull, matching real Cosmos DB behaviour. -
GetCurrentDateTime test fix: Flaky midnight-boundary test fixed by parsing with
DateTimeStyles.RoundtripKindto preserve UTC kind, preventing timezone-induced date mismatch on non-UTC systems. - 60 new query edge case tests: Comprehensive coverage added for integer overflow/underflow, bit shift edge cases (≥64 bits, negative), INTDIV/INTMOD by zero, NUMBERBIN zero/negative bin size, REPLICATE boundary (10000/10001), INDEX_OF/CONTAINS/STARTSWITH/ENDSWITH with empty strings, POWER(0,0)/POWER(0,-1), LOG(0)/LOG(1), SQRT(0), TRUNC large doubles, ARRAY_SLICE negative start beyond length, STRINGTOARRAY/STRINGTOOBJECT invalid JSON, ORDER BY mixed null/undefined/number/string type ranking, DATETIMEADD/DATETIMEPART overflow, nested COALESCE, aggregate subqueries, BETWEEN equal bounds, IN single/mixed types, NOT IN, LIKE underscore/percent wildcards, TYPE function, IS_NULL/IS_DEFINED null vs undefined, CHOOSE valid/zero index, SET operations, and more.
-
CONCAT null → undefined:
CONCAT('a', null, 'b')now returnsundefinedinstead of"ab", matching Cosmos DB three-value logic where any null argument makes the entire result undefined. -
REPLICATE edge cases → undefined:
REPLICATE('x', -1)andREPLICATE('a', 10001)now returnundefinedinstead ofnull/capped values. Cosmos DB treats negative counts and counts exceeding 10,000 as undefined. -
ROUND negative precision:
ROUND(1234.5, -2)now correctly rounds to the nearest power of ten (→1200). Previously threwArgumentOutOfRangeExceptionasMath.Rounddoesn't support negative precision natively. -
NumberBin zero/negative binSize → undefined:
NumberBin(42, 0)andNumberBin(42, -5)now returnundefinedinstead ofnull, matching Cosmos DB. -
Arithmetic on null → undefined:
null + 1,null * 2,null - 3,null % 2now returnundefined(instead ofnull), matching Cosmos DB three-value arithmetic semantics. -
NOT null → undefined:
NOT nullnow returnsundefined(instead oftrue), matching Cosmos DB three-value logic. -
StringSplit empty delimiter → undefined:
StringSplit('abc', '')now returnsundefinedinstead of throwing. -
Object parameter equality:
WHERE c.nested = @objwith anonymous C# object parameters now correctly deep-compares with JObject document fields usingJToken.DeepEquals. - 39 new query tests: Comprehensive coverage added for CONCAT non-string/null args, REPLICATE edge cases, ROUND negative precision, NumberBin zero/negative bin, arithmetic on null, mixed-type comparisons (number vs string, bool vs string), DISTINCT multiple fields, BETWEEN with date strings, IIF non-boolean conditions, LIKE with OR patterns, NOT on null, coalesce three args, JOIN on non-array/null/undefined fields, IS_DEFINED deeply nested, cross-partition pagination with continuation tokens, DateTimeDiff same/reversed args, DateTimeBin truncation, GetCurrentDateTime consistency, array/object parameter handling, SET_INTERSECT/UNION/DIFFERENCE, ARRAY_SLICE zero length, StringSplit edge cases.
-
New function:
IS_NAN(expr): Returnstruewhen the value isNaN(Not-a-Number). Returnsfalsefor numbers, strings, booleans, null, and undefined/missing fields. -
New function:
TYPE(expr): Returns the JSON type name of an expression as a string:"string","number","boolean","null","array", or"object". Returnsundefinedfor missing fields (row excluded fromSELECT VALUE). -
String functions null → undefined:
UPPER,LOWER,TRIM,LTRIM,RTRIM,REVERSE,LENGTH,LEFT,RIGHT,REPLACEnow returnundefined(instead ofnull) when given explicit null input, matching Cosmos DB three-value logic semantics. -
STARTSWITH/ENDSWITH/CONTAINS null → undefined: These functions now return
undefinedwhen the input string is null (instead offalse), matching Cosmos DB. In WHERE clauses, the effect is the same (row excluded). -
LOG(val, base) NaN/Infinity guard:
LOG(10, 1)(infinity result) and similar edge cases now returnundefinedinstead of rawInfinity. -
REPLACE empty find string:
REPLACE('hello', '', 'x')no longer throwsArgumentException— empty find string returns the original string unchanged. -
DateTimeToTicks invalid format → undefined:
DateTimeToTicks('not-a-date')now returnsundefinedinstead ofnull. -
68 new query tests: Comprehensive coverage added for
IS_NAN,TYPE(), string function null input semantics, math edge cases (COT(0), LOG base 1, ATN2(0,0)), array set operations, aggregate null handling, date/time edge cases, conditional expressions, clause interactions (BETWEEN reversed bounds, IN mixed types, NOT BETWEEN/IN), and cross-feature interactions (GROUP BY + ORDER BY, JOIN + GROUP BY, DISTINCT + ORDER BY).
-
SUBSTRING negative index safety:
SUBSTRING(str, -1, 3)andSUBSTRING(str, 0, -1)no longer throwArgumentOutOfRangeException— negative start and length values are clamped to zero. -
LEFT/RIGHT negative count safety:
LEFT(str, -1)andRIGHT(str, -1)no longer throw — negative counts returnnull. -
77 new query edge case tests: Comprehensive coverage added for null inputs to functions, undefined (missing field) inputs, empty strings, type mismatches, negative indices, WHERE boundary conditions (
WHERE true,WHERE false,WHERE null), IN/BETWEEN/LIKE special characters, nested function calls, multiple aggregates in SELECT, and type-check function edge cases.
-
NaN/Infinity → undefined: Math functions (
SQRT(-1),LOG(0),POWER(0,-1)) and arithmetic (1/0,0%0) now returnundefinedinstead of NaN/Infinity, matching Cosmos DB semantics. -
DateTime null → undefined:
DATETIMEADD,DATETIMEPART,DATETIMEFROMPARTS,DATETIMEBINnow returnundefinedfor null/invalid inputs instead of null. -
SUBSTRING null → undefined:
SUBSTRING(null, ...)now returnsundefined. -
GROUP BY null vs undefined:
GROUP BYnow correctly distinguishesnullfromundefined(missing field), producing separate groups. -
Multi-JOIN source alias:
JOIN t IN g.tags(wheregis a previous JOIN alias) now resolves correctly. -
ARRAY_CONTAINS with parameter array:
ARRAY_CONTAINS(@names, c.field)where@namesis an array parameter now works in WHERE clauses. -
Static DateTime race:
GetCurrentDateTime(),GetCurrentTimestamp(), andGetCurrentTicks()now captureDateTime.UtcNowonce per query to avoid inconsistencies.
-
COUNT(c.field) null semantics:
COUNT(c.field)now correctly excludes documents where the field isnull, matching Cosmos DB behaviour.COUNT(1)andCOUNT(*)still count all documents. -
Subquery TOP/ORDER BY ordering: Subqueries with both
TOPandORDER BYnow apply sorting before taking the top N results. -
Cross-type equality: Strict type comparison —
true = "True"and42 = "42"now correctly returnfalse, matching Cosmos DB semantics. -
LIKE with undefined fields:
LIKEon an undefined (missing) field now returnsfalseinstead of crashing. -
String concat (||) with undefined:
undefined || "text"now returnsundefinedinstead of"text".
| Clause | Examples |
|---|---|
SELECT |
SELECT *, SELECT c.name, c.age, SELECT VALUE c.name
|
SELECT DISTINCT |
SELECT DISTINCT c.category |
SELECT TOP |
SELECT TOP 10 * FROM c, SELECT DISTINCT TOP 5 VALUE c.cat FROM c
|
FROM |
FROM c, FROM s IN c.scores (top-level array iteration) |
WHERE |
c.age > 30 AND c.active = true |
ORDER BY |
ORDER BY c.name ASC, c.date DESC |
ORDER BY RANK |
ORDER BY RANK FullTextScore(c.text, ['term']) (for full-text search) |
GROUP BY / HAVING
|
GROUP BY c.category HAVING COUNT(1) > 5 |
OFFSET / LIMIT
|
OFFSET 10 LIMIT 20 |
JOIN |
JOIN t IN c.tags (array expansion, multiple JOINs) |
| Category | Supported |
|---|---|
| Comparison |
=, !=, <>, <, >, <=, >=
|
| Logical |
AND, OR, NOT
|
| Arithmetic |
+, -, *, /, %
|
| String concat | || |
| Null coalesce | ?? |
| Ternary | condition ? ifTrue : ifFalse |
| Bitwise |
&, |, ^, ~
|
| Range |
BETWEEN low AND high, NOT BETWEEN low AND high
|
| Membership |
IN ('a', 'b', 'c'), NOT IN (...)
|
| Pattern |
LIKE '%pattern%', NOT LIKE, LIKE ... ESCAPE (with % and _ wildcards) |
| Null checks |
IS NULL, IS NOT NULL
|
| Function | Example |
|---|---|
UPPER(str) |
SELECT VALUE UPPER(c.name) FROM c |
LOWER(str) |
SELECT VALUE LOWER(c.name) FROM c |
LTRIM(str) |
SELECT VALUE LTRIM(c.name) FROM c |
RTRIM(str) |
SELECT VALUE RTRIM(c.name) FROM c |
TRIM(str) |
SELECT VALUE TRIM(c.name) FROM c |
SUBSTRING(str, start, length) |
SELECT VALUE SUBSTRING(c.name, 0, 3) FROM c |
LENGTH(str) |
SELECT VALUE LENGTH(c.name) FROM c |
CONCAT(str1, str2, ...) |
SELECT VALUE CONCAT(c.first, ' ', c.last) FROM c |
CONTAINS(str, substr [, ignoreCase]) |
SELECT * FROM c WHERE CONTAINS(c.name, 'ali', true) |
STARTSWITH(str, prefix [, ignoreCase]) |
SELECT * FROM c WHERE STARTSWITH(c.name, 'A', true) |
ENDSWITH(str, suffix [, ignoreCase]) |
SELECT * FROM c WHERE ENDSWITH(c.name, 'e', true) |
INDEX_OF(str, substr) |
SELECT VALUE INDEX_OF(c.name, 'l') FROM c |
REPLACE(str, old, new) |
SELECT VALUE REPLACE(c.name, 'old', 'new') FROM c |
REVERSE(str) |
SELECT VALUE REVERSE(c.name) FROM c |
LEFT(str, count) |
SELECT VALUE LEFT(c.name, 3) FROM c |
RIGHT(str, count) |
SELECT VALUE RIGHT(c.name, 3) FROM c |
REPLICATE(str, count) |
SELECT VALUE REPLICATE('x', 5) FROM c |
REGEXMATCH(str, pattern [, modifiers]) |
SELECT * FROM c WHERE REGEXMATCH(c.email, '^[a-z]+@', 'i') |
StringEquals(str1, str2 [, ignoreCase]) |
SELECT * FROM c WHERE StringEquals(c.name, 'JOHN', true) |
StringJoin(separator, array) |
SELECT VALUE StringJoin(',', c.tags) FROM c |
StringSplit(str, delimiter) |
SELECT VALUE StringSplit(c.csv, ',') FROM c |
Optional arguments:
CONTAINS,STARTSWITH, andENDSWITHaccept an optional boolean 3rd argument for case-insensitive matching.REGEXMATCHaccepts an optional 3rd argument with modifier flags:'i'(ignore case),'m'(multiline),'s'(single-line),'x'(ignore whitespace).
| Function | Returns true when... |
|---|---|
IS_ARRAY(expr) |
Value is an array |
IS_BOOL(expr) |
Value is a boolean |
IS_NULL(expr) |
Value is null |
IS_DEFINED(expr) |
Property exists on the document |
IS_NUMBER(expr) |
Value is a number |
IS_OBJECT(expr) |
Value is a JSON object |
IS_STRING(expr) |
Value is a string |
IS_PRIMITIVE(expr) |
Value is string, number, boolean, or null |
IS_FINITE_NUMBER(expr) |
Value is a finite number (not NaN/Infinity) |
IS_INTEGER(expr) |
Value is an integer |
IS_NAN(expr) |
Value is NaN (Not-a-Number) |
TYPE(expr) |
Returns type name: "string", "number", "boolean", "null", "array", "object"
|
| Function | Description |
|---|---|
ABS(num) |
Absolute value |
CEILING(num) |
Ceiling (round up) |
FLOOR(num) |
Floor (round down) |
ROUND(num [, precision]) |
Round to nearest integer, or to N decimal places |
SQRT(num) |
Square root |
SQUARE(num) |
Square (num²) |
POWER(base, exp) |
Exponentiation |
EXP(num) |
e^num |
LOG(num [, base]) |
Natural logarithm, or logarithm in given base |
LOG10(num) |
Base-10 logarithm |
SIGN(num) |
Sign (-1, 0, or 1) |
TRUNC(num) |
Truncate to integer |
PI() |
π constant |
SIN(num), COS(num), TAN(num)
|
Trigonometric functions |
COT(num) |
Cotangent (1/tan) |
ASIN(num), ACOS(num), ATAN(num)
|
Inverse trigonometric functions |
ATN2(y, x) |
Two-argument arctangent |
DEGREES(radians) |
Radians to degrees |
RADIANS(degrees) |
Degrees to radians |
RAND() |
Random number [0, 1) |
NumberBin(num, binSize) |
Bin a number to the nearest multiple |
| Function | Description |
|---|---|
IntAdd(a, b) |
Integer addition |
IntSub(a, b) |
Integer subtraction |
IntMul(a, b) |
Integer multiplication |
IntDiv(a, b) |
Integer division |
IntMod(a, b) |
Integer modulo |
IntBitAnd(a, b) |
Bitwise AND |
IntBitOr(a, b) |
Bitwise OR |
IntBitXor(a, b) |
Bitwise XOR |
IntBitNot(a) |
Bitwise NOT |
IntBitLeftShift(a, b) |
Left shift |
IntBitRightShift(a, b) |
Right shift |
| Function | Example |
|---|---|
ARRAY_CONTAINS(arr, val) |
SELECT * FROM c WHERE ARRAY_CONTAINS(c.tags, 'urgent') |
ARRAY_CONTAINS_ANY(arr, searchArr) |
true if any element of searchArr exists in arr |
ARRAY_CONTAINS_ALL(arr, searchArr) |
true if all elements of searchArr exist in arr |
ARRAY_LENGTH(arr) |
SELECT VALUE ARRAY_LENGTH(c.items) FROM c |
ARRAY_SLICE(arr, start, length) |
SELECT VALUE ARRAY_SLICE(c.items, 0, 3) FROM c |
ARRAY_CONCAT(arr1, arr2) |
SELECT VALUE ARRAY_CONCAT(c.tags, c.labels) FROM c |
SetIntersect(arr1, arr2) |
Set intersection |
SetUnion(arr1, arr2) |
Set union |
SetDifference(arr1, arr2) |
Set difference (elements in arr1 not in arr2) |
CHOOSE(index, val1, val2, ...) |
1-based index selection from argument list |
ObjectToArray(object) |
Convert {a:1, b:2} to [{k:"a", v:1}, ...]
|
ArrayToObject(array) |
Convert [{k:"a", v:1}, ...] to {a:1, b:2} (inverse of ObjectToArray) |
| Function | Description |
|---|---|
TOSTRING(value) |
Convert to string |
TONUMBER(value) |
Convert to number |
TOBOOLEAN(value) |
Convert to boolean |
StringToArray(str) |
Parse JSON array string |
StringToBoolean(str) |
Parse boolean string |
StringToNull(str) |
Parse "null" string |
StringToNumber(str) |
Parse number string |
StringToObject(str) |
Parse JSON object string |
| Function | Description |
|---|---|
GetCurrentDateTime() |
Current UTC datetime as ISO string |
GetCurrentTimestamp() |
Current UTC as Unix timestamp (ms) |
GetCurrentTicks() |
Current UTC as .NET ticks |
GetCurrentDateTimeStatic() |
Same datetime for all items in a query |
GetCurrentTimestampStatic() |
Same timestamp for all items in a query |
GetCurrentTicksStatic() |
Same ticks for all items in a query |
DateTimeAdd(part, num, datetime) |
Add to a datetime |
DateTimePart(part, datetime) |
Extract part of a datetime |
DateTimeDiff(part, start, end) |
Difference between two datetimes |
DateTimeBin(datetime, part, binSize, [origin]) |
Bin a datetime to intervals |
DateTimeFromParts(year, month, day, hour, min, sec, ms) |
Construct a datetime |
DateTimeToTicks(datetime) |
Convert ISO datetime to .NET ticks |
DateTimeToTimestamp(datetime) |
Convert ISO datetime to Unix timestamp (ms) |
TicksToDateTime(ticks) |
Convert .NET ticks to ISO datetime |
TimestampToDateTime(timestamp) |
Convert Unix timestamp (ms) to ISO datetime |
| Function | Description |
|---|---|
COUNT(expr), COUNT(1), or COUNT(*)
|
Count items |
SUM(expr) |
Sum numeric values |
AVG(expr) |
Average of numeric values |
MIN(expr) |
Minimum value |
MAX(expr) |
Maximum value |
| Function | Description |
|---|---|
IIF(condition, ifTrue, ifFalse) |
Inline conditional (only boolean true triggers true branch; numbers, strings, etc. yield false branch) |
COALESCE(expr1, expr2, ...) |
First non-null/undefined value |
| Function | Description |
|---|---|
DOCUMENTID(item) |
Returns the document's resource ID |
| Function | Implementation |
|---|---|
ST_DISTANCE(point1, point2) |
Haversine formula (metres) |
ST_WITHIN(point, region) |
Point-in-polygon (ray casting) + point-in-circle (haversine radius) |
ST_INTERSECTS(geo1, geo2) |
Point-point, point-polygon, polygon-polygon overlap |
ST_ISVALID(geojson) |
Full GeoJSON validation (Point, Polygon, LineString, MultiPoint) |
ST_ISVALIDDETAILED(geojson) |
Returns { valid, reason } with specific error messages |
ST_AREA(polygon) |
Spherical excess formula |
All geospatial functions use real geometric calculations. Results may differ slightly from Cosmos DB's exact values. See Known Limitations for details on precision differences.
| Function | Implementation |
|---|---|
VECTORDISTANCE(vec1, vec2) |
Cosine similarity (default). Returns -1 to +1 |
VECTORDISTANCE(vec1, vec2, bruteForce) |
3rd bool arg accepted but ignored (always brute-force) |
VECTORDISTANCE(vec1, vec2, false, {distanceFunction:'cosine'}) |
Cosine similarity: `dot(a,b) / ( |
VECTORDISTANCE(vec1, vec2, false, {distanceFunction:'dotproduct'}) |
Dot product: Σ(a[i] × b[i])
|
VECTORDISTANCE(vec1, vec2, false, {distanceFunction:'euclidean'}) |
Euclidean distance: √Σ(a[i] - b[i])²
|
Works in SELECT projections, WHERE filters, and ORDER BY clauses. Supports vectors of any dimensionality (tested up to 2000). Returns null for mismatched dimensions, missing vector properties, zero-magnitude vectors (cosine), non-numeric elements, multi-dimensional arrays, or Infinity/NaN overflow results. Additional options (dataType, searchListSizeMultiplier, filterPriority) are accepted but ignored. Unknown distance functions silently fall back to cosine. Extra arguments beyond the 4th are silently ignored. Works with arithmetic expressions (VectorDistance(...) * 100), function composition (ABS(VectorDistance(...))), and conditional logic (IIF(VectorDistance(...) > 0.5, ...)).
-- Find the 10 most similar documents by cosine similarity
SELECT TOP 10 c.title, VectorDistance(c.embedding, [0.1, 0.2, 0.3]) AS score
FROM c
ORDER BY VectorDistance(c.embedding, [0.1, 0.2, 0.3]) DESC
-- Filter by similarity threshold
SELECT c.id FROM c
WHERE VectorDistance(c.embedding, [0.1, 0.2, 0.3]) > 0.5
-- Arithmetic on vector scores
SELECT c.id, VectorDistance(c.embedding, [0.1, 0.2, 0.3]) * 100 AS pctScore
FROM cNo vector index policy or container configuration is required. The emulator always performs exact (brute-force) distance computation, which is correct for testing but does not simulate ANN index behaviour (DiskANN, quantizedFlat, flat). See Known Limitations for details.
The emulator provides approximate implementations of all four Cosmos DB full-text search functions using case-insensitive substring matching. Real Cosmos DB uses NLP tokenisation and BM25 scoring.
| Function | Description |
|---|---|
FullTextContains(field, term) |
true if the field contains the search term |
FullTextContainsAll(field, term1, term2, ...) |
true if the field contains all search terms |
FullTextContainsAny(field, term1, term2, ...) |
true if the field contains any search term |
FullTextScore(field, [term1, term2, ...]) |
Returns a relevance score (naive term-frequency count) |
Use ORDER BY RANK FullTextScore(...) to sort results by relevance:
-- Find documents containing 'database', sorted by relevance
SELECT * FROM c
WHERE FullTextContains(c.description, 'database')
ORDER BY RANK FullTextScore(c.description, ['database', 'cosmos'])Note: Real Cosmos DB requires a full-text indexing policy. The emulator skips this validation — queries work on any container without configuration. Matching is approximate (case-insensitive substring, no stemming, no BM25 scoring). See Known Limitations for details.
var query = new QueryDefinition(
"SELECT * FROM c WHERE c.status = @status AND c.age > @minAge")
.WithParameter("@status", "active")
.WithParameter("@minAge", 21);
var iterator = container.GetItemQueryIterator<MyDoc>(query);-- EXISTS
SELECT * FROM c WHERE EXISTS(
SELECT VALUE 1 FROM t IN c.tags WHERE t = 'important')
-- ARRAY()
SELECT c.id, ARRAY(
SELECT VALUE t FROM t IN c.tags WHERE t != 'draft') AS filteredTags
FROM c
-- Scalar subqueries in SELECT and WHERE
SELECT (SELECT VALUE COUNT(1) FROM t IN c.items) AS itemCount FROM cSubqueries support ORDER BY, OFFSET, and LIMIT clauses, evaluated within the subquery scope:
-- Sorted subquery
SELECT ARRAY(SELECT VALUE s FROM s IN c.scores ORDER BY s DESC) AS sorted FROM c
-- Paginated subquery (skip 1, take 2)
SELECT ARRAY(SELECT VALUE s FROM s IN c.scores OFFSET 1 LIMIT 2) AS page FROM c
-- Combined: sort, then paginate
SELECT ARRAY(SELECT VALUE s FROM s IN c.scores ORDER BY s ASC OFFSET 1 LIMIT 3) AS page FROM cRegister C# functions callable as udf.name() in SQL. See API Reference for the full RegisterUdf signature.
container.RegisterUdf("IsEven", args =>
{
if (args[0] is not long num) return false;
return num % 2 == 0;
});
var iterator = container.GetItemQueryIterator<dynamic>(
"SELECT * FROM c WHERE udf.IsEven(c.value)");The SQL parser is also available directly for advanced scenarios. See API Reference for the full class reference.
// Parse a query
var parsed = CosmosSqlParser.Parse("SELECT c.name FROM c WHERE c.age > 30");
// Inspect the AST
Console.WriteLine(parsed.FromAlias); // "c"
Console.WriteLine(parsed.Fields[0].Alias); // "name"
// Try-parse (no exception on failure)
if (CosmosSqlParser.TryParse(sql, out var result))
{
// Use result
}Getting Started
Integration & Dependency Injection
Data Management
Reference
Help