Skip to content

Commit 985315e

Browse files
authored
Merge pull request #179 from poissoncorp/RDBC-692
RDBC-692 Counters - #3
2 parents 52c1feb + e5e2574 commit 985315e

File tree

11 files changed

+728
-70
lines changed

11 files changed

+728
-70
lines changed

ravendb/documents/indexes/counters.py

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
from typing import Dict, Set, Optional, Callable, Union
2+
3+
from ravendb import constants
4+
from ravendb.documents.conventions import DocumentConventions
5+
from ravendb.documents.indexes.definitions import (
6+
FieldStorage,
7+
FieldIndexing,
8+
FieldTermVector,
9+
IndexDefinition,
10+
IndexSourceType,
11+
IndexFieldOptions,
12+
IndexType,
13+
)
14+
from ravendb.documents.indexes.index_creation import AbstractIndexCreationTaskBase, AbstractIndexDefinitionBuilder
15+
from ravendb.documents.indexes.spatial.configuration import SpatialOptions, SpatialOptionsFactory
16+
17+
18+
class CountersIndexDefinition(IndexDefinition):
19+
@property
20+
def source_type(self) -> IndexSourceType:
21+
return IndexSourceType.COUNTERS
22+
23+
24+
class AbstractGenericCountersIndexCreationTask(AbstractIndexCreationTaskBase[CountersIndexDefinition]):
25+
def __init__(self):
26+
super(AbstractGenericCountersIndexCreationTask, self).__init__()
27+
self._reduce: Optional[str] = None
28+
29+
self._stores_strings: Dict[str, FieldStorage] = {}
30+
self._indexes_strings: Dict[str, FieldIndexing] = {}
31+
self._analyzers_strings: Dict[str, str] = {}
32+
self._index_suggestions: Set[str] = set()
33+
self._term_vectors_strings: Dict[str, FieldTermVector] = {}
34+
self._spatial_options_strings: Dict[str, SpatialOptions] = {}
35+
36+
self._output_reduce_to_collection: Optional[str] = None
37+
self._pattern_for_output_reduce_to_collection_references: Optional[str] = None
38+
self._pattern_references_collection_name: Optional[str] = None
39+
40+
@property
41+
def is_map_reduce(self) -> bool:
42+
return self._reduce is not None
43+
44+
def _index(self, field: str, indexing: FieldIndexing) -> None:
45+
self._indexes_strings[field] = indexing
46+
47+
def _spatial(self, field: str, indexing: Callable[[SpatialOptionsFactory], SpatialOptions]) -> None:
48+
self._spatial_options_strings[field] = indexing(SpatialOptionsFactory())
49+
50+
def _store_all_fields(self, storage: FieldStorage) -> None:
51+
self._stores_strings[constants.Documents.Indexing.Fields.ALL_FIELDS] = storage
52+
53+
def _store(self, field: str, storage: FieldStorage) -> None:
54+
self._stores_strings[field] = storage
55+
56+
def _analyze(self, field: str, analyzer: str) -> None:
57+
self._analyzers_strings[field] = analyzer
58+
59+
def _term_vector(self, field: str, term_vector: FieldTermVector) -> None:
60+
self._term_vectors_strings[field] = term_vector
61+
62+
def _suggestion(self, field: str) -> None:
63+
self._index_suggestions.add(field)
64+
65+
66+
class CountersIndexDefinitionBuilder(AbstractIndexDefinitionBuilder[CountersIndexDefinition]):
67+
def __init__(self, index_name: Optional[str] = None):
68+
super(CountersIndexDefinitionBuilder, self).__init__(index_name)
69+
self.map: Optional[str] = None
70+
71+
def _new_index_definition(self) -> CountersIndexDefinition:
72+
return CountersIndexDefinition()
73+
74+
def to_index_definition(
75+
self, conventions: DocumentConventions, validate_map: bool = True
76+
) -> CountersIndexDefinition:
77+
if self.map is None and validate_map:
78+
raise RuntimeError(
79+
f"Map is required to generate an index, you "
80+
f"cannot create an index without a valid map property (in index {self._index_name})."
81+
)
82+
83+
return super(CountersIndexDefinitionBuilder, self).to_index_definition(conventions, validate_map)
84+
85+
def _to_index_definition(self, index_definition: CountersIndexDefinition, conventions: DocumentConventions) -> None:
86+
if self.map is None:
87+
return
88+
89+
index_definition.maps.add(self.map)
90+
91+
92+
class AbstractCountersIndexCreationTask(AbstractGenericCountersIndexCreationTask):
93+
def __init__(self):
94+
super(AbstractCountersIndexCreationTask, self).__init__()
95+
self.map: Optional[str] = None
96+
97+
def create_index_definition(self) -> CountersIndexDefinition:
98+
if self.conventions is None:
99+
self.conventions = DocumentConventions()
100+
101+
index_definition_builder = CountersIndexDefinitionBuilder(self.index_name)
102+
index_definition_builder.indexes_strings = self._indexes_strings
103+
index_definition_builder.analyzers_strings = self._analyzers_strings
104+
index_definition_builder.map = self.map
105+
index_definition_builder.reduce = self._reduce
106+
index_definition_builder.stores_strings = self._stores_strings
107+
index_definition_builder.suggestions_options = self._index_suggestions
108+
index_definition_builder.term_vectors_strings = self._term_vectors_strings
109+
index_definition_builder.spatial_indexes_strings = self._spatial_options_strings
110+
index_definition_builder.output_reduce_to_collection = self._output_reduce_to_collection
111+
index_definition_builder.pattern_for_output_reduce_to_collection_references = (
112+
self._pattern_for_output_reduce_to_collection_references
113+
)
114+
index_definition_builder.pattern_references_collection_name = self._pattern_references_collection_name
115+
index_definition_builder.additional_sources = self.additional_sources
116+
index_definition_builder.additional_assemblies = self.additional_assemblies
117+
index_definition_builder.configuration = self.configuration
118+
index_definition_builder.lock_mode = self.lock_mode
119+
index_definition_builder.priority = self.priority
120+
index_definition_builder.state = self.state
121+
index_definition_builder.deployment_mode = self.deployment_mode
122+
123+
return index_definition_builder.to_index_definition(self.conventions)
124+
125+
126+
class AbstractJavaScriptCountersIndexCreationTask(AbstractIndexCreationTaskBase[CountersIndexDefinition]):
127+
def __init__(self):
128+
super(AbstractJavaScriptCountersIndexCreationTask, self).__init__()
129+
self._definition = CountersIndexDefinition()
130+
131+
@property
132+
def maps(self) -> Set[str]:
133+
return self._definition.maps
134+
135+
@maps.setter
136+
def maps(self, value: Set[str]):
137+
self._definition.maps = value
138+
139+
@property
140+
def fields(self) -> Dict[str, IndexFieldOptions]:
141+
return self._definition.fields
142+
143+
@fields.setter
144+
def fields(self, value: Dict[str, IndexFieldOptions]):
145+
self._definition.fields = value
146+
147+
@property
148+
def reduce(self) -> str:
149+
return self._definition.reduce
150+
151+
@reduce.setter
152+
def reduce(self, value: Dict[str, IndexFieldOptions]):
153+
self._definition.reduce = value
154+
155+
@property
156+
def is_map_reduce(self) -> bool:
157+
return self.reduce is not None
158+
159+
@property
160+
def output_reduce_to_collection(self) -> str:
161+
return self._definition.output_reduce_to_collection
162+
163+
@output_reduce_to_collection.setter
164+
def output_reduce_to_collection(self, value: Dict[str, IndexFieldOptions]):
165+
self._definition.output_reduce_to_collection = value
166+
167+
@property
168+
def pattern_references_collection_name(self) -> str:
169+
return self._definition.pattern_references_collection_name
170+
171+
@pattern_references_collection_name.setter
172+
def pattern_references_collection_name(self, value: Dict[str, IndexFieldOptions]):
173+
self._definition.pattern_references_collection_name = value
174+
175+
@property
176+
def pattern_for_output_reduce_to_collection_references(self) -> str:
177+
return self._definition.pattern_for_output_reduce_to_collection_references
178+
179+
@pattern_for_output_reduce_to_collection_references.setter
180+
def pattern_for_output_reduce_to_collection_references(self, value: Dict[str, IndexFieldOptions]):
181+
self._definition.pattern_for_output_reduce_to_collection_references = value
182+
183+
def create_index_definition(self) -> CountersIndexDefinition:
184+
self._definition.name = self.index_name
185+
self._definition.type = IndexType.JAVA_SCRIPT_MAP_REDUCE if self.is_map_reduce else IndexType.JAVA_SCRIPT_MAP
186+
self._definition.additional_sources = self.additional_sources or {}
187+
self._definition.additional_assemblies = self.additional_assemblies or set()
188+
self._definition.configuration = self.configuration
189+
self._definition.lock_mode = self.lock_mode
190+
self._definition.priority = self.priority
191+
self._definition.state = self.state
192+
self._definition.deployment_mode = self.deployment_mode
193+
return self._definition

