Skip to content

Portable API Surface

The Multicloud DB SDK's portable API surface covers capabilities that work identically across all three providers. The features listed below require no runtime capability checks - they are guaranteed to work on Azure Cosmos DB, Amazon DynamoDB, and Google Cloud Spanner. Some providers offer additional capabilities (e.g., CROSS_PARTITION_QUERY, ORDER_BY, LIKE); use client.capabilities() to discover what the current provider supports.


What Works Everywhere

Every capability listed below is fully supported on all providers. There are no asterisks, no provider-specific caveats, and no runtime checks required.

CRUD Operations

Operation Description
Create Insert a new document (fails if the key already exists)
Read Point-read by partition key + sort key
Update Replace an existing document (fails if not found)
Upsert Create or replace - always succeeds
Delete Remove by key (idempotent)

Query - Portable Expression DSL

Write a WHERE-clause filter once. The SDK translates it to the native query language of whichever provider is configured - Cosmos SQL, DynamoDB PartiQL, or Spanner GoogleSQL.

Feature Operators / Functions Example
Comparison =, !=, <, >, <=, >= status = 'active'
Logical AND, OR, NOT age > 18 AND active = true
String functions STARTS_WITH, CONTAINS STARTS_WITH(name, 'A')
Field introspection FIELD_EXISTS FIELD_EXISTS(metadata)
Length functions STRING_LENGTH, COLLECTION_SIZE STRING_LENGTH(name) > 3
Named parameters @paramName price > @minPrice
QueryRequest query = QueryRequest.builder()
    .expression("STARTS_WITH(name, @prefix) AND age >= @minAge")
    .parameter("prefix", "J")
    .parameter("minAge", 21)
    .maxPageSize(50)
    .build();

QueryPage page = client.query(address, query);

Pagination

Feature Description
Cursor-based paging Continuation-token pagination across all providers
Page size control maxPageSize to limit results per page

Data Management

Feature Description
Schema provisioning provisionSchema() creates databases, containers, and tables portably
Transactions Multi-document transactional operations
Batch operations Batch read/write for throughput efficiency
Strong consistency Strongly-consistent reads
Change feed Change feed / change streams

Diagnostics & Error Handling

Feature Description
Structured diagnostics Latency, request charge, and provider correlation IDs per operation
Portable error categories All provider exceptions mapped to MulticloudDbErrorCategory
Capability introspection client.capabilities() reports what the current provider supports

Portable Error Mapping

All provider exceptions are mapped to portable MulticloudDbErrorCategory values. The raw HTTP or gRPC status code is also available via error.statusCode().

Category Cosmos DB DynamoDB Spanner
INVALID_REQUEST HTTP 400 ValidationException, HTTP 400 INVALID_ARGUMENT, FAILED_PRECONDITION
AUTHENTICATION_FAILED HTTP 401 UnrecognizedClientException, HTTP 401/403 UNAUTHENTICATED
AUTHORIZATION_FAILED HTTP 403 AccessDeniedException PERMISSION_DENIED
NOT_FOUND HTTP 404 ResourceNotFoundException, HTTP 404 NOT_FOUND
CONFLICT (409 - duplicate key) HTTP 409 ConditionalCheckFailedException from create() - attribute_not_exists guard fails when the item already exists ALREADY_EXISTS
CONFLICT (412 - precondition) HTTP 412 ConditionalCheckFailedException from update()/upsert() with a condition expression¹ ABORTED
THROTTLED HTTP 429 ProvisionedThroughputExceededException, ThrottlingException RESOURCE_EXHAUSTED
TRANSIENT_FAILURE HTTP 449, 500, 502, 503 HTTP 500–5xx UNAVAILABLE
PERMANENT_FAILURE - ItemCollectionSizeLimitExceededException -
UNSUPPORTED_CAPABILITY - - UNIMPLEMENTED
PROVIDER_ERROR Other Other INTERNAL, Other

¹ DynamoDB uses ConditionalCheckFailedException for both the 409 (duplicate-key on create) and 412 (precondition failure on conditional update/upsert) cases - both currently map to CONFLICT. The portable API does not yet expose ETag-based conditional updates; when it does, the 412-equivalent path will be split into a dedicated PRECONDITION_FAILED category (tracked in issue #29).

Default Sort-Key Ordering

All Cosmos DB and DynamoDB query paths return results sorted by the document's sort key ascending.

Design note: The default ORDER BY is applied to all Cosmos queries (both partition-scoped and cross-partition), not just partition-scoped ones. This gives the strongest consistency guarantee: every query, on every provider, returns items sorted by sort key. The early PR description mentioned partition-scoped only as a starting point; the final implementation was intentionally broadened to cover all queries.

Cosmos DB

Cosmos DB appends ORDER BY c.id ASC to every query that does not already carry an explicit ORDER BY clause (and is not an aggregate / GROUP BY query). This is applied server-side, so the order is globally consistent across all pages.

⚠️ Custom indexing policy - composite index required If your Cosmos container uses a custom indexing policy that does not include a composite index on (filterField ASC, id ASC), Cosmos DB will throw a 400 Bad Request at runtime for cross-partition queries that combine WHERE and the default ORDER BY c.id ASC. The default indexing policy includes all paths and supports this automatically. If you have tuned your indexing policy, add the composite index for every field you filter on:

{ "compositeIndexes": [ [{ "path": "/filterField", "order": "ascending" },
                         { "path": "/id", "order": "ascending" }] ] }

⚠️ RU cost Appending ORDER BY c.id ASC to all Cosmos queries incurs an additional RU charge versus unordered queries, proportional to result-set size. This cost is the price of cross-provider consistency and is expected behavior.

⚠️ Aggregates and GROUP BY Cosmos DB rejects ORDER BY on aggregate expressions (COUNT, SUM, MIN, MAX, AVG) and GROUP BY queries. The SDK automatically detects these patterns and omits the default ORDER BY for them.

DynamoDB

DynamoDB results are sorted in memory per page after fetching (client-side). Within a single page, items are returned sorted by sort key ascending. For multi-page scans the overall order across pages is determined by DynamoDB's internal token-based traversal, not sort key - this is a known limitation.

Spanner

The Spanner provider does not yet implement default sort-key ordering. Consumers relying on consistent cross-provider sort behavior should not use the Spanner provider until this gap is addressed.

Tracking: A follow-up issue will be filed to implement default sort-key ordering for the Spanner provider. Until resolved, do not mix Spanner with Cosmos or DynamoDB in conformance-sensitive workloads.

Escape Hatch Policy

The SDK does not expose a nativeClient() method. Direct access to the underlying provider client is intentionally omitted to enforce portability guarantees - code written against the SDK must remain switchable between providers by configuration alone.