Skip to content

Commit 564afb1

Browse files
mbhaskarCopilot
andcommitted
Add GlobalSecondaryIndexDefinition for GSI container support
- Create GlobalSecondaryIndexDefinition class with validation, serialization, and dual-write pattern (globalSecondaryIndexDefinition + materializedViewDefinition) - Add global_secondary_index_definition keyword to create_container, create_container_if_not_exists, and replace_container (sync and async) - Add unit tests (21 tests) and live test infrastructure - Add GSI pipeline configuration (pytest mark, matrix entry, env var mapping) - Update CHANGELOG.md and api.md Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 15d9064 commit 564afb1

12 files changed

Lines changed: 646 additions & 3 deletions

sdk/cosmos/azure-cosmos/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
### 4.16.2 (Unreleased)
44

55
#### Features Added
6+
* Added `GlobalSecondaryIndexDefinition` class and `global_secondary_index_definition` keyword to `create_container`, `create_container_if_not_exists`, and `replace_container` methods for creating Global Secondary Index (GSI) containers. See [PR 47468](https://github.com/Azure/azure-sdk-for-python/pull/47468).
67

78
#### Breaking Changes
89

sdk/cosmos/azure-cosmos/api.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,7 @@ namespace azure.cosmos
646646
change_feed_policy: Optional[dict[str, Any]] = ...,
647647
computed_properties: Optional[list[dict[str, str]]] = ...,
648648
full_text_policy: Optional[dict[str, Any]] = ...,
649+
global_secondary_index_definition: Optional[Union[GlobalSecondaryIndexDefinition, dict[str, Any]]] = ...,
649650
initial_headers: Optional[dict[str, str]] = ...,
650651
return_properties: Literal[False] = False,
651652
vector_embedding_policy: Optional[dict[str, Any]] = ...,
@@ -668,6 +669,7 @@ namespace azure.cosmos
668669
change_feed_policy: Optional[dict[str, Any]] = ...,
669670
computed_properties: Optional[list[dict[str, str]]] = ...,
670671
full_text_policy: Optional[dict[str, Any]] = ...,
672+
global_secondary_index_definition: Optional[Union[GlobalSecondaryIndexDefinition, dict[str, Any]]] = ...,
671673
initial_headers: Optional[dict[str, str]] = ...,
672674
return_properties: Literal[True],
673675
vector_embedding_policy: Optional[dict[str, Any]] = ...,
@@ -690,6 +692,7 @@ namespace azure.cosmos
690692
change_feed_policy: Optional[dict[str, Any]] = ...,
691693
computed_properties: Optional[list[dict[str, str]]] = ...,
692694
full_text_policy: Optional[dict[str, Any]] = ...,
695+
global_secondary_index_definition: Optional[Union[GlobalSecondaryIndexDefinition, dict[str, Any]]] = ...,
693696
initial_headers: Optional[dict[str, str]] = ...,
694697
return_properties: Literal[False] = False,
695698
vector_embedding_policy: Optional[dict[str, Any]] = ...,
@@ -712,6 +715,7 @@ namespace azure.cosmos
712715
change_feed_policy: Optional[dict[str, Any]] = ...,
713716
computed_properties: Optional[list[dict[str, str]]] = ...,
714717
full_text_policy: Optional[dict[str, Any]] = ...,
718+
global_secondary_index_definition: Optional[Union[GlobalSecondaryIndexDefinition, dict[str, Any]]] = ...,
715719
initial_headers: Optional[dict[str, str]] = ...,
716720
return_properties: Literal[True],
717721
vector_embedding_policy: Optional[dict[str, Any]] = ...,
@@ -838,6 +842,7 @@ namespace azure.cosmos
838842
analytical_storage_ttl: Optional[int] = ...,
839843
computed_properties: Optional[list[dict[str, str]]] = ...,
840844
full_text_policy: Optional[dict[str, Any]] = ...,
845+
global_secondary_index_definition: Optional[Union[GlobalSecondaryIndexDefinition, dict[str, Any]]] = ...,
841846
initial_headers: Optional[dict[str, str]] = ...,
842847
return_properties: Literal[False] = False,
843848
vector_embedding_policy: Optional[dict[str, Any]] = ...,
@@ -857,6 +862,7 @@ namespace azure.cosmos
857862
analytical_storage_ttl: Optional[int] = ...,
858863
computed_properties: Optional[list[dict[str, str]]] = ...,
859864
full_text_policy: Optional[dict[str, Any]] = ...,
865+
global_secondary_index_definition: Optional[Union[GlobalSecondaryIndexDefinition, dict[str, Any]]] = ...,
860866
initial_headers: Optional[dict[str, str]] = ...,
861867
return_properties: Literal[True],
862868
vector_embedding_policy: Optional[dict[str, Any]] = ...,
@@ -892,6 +898,19 @@ namespace azure.cosmos
892898
) -> UserProxy: ...
893899

