Skip to content

Commit 6041e81

Browse files
committed
RDBC-766 RDBC_501Test::shouldProperlyMapTypedEntries
1 parent 49dd546 commit 6041e81

File tree

3 files changed

+241
-4
lines changed

3 files changed

+241
-4
lines changed

ravendb/documents/queries/time_series.py

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
from __future__ import annotations
2+
3+
import datetime
24
from typing import Optional, Dict, Any, List, Type, TypeVar, Generic
35

4-
from ravendb.documents.session.time_series import TimeSeriesEntry, TypedTimeSeriesEntry
6+
from ravendb.documents.session.time_series import TimeSeriesEntry, TypedTimeSeriesEntry, TimeSeriesValuesHelper
7+
from ravendb.tools.utils import Utils
58

69
_T_TS_Bindable = TypeVar("_T_TS_Bindable")
710

@@ -47,3 +50,139 @@ def from_json(cls, json_dict: Dict[str, Any]) -> TimeSeriesQueryResult:
4750
json_dict["Count"],
4851
[TypedTimeSeriesEntry.from_json(typed_ts_entry_json) for typed_ts_entry_json in json_dict["Results"]],
4952
)
53+
54+
55+
class TimeSeriesQueryBuilder:
56+
def __init__(self, query: str = None):
57+
self._query = query
58+
59+
@property
60+
def query_text(self):
61+
return self._query
62+
63+
def raw(self, query_text: str) -> TimeSeriesQueryResult:
64+
self._query = query_text
65+
return None
66+
67+
68+
class TimeSeriesRangeAggregation:
69+
def __init__(
70+
self,
71+
count: List[int] = None,
72+
max: List[float] = None,
73+
min: List[float] = None,
74+
last: List[float] = None,
75+
first: List[float] = None,
76+
average: List[float] = None,
77+
sum: List[float] = None,
78+
to_date: datetime.datetime = None,
79+
from_date: datetime.datetime = None,
80+
):
81+
self.count = count
82+
self.max = max
83+
self.min = min
84+
self.last = last
85+
self.first = first
86+
self.average = average
87+
self.sum = sum
88+
self.to_date = to_date
89+
self.from_date = from_date
90+
91+
@classmethod
92+
def from_json(cls, json_dict: Dict[str, Any]) -> TimeSeriesRangeAggregation:
93+
return cls(
94+
json_dict["Count"] if "Count" in json_dict else None,
95+
json_dict["Max"] if "Max" in json_dict else None,
96+
json_dict["Min"] if "Min" in json_dict else None,
97+
json_dict["Last"] if "Last" in json_dict else None,
98+
json_dict["First"] if "First" in json_dict else None,
99+
json_dict["Average"] if "Average" in json_dict else None,
100+
json_dict["Sum"] if "Sum" in json_dict else None,
101+
Utils.string_to_datetime(json_dict["To"]),
102+
Utils.string_to_datetime(json_dict["From"]),
103+
)
104+
105+
def as_typed_entry(
106+
self, ts_bindable_object_type: Type[_T_TS_Bindable]
107+
) -> TypedTimeSeriesRangeAggregation[_T_TS_Bindable]:
108+
typed_entry = TypedTimeSeriesRangeAggregation()
109+
110+
typed_entry.from_date = self.from_date
111+
typed_entry.to_date = self.to_date
112+
typed_entry.min = (
113+
TimeSeriesValuesHelper.set_fields(ts_bindable_object_type, self.min, False) if self.min else None
114+
)
115+
typed_entry.max = (
116+
TimeSeriesValuesHelper.set_fields(ts_bindable_object_type, self.max, False) if self.max else None
117+
)
118+
typed_entry.first = (
119+
TimeSeriesValuesHelper.set_fields(ts_bindable_object_type, self.first, False) if self.first else None
120+
)
121+
typed_entry.last = (
122+
TimeSeriesValuesHelper.set_fields(ts_bindable_object_type, self.last, False) if self.last else None
123+
)
124+
typed_entry.sum = (
125+
TimeSeriesValuesHelper.set_fields(ts_bindable_object_type, self.sum, False) if self.sum else None
126+
)
127+
counts = [float(count) for count in self.count]
128+
typed_entry.count = (
129+
TimeSeriesValuesHelper.set_fields(ts_bindable_object_type, counts, False) if self.count else None
130+
)
131+
typed_entry.average = (
132+
TimeSeriesValuesHelper.set_fields(ts_bindable_object_type, self.average, False) if self.average else None
133+
)
134+
135+
return typed_entry
136+
137+
138+
class TypedTimeSeriesRangeAggregation(Generic[_T_TS_Bindable]):
139+
def __init__(
140+
self,
141+
count: _T_TS_Bindable = None,
142+
max: _T_TS_Bindable = None,
143+
min: _T_TS_Bindable = None,
144+
last: _T_TS_Bindable = None,
145+
first: _T_TS_Bindable = None,
146+
average: _T_TS_Bindable = None,
147+
sum: _T_TS_Bindable = None,
148+
to_date: datetime.datetime = None,
149+
from_date: datetime.datetime = None,
150+
):
151+
self.count = count
152+
self.max = max
153+
self.min = min
154+
self.last = last
155+
self.first = first
156+
self.average = average
157+
self.sum = sum
158+
self.to_date = to_date
159+
self.from_date = from_date
160+
161+
162+
class TypedTimeSeriesAggregationResult(TimeSeriesQueryResult, Generic[_T_TS_Bindable]):
163+
def __init__(
164+
self, count: Optional[int] = None, results: List[TypedTimeSeriesRangeAggregation[_T_TS_Bindable]] = None
165+
):
166+
super(TypedTimeSeriesAggregationResult, self).__init__(count)
167+
self.results = results
168+
169+
170+
class TimeSeriesAggregationResult(TimeSeriesQueryResult):
171+
def __init__(self, count: Optional[int] = None, results: Optional[List[TimeSeriesRangeAggregation]] = None):
172+
super().__init__(count)
173+
self.results = results
174+
175+
def as_typed_result(
176+
self, ts_bindable_object_type: Type[_T_TS_Bindable]
177+
) -> TypedTimeSeriesAggregationResult[_T_TS_Bindable]:
178+
result = TypedTimeSeriesAggregationResult()
179+
result.count = self.count
180+
result.results = [x.as_typed_entry(ts_bindable_object_type) for x in self.results]
181+
return result
182+
183+
@classmethod
184+
def from_json(cls, json_dict: Dict[str, Any]) -> TimeSeriesQueryResult:
185+
json_dict = json_dict["__timeSeriesQueryFunction"]
186+
return cls(
187+
json_dict["Count"], [TimeSeriesRangeAggregation.from_json(result) for result in json_dict["Results"]]
188+
)

