Azure Cosmos DB (SQL API)
Compatible with the azure-cosmos SDK (Java, Python, JavaScript, .NET).
Features
- Databases: create, get, list, delete (cascade-deletes all containers and documents)
- Containers: create, replace, get, list, delete; configurable partition key path; custom indexing policies with composite indexes (persisted on create/replace and returned on read)
- Documents: create, get, replace, delete, list; upsert via
x-ms-documentdb-is-upsertheader - Stored procedures, triggers and user-defined functions: create, get, replace, delete, list and query, stored per container (deleting the container or database removes them). They are stored, never run: see Server-side scripts
- Time to live (TTL): container
defaultTtl(set on create or replace) with per-documentttloverrides; expired documents disappear from reads, lists, queries, and batches - Queries: in-process SQL engine with full Cosmos DB SQL dialect support:
SELECT *,SELECT c.field1, c.field2,SELECT VALUE c.field,SELECT TOP nWHEREwith=,!=,<>,>,>=,<,<=,IN,BETWEEN,NOT,AND,OR, and correlatedEXISTSover arrays- Logical precedence follows Cosmos DB:
NOTbinds beforeAND, thenOR; parentheses override that order WHEREfunctions:IS_DEFINED,IS_NULL,IS_STRING,IS_NUMBER,IS_BOOL,IS_ARRAY,IS_OBJECT,CONTAINS,STARTSWITH,ENDSWITH,ARRAY_CONTAINSORDER BY field [ASC|DESC], multiple fields: like Azure, anORDER BYover two or more properties requires a matching composite index on the container, otherwise the query fails with400 BadRequest(errorSC2104)OFFSET n LIMIT mpaginationSELECT VALUE COUNT(1)aggregation- Named parameters (
@param), including array values inARRAY_CONTAINS(@values, c.field); both membership arguments may be expressions, andNOT ARRAY_CONTAINS(...)excludes matches - System properties:
_rid,_self,_etag,_ts,_attachmentsauto-generated on every write - Partition keys: resolved from
x-ms-documentdb-partitionkeyheader or extracted from document body using the container's configured path
Queries with x-ms-documentdb-partitionkey (including .NET QueryRequestOptions.PartitionKey)
are scoped to the configured logical partition before SQL filtering, aggregation, ordering and
pagination. Key values retain their JSON types; [null] and [{}] select null and undefined
keys respectively. Omitting the header permits cross-partition queries. Malformed scopes return 400.
Writes reject partition headers that disagree with the document body, and patches cannot change
partition-key values. Transactional batches enforce the same rules before committing staged writes.
Query continuation tokens bookmark the last source document and its ORDER BY values, including
a stable identity to distinguish equal sort values. Deleting an already consumed page, including
through transactional batches, does not skip remaining documents. This also applies to projections
that omit the sort fields or return scalar values. TOP and OFFSET ... LIMIT retain their total
query limits across pages. Tokens are stateless; no server-side query session is required.
New tokens are bound to their query text, parameters, account, container identity, and partition
scope. Reusing them with a different query or scope returns 400 BadRequest.
Parameter order, JSON object member order, and equivalent numeric representations do not change
the scope. Array element order remains significant.
Pagination does not provide snapshot isolation for concurrent inserts or changes to sort values.
Aggregate, GROUP BY, and DISTINCT queries retain the emulator's existing result-offset pagination;
the document bookmark guarantee applies to queries without aggregation or deduplication.
Tokens issued by older emulator versions also retain their original offset pagination until
completion. Start a new query to use document bookmarks after upgrading.
.NET query planner configuration
The account response advertises query-engine capabilities. Floci-AZ emits 20 keys, while the observed Azure gateway payloads contain 19. Two details intentionally differ:
sqlAllowLikeistrueso the advertised capability set matches what Floci-AZ's query engine can execute. Azure currently advertisesfalse.sqlDisableOptimizationFlagsdoes not appear in the observed Azure payloads. Floci-AZ emits the extra key with value0so its advertised set remains a superset; no current code reads it.
These are client-planning compatibility values, not user configuration.
Endpoint
Default account: devstoreaccount1
Default endpoint: http://localhost:4577/devstoreaccount1-cosmos
SDK Connection
import com.azure.cosmos.CosmosClient;
import com.azure.cosmos.CosmosClientBuilder;
CosmosClient client = new CosmosClientBuilder()
.endpoint("https://localhost:4578/devstoreaccount1-cosmos")
.key("C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==")
.gatewayMode()
.endpointDiscoveryEnabled(false)
.buildClient();
[!TIP] Any key is accepted: the well-known Cosmos DB emulator key above works out of the box with all SDKs.
[!NOTE] Port difference by SDK: The Java SDK enforces TLS in gateway mode and cannot use plain HTTP, so it connects to
https://localhost:4578(HTTPS, bundled self-signed cert: no import required). Python and Node.js SDKs accept plain HTTP and connect tohttp://localhost:4577/....
API Reference
Databases
| Method | Path | Description |
|---|---|---|
POST |
/dbs |
Create a database |
GET |
/dbs |
List all databases |
GET |
/dbs/{dbId} |
Get a database |
DELETE |
/dbs/{dbId} |
Delete a database (cascades to containers and documents) |
Containers (Collections)
| Method | Path | Description |
|---|---|---|
POST |
/dbs/{dbId}/colls |
Create a container |
GET |
/dbs/{dbId}/colls |
List containers |
GET |
/dbs/{dbId}/colls/{collId} |
Get a container |
PUT |
/dbs/{dbId}/colls/{collId} |
Replace container properties (e.g. the indexing policy or defaultTtl; id and partition key are immutable) |
DELETE |
/dbs/{dbId}/colls/{collId} |
Delete a container (cascades to documents) |
Indexing policies
A custom indexingPolicy (included/excluded paths, composite indexes) supplied on container
create or replace is normalized the way Azure does: missing fields are filled with defaults
and composite-index path order defaults to ascending: then persisted and returned on
container read. The id and partitionKey of a container are immutable on replace, matching Azure.
Composite indexes are enforced for queries: an ORDER BY over two or more properties is only
served when the container has a composite index whose paths match the ORDER BY clause exactly
(same properties, same sequence, same length) with sort directions matching either exactly or
exactly inverted on all paths. Queries without such an index fail with 400 BadRequest
("The order by query does not have a corresponding composite index that it can be served from"),
so code that would break in production fails locally too.
{
"id": "messages",
"partitionKey": { "paths": ["/conversationId"], "kind": "Hash" },
"indexingPolicy": {
"indexingMode": "consistent",
"automatic": true,
"includedPaths": [{ "path": "/*" }],
"excludedPaths": [{ "path": "/\"_etag\"/?" }],
"compositeIndexes": [[
{ "path": "/conversationId", "order": "ascending" },
{ "path": "/sequence", "order": "ascending" }
]]
}
}
Documents
| Method | Path | Description |
|---|---|---|
POST |
/dbs/{dbId}/colls/{collId}/docs |
Create a document |
GET |
/dbs/{dbId}/colls/{collId}/docs |
List all documents |
GET |
/dbs/{dbId}/colls/{collId}/docs/{docId} |
Get a document |
PUT |
/dbs/{dbId}/colls/{collId}/docs/{docId} |
Replace a document |
DELETE |
/dbs/{dbId}/colls/{collId}/docs/{docId} |
Delete a document |
Queries
POST /dbs/{dbId}/colls/{collId}/docs with header x-ms-documentdb-isquery: True (or Content-Type: application/query+json).
Server-side scripts
| Method | Path | Description |
|---|---|---|
POST |
/dbs/{dbId}/colls/{collId}/{sprocs,triggers,udfs} |
Create a script (409 Conflict if the id exists) |
GET |
/dbs/{dbId}/colls/{collId}/{sprocs,triggers,udfs} |
List scripts (StoredProcedures, Triggers, UserDefinedFunctions) |
POST |
same path, with x-ms-documentdb-isquery: True |
Query scripts with SQL, for example SELECT * FROM root r WHERE r.id = @id |
GET |
/dbs/{dbId}/colls/{collId}/{sprocs,triggers,udfs}/{id} |
Get a script |
PUT |
/dbs/{dbId}/colls/{collId}/{sprocs,triggers,udfs}/{id} |
Replace a script (honours If-Match) |
DELETE |
/dbs/{dbId}/colls/{collId}/{sprocs,triggers,udfs}/{id} |
Delete a script |
POST |
/dbs/{dbId}/colls/{collId}/sprocs/{id} |
Execute a stored procedure: not supported, see below |
Every script needs an id and a string body; a trigger also needs triggerType (Pre or Post)
and triggerOperation (All, Create, Update, Delete or Replace), matched case-insensitively
and returned as sent. A missing or invalid field is 400 BadRequest, on create and on replace, where
the id must also match the one in the URL. List and query honour x-ms-max-item-count and
x-ms-continuation. Responses carry _rid, _self,
_etag and _ts like any other Cosmos resource, so SDK code that registers scripts through
container.getScripts() (Java), container.scripts (Python) or container.scripts (JavaScript)
works unchanged.
Emulator limitation: scripts are never run. floci-az has no JavaScript runtime:
- Executing a stored procedure that exists fails with
501and the Cosmos error body{"code":"NotImplemented","message":"Executing stored procedure '<id>' is not supported: ..."}, which every SDK raises as its Cosmos exception (CosmosExceptionin Java,CosmosHttpResponseErrorin Python). Executing one that does not exist is404 NotFound. - Pre- and post-triggers named on a document request (
x-ms-documentdb-pre-trigger-include,x-ms-documentdb-post-trigger-include) are ignored: the write goes ahead without them. - User-defined functions cannot be called from a query (
udf.name(...)).
Any other request the emulator does not implement also answers 501 with a
{"code":"NotImplemented","message":"..."} body, never an empty response.
Request / Response Examples
Create database
curl -X POST http://localhost:4577/devstoreaccount1-cosmos/dbs \
-H "Content-Type: application/json" \
-d '{"id": "mydb"}'
Create container
curl -X POST http://localhost:4577/devstoreaccount1-cosmos/dbs/mydb/colls \
-H "Content-Type: application/json" \
-d '{"id": "items", "partitionKey": {"paths": ["/category"], "kind": "Hash"}}'
Create document
curl -X POST http://localhost:4577/devstoreaccount1-cosmos/dbs/mydb/colls/items/docs \
-H "Content-Type: application/json" \
-H "x-ms-documentdb-partitionkey: [\"electronics\"]" \
-d '{"id": "laptop-1", "category": "electronics", "name": "Laptop Pro", "price": 1299}'
Query documents
curl -X POST http://localhost:4577/devstoreaccount1-cosmos/dbs/mydb/colls/items/docs \
-H "Content-Type: application/query+json" \
-H "x-ms-documentdb-isquery: True" \
-H "x-ms-documentdb-query-enablecrosspartition: True" \
-d '{
"query": "SELECT * FROM c WHERE c.price > @minPrice ORDER BY c.price DESC",
"parameters": [{"name": "@minPrice", "value": 500}]
}'
Query with COUNT
curl -X POST .../docs \
-H "x-ms-documentdb-isquery: True" \
-d '{"query": "SELECT VALUE COUNT(1) FROM c WHERE c.category = '\''electronics'\''"}'
Response: {"_rid": "...", "_count": 1, "Documents": [2]}
Supported SQL functions
Full Azure Cosmos DB SQL grammar implemented in-process.
Predicates & type checks
| Function | Description |
|---|---|
IS_DEFINED(c.field) |
True if the field exists on the document |
IS_NULL(c.field) |
True if the field is null or missing |
IS_STRING(c.field) |
True if the value is a string |
IS_NUMBER(c.field) |
True if the value is a number |
IS_INTEGER(c.field) |
True if the value is an integer |
IS_BOOL(c.field) |
True if the value is a boolean |
IS_ARRAY(c.field) |
True if the value is an array |
IS_OBJECT(c.field) |
True if the value is an object |
IS_PRIMITIVE(c.field) |
True if the value is a scalar (string, number, bool, or null) |
CONTAINS(c.field, 'str' [, true]) |
String contains; optional 3rd arg for case-insensitive |
STARTSWITH(c.field, 'prefix' [, true]) |
String starts-with; optional 3rd arg for case-insensitive |
ENDSWITH(c.field, 'suffix' [, true]) |
String ends-with; optional 3rd arg for case-insensitive |
STRINGEQUALS(c.field, 'val' [, true]) |
Case-insensitive equality when 3rd arg is true |
REGEXMATCH(c.field, 'pattern' [, 'flags']) |
Regular-expression match |
ARRAY_CONTAINS(c.arr, value) |
Array contains value |
LIKE |
Pattern matching (% wildcard, _ single char) |
String functions
| Function | Description |
|---|---|
LOWER(c.field) |
Convert to lower case |
UPPER(c.field) |
Convert to upper case |
LENGTH(c.field) |
String length |
CONCAT(s1, s2, ...) |
Concatenate strings |
SUBSTRING(s, start, length) |
Extract substring |
TRIM(s) |
Remove leading and trailing spaces |
LTRIM(s) / RTRIM(s) |
Remove leading / trailing spaces |
REPLACE(s, old, new) |
Replace occurrences |
REVERSE(s) |
Reverse string |
INDEX_OF(s, sub) |
Position of first occurrence (−1 if not found) |
LEFT(s, n) / RIGHT(s, n) |
Extract leftmost / rightmost n characters |
TOSTRING(val) |
Convert value to string |
STRINGJOIN(separator, arr) |
Join array elements with separator |
STRINGSPLIT(s, delimiter) |
Split string into array |
Math functions
| Function | Description |
|---|---|
ABS(n) |
Absolute value |
CEILING(n) |
Smallest integer ≥ n |
FLOOR(n) |
Largest integer ≤ n |
ROUND(n) |
Round to nearest integer |
SQRT(n) |
Square root |
POWER(base, exp) |
Exponentiation |
LOG(n [, base]) |
Natural log or log base |
LOG10(n) |
Base-10 logarithm |
EXP(n) |
e^n |
SIGN(n) |
−1, 0, or 1 |
TRUNC(n) |
Truncate toward zero |
PI() |
π |
RAND() |
Random number in [0, 1) |
Array functions
| Function | Description |
|---|---|
ARRAY_LENGTH(arr) |
Number of elements |
ARRAY_SLICE(arr, start [, count]) |
Extract slice |
ARRAY_CONCAT(arr1, arr2, ...) |
Concatenate arrays |
Conditional
| Function | Description |
|---|---|
IIF(condition, trueVal, falseVal) |
Inline if-else expression |
Upsert
Set x-ms-documentdb-is-upsert: True on POST /docs to create or silently overwrite:
curl -X POST .../docs \
-H "x-ms-documentdb-is-upsert: True" \
-d '{"id": "laptop-1", "category": "electronics", "price": 999}'
Time to live (TTL)
Containers accept Azure's defaultTtl property on create and replace (-1, or a positive
number of seconds up to 2147483647; 0 is rejected with 400, matching Azure). Documents
may override it with their own ttl property. Expiry follows the
Azure TTL semantics, measured
from the document's last modification (_ts):
Container defaultTtl |
Document ttl |
Result |
|---|---|---|
| absent | anything | TTL disabled: nothing expires |
-1 |
absent or -1 |
never expires |
-1 |
m |
expires m seconds after _ts |
n |
absent | expires n seconds after _ts |
n |
-1 |
never expires |
n |
m |
expires m seconds after _ts |
# Create a container whose documents expire after one hour
curl -X POST http://localhost:4577/devstoreaccount1-cosmos/dbs/mydb/colls \
-H "Content-Type: application/json" \
-d '{"id": "audit", "partitionKey": {"paths": ["/tenant"], "kind": "Hash"}, "defaultTtl": 3600}'
# Switch TTL off again (omit defaultTtl on replace)
curl -X PUT http://localhost:4577/devstoreaccount1-cosmos/dbs/mydb/colls/audit \
-H "Content-Type: application/json" \
-d '{"id": "audit"}'
Expired documents vanish from point reads, lists, queries, and transactional batches immediately (and no longer block re-creating the same id). Physical deletion is lazy: an expired document is purged when a read next encounters it: mirroring Azure's contract that expired items leave query results at once while background deletion timing is unspecified.
Storage Mode
# docker-compose.yml
environment:
FLOCI_AZ_STORAGE_MODE: memory
FLOCI_AZ_STORAGE_SERVICES_COSMOS_MODE: wal # full durability for Cosmos documents
Environment Variables
| Variable | Default | Description |
|---|---|---|
FLOCI_AZ_SERVICES_COSMOS_ENABLED |
true |
Enable or disable Cosmos DB |
FLOCI_AZ_SERVICES_COSMOS_MOCKED |
false |
Master switch: when true, no engine containers are started for any API (equivalent to engines.startup=disabled). The in-process NoSQL/Table paths are unaffected. |
Multi-API engines
The API reference above covers the always-on SQL / NoSQL endpoint ({account}-cosmos and
{account}-cosmos-nosql). Floci AZ also emulates the other Cosmos DB APIs through API-specific engines.
All engines are disabled by default: enable only the APIs your application uses. Four APIs are Docker-backed (MongoDB, PostgreSQL, Cassandra, Gremlin): they launch a sidecar container on first request. Two APIs are embedded (NoSQL and Table): in-process, no Docker pull, instant startup.
Docker-backed engines
| Variable | Default | Engine image | Native port |
|---|---|---|---|
FLOCI_AZ_SERVICES_COSMOS_ENGINES_MONGODB_ENABLED |
false |
mongo:7 |
27017 |
FLOCI_AZ_SERVICES_COSMOS_ENGINES_POSTGRESQL_ENABLED |
false |
citusdata/citus |
5432 |
FLOCI_AZ_SERVICES_COSMOS_ENGINES_CASSANDRA_ENABLED |
false |
scylladb/scylla:6.2 |
9042 |
FLOCI_AZ_SERVICES_COSMOS_ENGINES_GREMLIN_ENABLED |
false |
tinkerpop/gremlin-server |
8182 |
You can override the Docker image or host port for any Docker-backed engine:
| Variable | Description |
|---|---|
FLOCI_AZ_SERVICES_COSMOS_ENGINES_MONGODB_IMAGE |
Override the MongoDB image |
FLOCI_AZ_SERVICES_COSMOS_ENGINES_MONGODB_PORT |
Override the MongoDB host port |
FLOCI_AZ_SERVICES_COSMOS_ENGINES_STARTUP |
on-demand (default) or eager |
docker-compose.yml example (enable MongoDB and PostgreSQL):
services:
floci-az:
image: floci/floci-az:latest
ports:
- "4577:4577"
- "27017:27017" # MongoDB (Cosmos MongoDB API)
- "5432:5432" # PostgreSQL (Cosmos PostgreSQL API)
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
FLOCI_AZ_SERVICES_COSMOS_ENGINES_MONGODB_ENABLED: "true"
FLOCI_AZ_SERVICES_COSMOS_ENGINES_POSTGRESQL_ENABLED: "true"
How it works: when you first send a request to /{account}-cosmos-mongo/, floci-az pulls mongo:7
and starts the container. Subsequent requests go directly to the container's native port
(localhost:27017). The /connect endpoint returns the connection string:
curl http://localhost:4577/devstoreaccount1-cosmos-mongo/connect
# → {"api":"MONGODB","host":"localhost","port":27017,"connectionString":"mongodb://localhost:27017/","status":"running"}
Do not publish engine ports (
27017,5432, etc.) on thefloci-azservice. Engines are launched as sibling containers by the host Docker daemon, so they bind ports directly on the host.
With FLOCI_AZ_DOCKER_ENDPOINT_MODE=published (a remote Docker daemon), host and the connection string
name the Docker daemon's host and the engine's published port instead of localhost.
Embedded engines: NoSQL and Table API (no Docker)
Both engines run entirely inside floci-az: no Docker pull, no container boot time. Data lives in memory; restarting floci-az clears it.
| Variable | Default | Backend |
|---|---|---|
FLOCI_AZ_SERVICES_COSMOS_ENGINES_NOSQL_ENABLED |
false |
In-process SQL engine: full Cosmos DB SQL dialect |
FLOCI_AZ_SERVICES_COSMOS_ENGINES_TABLE_ENABLED |
false |
In-memory OData engine (ConcurrentHashMap) |
NoSQL engine: activating this endpoint enables the same embedded SQL engine already powering
/{account}-cosmos. The /connect endpoint returns https://localhost:4577 as the connection URL
(Java SDK requires TLS; enable FLOCI_AZ_TLS_ENABLED=true and fetch the runtime cert from GET /_floci/tls-cert).
Table engine: supported operations: create/delete table · insert/get/replace/merge/delete entity ·
OData $filter · $top · $select. OData operators: eq, ne, gt, ge, lt, le, and, or, not.
# Enable the Table engine
export FLOCI_AZ_SERVICES_COSMOS_ENGINES_TABLE_ENABLED=true
# Trigger engine activation and retrieve connection string
curl http://localhost:4577/devstoreaccount1-cosmos-table/connect
# → {"api":"TABLE","status":"running","connectionString":"DefaultEndpointsProtocol=http;...","notes":"..."}
Use the host and port from the /connect response to build the endpoint, then connect
with AzureNamedKeyCredential: the official Cosmos DB for Table pattern
(quickstart):
// official Cosmos DB for Table SDK pattern
String endpoint = "http://" + host + ":" + port + "/devstoreaccount1-cosmos-table";
TableServiceClient client = new TableServiceClientBuilder()
.endpoint(endpoint)
.credential(new AzureNamedKeyCredential(
"devstoreaccount1",
"Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="))
.buildClient();
from azure.data.tables import TableServiceClient
from azure.core.credentials import AzureNamedKeyCredential
credential = AzureNamedKeyCredential("devstoreaccount1",
"Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==")
client = TableServiceClient(
endpoint=f"http://{host}:{port}/devstoreaccount1-cosmos-table",
credential=credential)
The connectionString field in the /connect response is also available for SDK clients that prefer the
Azure Storage connection string format.
Known Limitations
- Stored procedures, triggers, and UDFs are stored but not executed (see Server-side scripts).
- JOIN with nested arrays is not supported.
- Change feed is not emulated.
- Full-text search, vector search, and geospatial queries are not supported.
- RU/s throughput governance and multi-region replication are out of scope.
- Partition key paths must be a single top-level field (e.g.
/category), not nested paths.