894900

901+
class azure.cosmos.GlobalSecondaryIndexDefinition:
902+
property definition: str # Read-only
903+
property source_container_id: str # Read-only
904+
property source_container_rid: Optional[str] # Read-only
905+
property status: Optional[str] # Read-only
906+
907+
def __init__(
908+
self,
909+
source_container_id: str,
910+
definition: str
911+
): ...
912+
913+
895914
class azure.cosmos.IndexKind:
896915
Hash: Literal["Hash"] = Hash
897916
MultiHash: Literal["MultiHash"] = MultiHash
@@ -1830,6 +1849,7 @@ namespace azure.cosmos.aio
18301849
conflict_resolution_policy: Optional[dict[str, str]] = ...,
18311850
default_ttl: Optional[int] = ...,
18321851
full_text_policy: Optional[dict[str, Any]] = ...,
1852+
global_secondary_index_definition: Optional[Union[GlobalSecondaryIndexDefinition, dict[str, Any]]] = ...,
18331853
indexing_policy: Optional[dict[str, str]] = ...,
18341854
initial_headers: Optional[dict[str, str]] = ...,
18351855
offer_throughput: Optional[Union[int, ThroughputProperties]] = ...,
@@ -1851,6 +1871,7 @@ namespace azure.cosmos.aio
18511871
conflict_resolution_policy: Optional[dict[str, str]] = ...,
18521872
default_ttl: Optional[int] = ...,
18531873
full_text_policy: Optional[dict[str, Any]] = ...,
1874+
global_secondary_index_definition: Optional[Union[GlobalSecondaryIndexDefinition, dict[str, Any]]] = ...,
18541875
indexing_policy: Optional[dict[str, str]] = ...,
18551876
initial_headers: Optional[dict[str, str]] = ...,
18561877
offer_throughput: Optional[Union[int, ThroughputProperties]] = ...,
@@ -1872,6 +1893,7 @@ namespace azure.cosmos.aio
18721893
conflict_resolution_policy: Optional[dict[str, str]] = ...,
18731894
default_ttl: Optional[int] = ...,
18741895
full_text_policy: Optional[dict[str, Any]] = ...,
1896+
global_secondary_index_definition: Optional[Union[GlobalSecondaryIndexDefinition, dict[str, Any]]] = ...,
18751897
indexing_policy: Optional[dict[str, str]] = ...,
18761898
initial_headers: Optional[dict[str, str]] = ...,
18771899
offer_throughput: Optional[Union[int, ThroughputProperties]] = ...,
@@ -1893,6 +1915,7 @@ namespace azure.cosmos.aio
18931915
conflict_resolution_policy: Optional[dict[str, str]] = ...,
18941916
default_ttl: Optional[int] = ...,
18951917
full_text_policy: Optional[dict[str, Any]] = ...,
1918+
global_secondary_index_definition: Optional[Union[GlobalSecondaryIndexDefinition, dict[str, Any]]] = ...,
18961919
indexing_policy: Optional[dict[str, str]] = ...,
18971920
initial_headers: Optional[dict[str, str]] = ...,
18981921
offer_throughput: Optional[Union[int, ThroughputProperties]] = ...,
@@ -2004,6 +2027,7 @@ namespace azure.cosmos.aio
20042027
conflict_resolution_policy: Optional[dict[str, str]] = ...,
20052028
default_ttl: Optional[int] = ...,
20062029
full_text_policy: Optional[dict[str, Any]] = ...,
2030+
global_secondary_index_definition: Optional[Union[GlobalSecondaryIndexDefinition, dict[str, Any]]] = ...,
20072031
indexing_policy: Optional[dict[str, str]] = ...,
20082032
initial_headers: Optional[dict[str, str]] = ...,
20092033
return_properties: Literal[False] = False,
@@ -2022,6 +2046,7 @@ namespace azure.cosmos.aio
20222046
conflict_resolution_policy: Optional[dict[str, str]] = ...,
20232047
default_ttl: Optional[int] = ...,
20242048
full_text_policy: Optional[dict[str, Any]] = ...,
2049+
global_secondary_index_definition: Optional[Union[GlobalSecondaryIndexDefinition, dict[str, Any]]] = ...,
20252050
indexing_policy: Optional[dict[str, str]] = ...,
20262051
initial_headers: Optional[dict[str, str]] = ...,
20272052
return_properties: Literal[True],
@@ -2909,6 +2934,7 @@ namespace azure.cosmos.database
29092934
change_feed_policy: Optional[dict[str, Any]] = ...,
29102935
computed_properties: Optional[list[dict[str, str]]] = ...,
29112936
full_text_policy: Optional[dict[str, Any]] = ...,
2937+
global_secondary_index_definition: Optional[Union[GlobalSecondaryIndexDefinition, dict[str, Any]]] = ...,
29122938
initial_headers: Optional[dict[str, str]] = ...,
29132939
return_properties: Literal[False] = False,
29142940
vector_embedding_policy: Optional[dict[str, Any]] = ...,
@@ -2931,6 +2957,7 @@ namespace azure.cosmos.database
29312957
change_feed_policy: Optional[dict[str, Any]] = ...,
29322958
computed_properties: Optional[list[dict[str, str]]] = ...,
29332959
full_text_policy: Optional[dict[str, Any]] = ...,
2960+
global_secondary_index_definition: Optional[Union[GlobalSecondaryIndexDefinition, dict[str, Any]]] = ...,
29342961
initial_headers: Optional[dict[str, str]] = ...,
29352962
return_properties: Literal[True],
29362963
vector_embedding_policy: Optional[dict[str, Any]] = ...,
@@ -2953,6 +2980,7 @@ namespace azure.cosmos.database
29532980
change_feed_policy: Optional[dict[str, Any]] = ...,
29542981
computed_properties: Optional[list[dict[str, str]]] = ...,
29552982
full_text_policy: Optional[dict[str, Any]] = ...,
2983+
global_secondary_index_definition: Optional[Union[GlobalSecondaryIndexDefinition, dict[str, Any]]] = ...,
29562984
initial_headers: Optional[dict[str, str]] = ...,
29572985
return_properties: Literal[False] = False,
29582986
vector_embedding_policy: Optional[dict[str, Any]] = ...,
@@ -2975,6 +3003,7 @@ namespace azure.cosmos.database
29753003
change_feed_policy: Optional[dict[str, Any]] = ...,
29763004
computed_properties: Optional[list[dict[str, str]]] = ...,
29773005
full_text_policy: Optional[dict[str, Any]] = ...,
3006+
global_secondary_index_definition: Optional[Union[GlobalSecondaryIndexDefinition, dict[str, Any]]] = ...,
29783007
initial_headers: Optional[dict[str, str]] = ...,
29793008
return_properties: Literal[True],
29803009
vector_embedding_policy: Optional[dict[str, Any]] = ...,
@@ -3101,6 +3130,7 @@ namespace azure.cosmos.database
31013130
analytical_storage_ttl: Optional[int] = ...,
31023131
computed_properties: Optional[list[dict[str, str]]] = ...,
31033132
full_text_policy: Optional[dict[str, Any]] = ...,
3133+
global_secondary_index_definition: Optional[Union[GlobalSecondaryIndexDefinition, dict[str, Any]]] = ...,
31043134
initial_headers: Optional[dict[str, str]] = ...,
31053135
return_properties: Literal[False] = False,
31063136
vector_embedding_policy: Optional[dict[str, Any]] = ...,
@@ -3120,6 +3150,7 @@ namespace azure.cosmos.database
31203150
analytical_storage_ttl: Optional[int] = ...,
31213151
computed_properties: Optional[list[dict[str, str]]] = ...,
31223152
full_text_policy: Optional[dict[str, Any]] = ...,
3153+
global_secondary_index_definition: Optional[Union[GlobalSecondaryIndexDefinition, dict[str, Any]]] = ...,
31233154
initial_headers: Optional[dict[str, str]] = ...,
31243155
return_properties: Literal[True],
31253156
vector_embedding_policy: Optional[dict[str, Any]] = ...,
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
apiMdSha256: de3b8c0f2a0405fac7152d562c982bf98bf7bb546c46f05e39164574e47f7f7a
1+
apiMdSha256: 2e3b8901f784591bea2745cad02d1dd4c524db0702d0f357774f896de263fdf0
22
parserVersion: 0.3.28
3-
pythonVersion: 3.13.14
3+
pythonVersion: 3.10.20