ravendb/documents/session/query.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
TYPE_CHECKING,
2020
)
2121

22+
from ravendb.documents.queries.time_series import TimeSeriesQueryBuilder
2223
from ravendb.documents.session.time_series import TimeSeriesRange
2324
from ravendb.primitives import constants
2425
from ravendb.documents.conventions import DocumentConventions
@@ -93,6 +94,7 @@
9394
_T = TypeVar("_T")
9495
_TResult = TypeVar("_TResult")
9596
_TProjection = TypeVar("_TProjection")
97+
_T_TS_Bindable = TypeVar("_T_TS_Bindable")
9698
if TYPE_CHECKING:
9799
from ravendb.documents.store.definition import Lazy
98100
from ravendb.documents.session.document_session import DocumentSession
@@ -1298,8 +1300,13 @@ def _get_source_alias_if_exists(object_type: type, query_data: QueryData, fields
12981300

12991301
return possible_alias
13001302

1301-
# todo: def _create_time_series_query_data(time_series_query: Callable[[TimeSeriesQueryBuilder], None])
1302-
# -> QueryData:
1303+
def _create_time_series_query_data(self, time_series_query: Callable[[TimeSeriesQueryBuilder], None]) -> QueryData:
1304+
builder = TimeSeriesQueryBuilder()
1305+
time_series_query(builder)
1306+
1307+
fields = [constants.TimeSeries.SELECT_FIELD_NAME + "(" + builder.query_text + ")"]
1308+
projections = [constants.TimeSeries.QUERY_FUNCTION]
1309+
return QueryData(fields, projections)
13031310

13041311
def _add_before_query_executed_listener(self, action: Callable[[IndexQuery], None]) -> None:
13051312
self._before_query_executed_callback.append(action)
@@ -1741,7 +1748,11 @@ def __init__(
17411748
is_project_into,
17421749
)
17431750

1744-
# todo: selectTimeSeries
1751+
def select_time_series(
1752+
self, ts_bindable_object_type: Type[_T_TS_Bindable], time_series_query: Callable[[TimeSeriesQueryBuilder], None]
1753+
) -> DocumentQuery[_T_TS_Bindable]:
1754+
query_data = self._create_time_series_query_data(time_series_query)
1755+
return self.select_fields_query_data(ts_bindable_object_type, query_data)
17451756

17461757
def distinct(self) -> DocumentQuery[_T]:
17471758
self._distinct()
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from datetime import timedelta
2+
from typing import Dict, Tuple, Optional
3+
4+
from ravendb.documents.queries.time_series import TimeSeriesAggregationResult
5+
from ravendb.documents.session.time_series import ITimeSeriesValuesBindable
6+
from ravendb.tests.test_base import TestBase
7+
from ravendb.tools.raven_test_helper import RavenTestHelper
8+
9+
10+
class MarkerSymbol:
11+
def __init__(self, Id: str = None):
12+
self.Id = Id
13+
14+
15+
class SymbolPrice(ITimeSeriesValuesBindable):
16+
def __init__(self, open: float = None, close: float = None, high: float = None, low: float = None):
17+
self.open = open
18+
self.close = close
19+
self.high = high
20+
self.low = low
21+
22+
def get_time_series_mapping(self) -> Dict[int, Tuple[str, Optional[str]]]:
23+
return {
24+
0: ("open", None),
25+
1: ("close", None),
26+
2: ("high", None),
27+
3: ("low", None),
28+
}
29+
30+
31+
class TestRDBC501(TestBase):
32+
def setUp(self):
33+
super(TestRDBC501, self).setUp()
34+
35+
def test_should_properly_map_typed_entries(self):
36+
base_line = RavenTestHelper.utc_today()
37+
with self.store.open_session() as session:
38+
symbol = MarkerSymbol()
39+
session.store(symbol, "markerSymbols/1-A")
40+
41+
price1 = SymbolPrice(4, 7, 10, 1)
42+
price2 = SymbolPrice(24, 27, 210, 21)
43+
price3 = SymbolPrice(34, 37, 310, 321)
44+
45+
tsf = session.typed_time_series_for_entity(SymbolPrice, symbol, "history")
46+
tsf.append(base_line + timedelta(hours=1), price1)
47+
tsf.append(base_line + timedelta(hours=2), price2)
48+
tsf.append(base_line + timedelta(days=2), price3)
49+
50+
session.save_changes()
51+
52+
with self.store.open_session() as session:
53+
aggregated_history_query_result = (
54+
session.query(object_type=MarkerSymbol)
55+
.select_time_series(
56+
TimeSeriesAggregationResult,
57+
lambda b: b.raw(
58+
"from history\n"
59+
+ " group by '1 days'\n"
60+
+ " select first(), last(), min(), max()"
61+
),
62+
)
63+
.first()
64+
)
65+
66+
self.assertEqual(2, len(aggregated_history_query_result.results))
67+
68+
typed = aggregated_history_query_result.as_typed_result(SymbolPrice)
69+
70+
self.assertEqual(2, len(typed.results))
71+
72+
first_result = typed.results[0]
73+
self.assertEqual(4, first_result.min.open)
74+
self.assertEqual(7, first_result.min.close)
75+
self.assertEqual(1, first_result.min.low)
76+
self.assertEqual(10, first_result.min.high)
77+
78+
self.assertEqual(4, first_result.first.open)
79+
self.assertEqual(7, first_result.first.close)
80+
self.assertEqual(1, first_result.first.low)
81+
self.assertEqual(10, first_result.first.high)
82+
83+
second_result = typed.results[1]
84+
self.assertEqual(34, second_result.min.open)
85+
self.assertEqual(37, second_result.min.close)
86+
self.assertEqual(321, second_result.min.low)
87+
self.assertEqual(310, second_result.min.high)

0 commit comments

Comments
 (0)