Skip to content

Commit 2c1f7a8

Browse files
committed
PYTHON-2557 Timeseries collection support
This change also resolves PYTHON-2604.
1 parent 62be0fb commit 2c1f7a8

File tree

5 files changed

+318
-4
lines changed

5 files changed

+318
-4
lines changed

pymongo/database.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,10 @@ def create_collection(self, name, codec_options=None,
364364
size of the collection.
365365
- "capped": if True, this is a capped collection
366366
- "max": maximum number of objects if capped (optional)
367+
- `timeseries`: a document specifying configuration options for
368+
timeseries collections
369+
- `expireAfterSeconds`: the number of seconds after which a
370+
document in a timeseries collection expires
367371
368372
See the MongoDB documentation for a full list of supported options by
369373
server version.
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
{
2+
"description": "timeseries-collection",
3+
"schemaVersion": "1.0",
4+
"runOnRequirements": [
5+
{
6+
"minServerVersion": "5.0"
7+
}
8+
],
9+
"createEntities": [
10+
{
11+
"client": {
12+
"id": "client0",
13+
"observeEvents": [
14+
"commandStartedEvent"
15+
]
16+
}
17+
},
18+
{
19+
"database": {
20+
"id": "database0",
21+
"client": "client0",
22+
"databaseName": "ts-tests"
23+
}
24+
},
25+
{
26+
"collection": {
27+
"id": "collection0",
28+
"database": "database0",
29+
"collectionName": "test"
30+
}
31+
}
32+
],
33+
"initialData": [
34+
{
35+
"collectionName": "test",
36+
"databaseName": "ts-tests",
37+
"documents": []
38+
}
39+
],
40+
"tests": [
41+
{
42+
"description": "createCollection with all options",
43+
"operations": [
44+
{
45+
"name": "dropCollection",
46+
"object": "database0",
47+
"arguments": {
48+
"collection": "test"
49+
}
50+
},
51+
{
52+
"name": "createCollection",
53+
"object": "database0",
54+
"arguments": {
55+
"collection": "test",
56+
"expireAfterSeconds": 604800,
57+
"timeseries": {
58+
"timeField": "time",
59+
"metaField": "meta",
60+
"granularity": "minutes"
61+
}
62+
}
63+
},
64+
{
65+
"name": "assertCollectionExists",
66+
"object": "testRunner",
67+
"arguments": {
68+
"databaseName": "ts-tests",
69+
"collectionName": "test"
70+
}
71+
}
72+
],
73+
"expectEvents": [
74+
{
75+
"client": "client0",
76+
"events": [
77+
{
78+
"commandStartedEvent": {
79+
"command": {
80+
"drop": "test"
81+
},
82+
"databaseName": "ts-tests"
83+
}
84+
},
85+
{
86+
"commandStartedEvent": {
87+
"command": {
88+
"listCollections": 1
89+
},
90+
"databaseName": "ts-tests"
91+
}
92+
},
93+
{
94+
"commandStartedEvent": {
95+
"command": {
96+
"create": "test",
97+
"expireAfterSeconds": 604800,
98+
"timeseries": {
99+
"timeField": "time",
100+
"metaField": "meta",
101+
"granularity": "minutes"
102+
}
103+
},
104+
"databaseName": "ts-tests"
105+
}
106+
}
107+
]
108+
}
109+
]
110+
},
111+
{
112+
"description": "insertMany with duplicate ids",
113+
"operations": [
114+
{
115+
"name": "dropCollection",
116+
"object": "database0",
117+
"arguments": {
118+
"collection": "test"
119+
}
120+
},
121+
{
122+
"name": "createCollection",
123+
"object": "database0",
124+
"arguments": {
125+
"collection": "test",
126+
"expireAfterSeconds": 604800,
127+
"timeseries": {
128+
"timeField": "time",
129+
"metaField": "meta",
130+
"granularity": "minutes"
131+
}
132+
}
133+
},
134+
{
135+
"name": "assertCollectionExists",
136+
"object": "testRunner",
137+
"arguments": {
138+
"databaseName": "ts-tests",
139+
"collectionName": "test"
140+
}
141+
},
142+
{
143+
"name": "insertMany",
144+
"object": "collection0",
145+
"arguments": {
146+
"documents": [
147+
{
148+
"_id": 1,
149+
"time": {
150+
"$date": {
151+
"$numberLong": "1552949630482"
152+
}
153+
}
154+
},
155+
{
156+
"_id": 1,
157+
"time": {
158+
"$date": {
159+
"$numberLong": "1552949630483"
160+
}
161+
}
162+
}
163+
]
164+
}
165+
},
166+
{
167+
"name": "find",
168+
"object": "collection0",
169+
"arguments": {
170+
"filter": {},
171+
"sort": {
172+
"time": 1
173+
}
174+
},
175+
"expectResult": [
176+
{
177+
"_id": 1,
178+
"time": {
179+
"$date": {
180+
"$numberLong": "1552949630482"
181+
}
182+
}
183+
},
184+
{
185+
"_id": 1,
186+
"time": {
187+
"$date": {
188+
"$numberLong": "1552949630483"
189+
}
190+
}
191+
}
192+
]
193+
}
194+
],
195+
"expectEvents": [
196+
{
197+
"client": "client0",
198+
"events": [
199+
{
200+
"commandStartedEvent": {
201+
"command": {
202+
"drop": "test"
203+
},
204+
"databaseName": "ts-tests"
205+
}
206+
},
207+
{
208+
"commandStartedEvent": {
209+
"command": {
210+
"listCollections": 1
211+
},
212+
"databaseName": "ts-tests"
213+
}
214+
},
215+
{
216+
"commandStartedEvent": {
217+
"command": {
218+
"create": "test",
219+
"expireAfterSeconds": 604800,
220+
"timeseries": {
221+
"timeField": "time",
222+
"metaField": "meta",
223+
"granularity": "minutes"
224+
}
225+
},
226+
"databaseName": "ts-tests"
227+
}
228+
},
229+
{
230+
"commandStartedEvent": {
231+
"command": {
232+
"insert": "test",
233+
"documents": [
234+
{
235+
"_id": 1,
236+
"time": {
237+
"$date": {
238+
"$numberLong": "1552949630482"
239+
}
240+
}
241+
},
242+
{
243+
"_id": 1,
244+
"time": {
245+
"$date": {
246+
"$numberLong": "1552949630483"
247+
}
248+
}
249+
}
250+
]
251+
}
252+
}
253+
},
254+
{
255+
"commandStartedEvent": {
256+
"command": {
257+
"find": "test",
258+
"filter": {},
259+
"sort": {
260+
"time": 1
261+
}
262+
},
263+
"databaseName": "ts-tests"
264+
}
265+
}
266+
]
267+
}
268+
]
269+
}
270+
]
271+
}

test/test_collection_management.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Copyright 2021-present MongoDB, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Test the collection management unified spec tests."""
16+
17+
import os
18+
import sys
19+
20+
sys.path[0:0] = [""]
21+
22+
from test import unittest
23+
24+
from test.unified_format import generate_test_classes
25+
26+
# Location of JSON test specifications.
27+
TEST_PATH = os.path.join(
28+
os.path.dirname(os.path.realpath(__file__)), 'collection_management')
29+
30+
# Generate unified tests.
31+
globals().update(generate_test_classes(TEST_PATH, module=__name__))
32+
33+
if __name__ == "__main__":
34+
unittest.main()

test/unified_format.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,17 +102,19 @@ def is_run_on_requirement_satisfied(requirement):
102102
topology_satisfied = client_context.is_topology_type(
103103
req_topologies)
104104

105+
server_version = Version(*client_context.version[:3])
106+
105107
min_version_satisfied = True
106108
req_min_server_version = requirement.get('minServerVersion')
107109
if req_min_server_version:
108110
min_version_satisfied = Version.from_string(
109-
req_min_server_version) <= client_context.version
111+
req_min_server_version) <= server_version
110112

111113
max_version_satisfied = True
112114
req_max_server_version = requirement.get('maxServerVersion')
113115
if req_max_server_version:
114116
max_version_satisfied = Version.from_string(
115-
req_max_server_version) >= client_context.version
117+
req_max_server_version) >= server_version
116118

117119
params_satisfied = True
118120
params = requirement.get('serverParameters')

test/utils.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,8 +1102,11 @@ def prepare_spec_arguments(spec, arguments, opname, entity_map,
11021102
copy.deepcopy(callback_ops))
11031103
elif opname == 'drop_collection' and arg_name == 'collection':
11041104
arguments['name_or_collection'] = arguments.pop(arg_name)
1105-
elif opname == 'create_collection' and arg_name == 'collection':
1106-
arguments['name'] = arguments.pop(arg_name)
1105+
elif opname == 'create_collection':
1106+
if arg_name == 'collection':
1107+
arguments['name'] = arguments.pop(arg_name)
1108+
# Any other arguments to create_collection are passed through
1109+
# **kwargs.
11071110
elif opname == 'create_index' and arg_name == 'keys':
11081111
arguments['keys'] = list(arguments.pop(arg_name).items())
11091112
elif opname == 'drop_index' and arg_name == 'name':

0 commit comments

Comments
 (0)