sdk/cosmos/azure-cosmos/azure/cosmos/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
)
4444
from .partition_key import PartitionKey
4545
from .permission import Permission
46+
from ._global_secondary_index import GlobalSecondaryIndexDefinition
4647

4748
__all__ = (
4849
"CosmosClient",
@@ -66,6 +67,7 @@
6667
"ConnectionRetryPolicy",
6768
"ThroughputProperties",
6869
"CosmosDict",
69-
"CosmosList"
70+
"CosmosList",
71+
"GlobalSecondaryIndexDefinition"
7072
)
7173
__version__ = VERSION
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# The MIT License (MIT)
2+
# Copyright (c) 2014 Microsoft Corporation
3+
4+
# Permission is hereby granted, free of charge, to any person obtaining a copy
5+
# of this software and associated documentation files (the "Software"), to deal
6+
# in the Software without restriction, including without limitation the rights
7+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
# copies of the Software, and to permit persons to whom the Software is
9+
# furnished to do so, subject to the following conditions:
10+
11+
# The above copyright notice and this permission notice shall be included in all
12+
# copies or substantial portions of the Software.
13+
14+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
# SOFTWARE.
21+
22+
"""Global Secondary Index (GSI) container definition."""
23+
24+
from typing import Optional
25+
26+
27+
class GlobalSecondaryIndexDefinition:
28+
"""**provisional** Definition for a Global Secondary Index (GSI) container.
29+
30+
A GSI container is a derived container built from a source container
31+
using a SQL-like projection query. The GSI definition is immutable after creation.
32+
33+
.. note::
34+
A maximum of 5 GSI containers can be created per source container.
35+
All GSI containers must be deleted before deleting the source container.
36+
37+
:param str source_container_id: The ID of the source container the GSI is derived from. Required.
38+
:param str definition: The SQL-like projection query that defines the GSI. Required.
39+
"""
40+
41+
def __init__(self, source_container_id: str, definition: str):
42+
if not source_container_id or not source_container_id.strip():
43+
raise ValueError("source_container_id cannot be None or empty.")
44+
if not definition or not definition.strip():
45+
raise ValueError("definition cannot be None or empty.")
46+
self._source_container_id = source_container_id
47+
self._definition = definition
48+
self._source_container_rid: Optional[str] = None
49+
self._status: Optional[str] = None
50+
51+
@property
52+
def source_container_id(self) -> str:
53+
"""The ID of the source container.
54+
55+
:returns: The source container ID.
56+
:rtype: str
57+
"""
58+
return self._source_container_id
59+
60+
@property
61+
def definition(self) -> str:
62+
"""The SQL-like projection query that defines the GSI.
63+
64+
:returns: The projection query.
65+
:rtype: str
66+
"""
67+
return self._definition
68+
69+
@property
70+
def source_container_rid(self) -> Optional[str]:
71+
"""The server-populated resource ID (_rid) of the source container. Read-only.
72+
73+
:returns: The source container resource ID, or None if not yet populated.
74+
:rtype: str or None
75+
"""
76+
return self._source_container_rid
77+
78+
@property
79+
def status(self) -> Optional[str]:
80+
"""The GSI build status. Read-only, server-populated.
81+
82+
Possible values: "Initializing", "InitialBuildAfterCreate",
83+
"InitialBuildAfterRestore", "Active", "DeleteInProgress"
84+
85+
:returns: The GSI status, or None if not yet populated.
86+
:rtype: str or None
87+
"""
88+
return self._status
89+
90+
def _to_dict(self) -> dict:
91+
"""Serialize to wire format dict.
92+
93+
:returns: A dictionary representation of the GSI definition.
94+
:rtype: dict
95+
"""
96+
result: dict = {
97+
"sourceCollectionId": self._source_container_id,
98+
"definition": self._definition,
99+
}
100+
if self._source_container_rid is not None:
101+
result["sourceCollectionRid"] = self._source_container_rid
102+
if self._status is not None:
103+
result["status"] = self._status
104+
return result
105+
106+
@classmethod
107+
def _from_dict(cls, data: Optional[dict]) -> Optional["GlobalSecondaryIndexDefinition"]:
108+
"""Deserialize from wire format dict.
109+
110+
:param dict data: The wire format dictionary.
111+
:returns: A GlobalSecondaryIndexDefinition instance, or None if data is None or invalid.
112+
:rtype: ~azure.cosmos.GlobalSecondaryIndexDefinition or None
113+
"""
114+
if data is None:
115+
return None
116+
source_container_id = data.get("sourceCollectionId")
117+
definition_query = data.get("definition")
118+
if not source_container_id or not definition_query:
119+
return None
120+
instance = cls(source_container_id, definition_query)
121+
instance._source_container_rid = data.get("sourceCollectionRid") # pylint: disable=protected-access
122+
instance._status = data.get("status") # pylint: disable=protected-access
123+
return instance

0 commit comments

Comments
 (0)