ravendb/documents/session/document_session.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -346,8 +346,8 @@ def __load_internal_stream(self, keys: List[str], operation: LoadOperation, stre
346346

347347
def load_starting_with(
348348
self,
349-
object_type: Type[_T],
350349
id_prefix: str,
350+
object_type: Optional[Type[_T]] = None,
351351
matches: Optional[str] = None,
352352
start: Optional[int] = None,
353353
page_size: Optional[int] = None,
@@ -727,17 +727,16 @@ def __load_starting_with_internal(
727727

728728
def load_starting_with(
729729
self,
730-
object_type: type,
731730
id_prefix: str,
732-
matches: str,
733-
start: int,
734-
page_size: int,
735-
exclude: str,
736-
start_after: str,
737-
) -> object:
738-
load_starting_with_operation = LoadStartingWithOperation(self._session)
739-
self.__load_starting_with_internal(
740-
id_prefix, load_starting_with_operation, None, matches, start, page_size, exclude, start_after
731+
object_type: Optional[Type[_T]] = None,
732+
matches: Optional[str] = None,
733+
start: Optional[int] = None,
734+
page_size: Optional[int] = None,
735+
exclude: Optional[str] = None,
736+
start_after: Optional[str] = None,
737+
) -> List[_T]:
738+
return self._session.load_starting_with(
739+
id_prefix, object_type, matches, start, page_size, exclude, start_after
741740
)
742741

743742
def load_starting_with_into_stream(

ravendb/documents/session/operations/lazy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,8 @@ def load(
210210

211211
def load_starting_with(
212212
self,
213-
object_type: Type[_T],
214213
id_prefix: str,
214+
object_type: Optional[Type[_T]],
215215
matches: str = None,
216216
start: int = 0,
217217
page_size: int = 25,

ravendb/tests/counters_tests/test_query_on_counters.py

Lines changed: 86 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,60 @@ def test_session_query_include_counter_and_document(self):
326326

327327
self.assertEqual(1, session.advanced.number_of_requests)
328328

329+
def test_session_query_include_all_counters(self):
330+
with self.store.open_session() as session:
331+
session.store(Order(company="companies/1-A"), "orders/1-A")
332+
session.store(Order(company="companies/2-A"), "orders/2-A")
333+
session.store(Order(company="companies/3-A"), "orders/3-A")
334+
335+
session.counters_for("orders/1-A").increment("Downloads", 100)
336+
session.counters_for("orders/2-A").increment("Downloads", 200)
337+
session.counters_for("orders/3-A").increment("Downloads", 300)
338+
339+
session.counters_for("orders/1-A").increment("Likes", 1000)
340+
session.counters_for("orders/2-A").increment("Likes", 2000)
341+
342+
session.counters_for("orders/1-A").increment("Votes", 10000)
343+
session.counters_for("orders/3-A").increment("Cats", 5)
344+
345+
session.save_changes()
346+
347+
with self.store.open_session() as session:
348+
query = session.query(object_type=Order).include(lambda i: i.include_all_counters())
349+
350+
self.assertEqual("from 'Orders' include counters()", query._to_string())
351+
352+
query_result = list(query)
353+
self.assertEqual(1, session.advanced.number_of_requests)
354+
355+
# included counters should be in cache
356+
order = query_result[0]
357+
self.assertEqual("orders/1-A", order.Id)
358+
359+
dic = session.counters_for_entity(order).get_all()
360+
self.assertEqual(3, len(dic))
361+
self.assertIn(("downloads", 100), dic.items())
362+
self.assertIn(("likes", 1000), dic.items())
363+
self.assertIn(("votes", 10000), dic.items())
364+
365+
order = query_result[1]
366+
self.assertEqual("orders/2-A", order.Id)
367+
368+
dic = session.counters_for_entity(order).get_all()
369+
self.assertEqual(2, len(dic))
370+
self.assertIn(("downloads", 200), dic.items())
371+
self.assertIn(("likes", 2000), dic.items())
372+
373+
order = query_result[2]
374+
self.assertEqual("orders/3-A", order.Id)
375+
376+
dic = session.counters_for_entity(order).get_all()
377+
self.assertEqual(2, len(dic))
378+
self.assertIn(("downloads", 300), dic.items())
379+
self.assertIn(("cats", 5), dic.items())
380+
381+
self.assertEqual(1, session.advanced.number_of_requests)
382+
329383
def test_session_query_include_all_counters_of_document_and_of_related_document(self):
330384
with self.store.open_session() as session:
331385
session.store(Order(employee="employees/1-A"), "orders/1-A")
@@ -413,36 +467,6 @@ def test_counters_should_be_cached_on_all_docs_collection(self):
413467
counter_value = session.counters_for("orders/1-A").get("downloads")
414468
self.assertEqual(500, counter_value)
415469

416-
def test_counters_should_be_cached_on_collection(self):
417-
with self.store.open_session() as session:
418-
session.store(Order(company="companies/1-A"), "orders/1-A")
419-
session.counters_for("orders/1-A").increment("downloads", 100)
420-
session.save_changes()
421-
422-
with self.store.open_session() as session:
423-
list(session.query(object_type=Order).include(lambda i: i.include_counters("downloads")))
424-
counter_value = session.counters_for("orders/1-A").get("downloads")
425-
self.assertEqual(100, counter_value)
426-
427-
session.counters_for("orders/1-A").increment("downloads", 200)
428-
session.save_changes()
429-
430-
list(session.query(object_type=Order).include(lambda i: i.include_counters("downloads")))
431-
432-
counter_value = session.counters_for("orders/1-A").get("downloads")
433-
self.assertEqual(300, counter_value)
434-
435-
session.counters_for("orders/1-A").increment("downloads", 200)
436-
session.save_changes()
437-
438-
list(session.query(object_type=Order).include(lambda i: i.include_counters("downloads")))
439-
440-
counter_value = session.counters_for("orders/1-A").get("downloads")
441-
self.assertEqual(500, counter_value)
442-
443-
with self.store.open_session() as session:
444-
session.load("orders/1-A", Order)
445-
446470
def test_counters_caching_should_handle_deletion__include_counters(self):
447471
self._counters_caching_should_handle_deletion(
448472
lambda session: list(
@@ -655,3 +679,35 @@ def test_raw_query_js_projection_with_counter_raw_values(self):
655679
self.assertEqual("Pigpen", query[2].name)
656680
self.assertEqual(500, query[2].likes.values().__iter__().__next__())
657681
self.assertIsNone(query[2].downloads)
682+
683+
def test_counters_should_be_cached_on_collection(self):
684+
with self.store.open_session() as session:
685+
session.store(Order(company="companies/1-A"), "orders/1-A")
686+
session.counters_for("orders/1-A").increment("downloads", 100)
687+
session.save_changes()
688+
689+
with self.store.open_session() as session:
690+
list(session.query(object_type=Order).include(lambda i: i.include_counter("downloads")))
691+
self.assertEqual(100, session.counters_for("orders/1-A").get("downloads"))
692+
session.counters_for("orders/1-A").increment("downloads", 200)
693+
session.save_changes()
694+
695+
list(session.query(object_type=Order).include(lambda i: i.include_counters("downloads")))
696+
self.assertEqual(300, session.counters_for("orders/1-A").get("downloads"))
697+
session.counters_for("orders/1-A").increment("downloads", 200)
698+
session.save_changes()
699+
700+
list(session.query(object_type=Order).include(lambda i: i.include_counters("downloads")))
701+
self.assertEqual(500, session.counters_for("orders/1-A").get("downloads"))
702+
703+
with self.store.open_session() as session:
704+
session.load("orders/1-A", Order, lambda i: i.include_counter("downloads"))
705+
counter_value = session.counters_for("orders/1-A").get("downloads")
706+
self.assertEqual(500, counter_value)
707+
708+
session.counters_for("orders/1-A").increment("downloads", 200)
709+
session.save_changes()
710+
711+
session.load("order/1-A", Order, lambda i: i.include_counter("downloads"))
712+
counter_value = session.counters_for("orders/1-A").get("downloads")
713+
self.assertEqual(700, counter_value)

ravendb/tests/jvm_migrated_tests/client_tests/indexing_tests/counters_tests/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)