diff --git a/ravendb/documents/session/document_session.py b/ravendb/documents/session/document_session.py index 8d97c0bc..cb1291b1 100644 --- a/ravendb/documents/session/document_session.py +++ b/ravendb/documents/session/document_session.py @@ -456,6 +456,8 @@ def counters_for_entity(self, entity: object) -> SessionDocumentCounters: return SessionDocumentCounters(self, entity) def time_series_for(self, document_id: str, name: str = None) -> SessionDocumentTimeSeries: + if not isinstance(document_id, str): + raise TypeError("Method time_series_for expects a string. Did you want to call time_series_for_entity?") return SessionDocumentTimeSeries(self, document_id, name) def time_series_for_entity(self, entity: object, name: str = None) -> SessionDocumentTimeSeries: diff --git a/ravendb/documents/session/loaders/include.py b/ravendb/documents/session/loaders/include.py index 70ace43b..10541ec3 100644 --- a/ravendb/documents/session/loaders/include.py +++ b/ravendb/documents/session/loaders/include.py @@ -358,23 +358,29 @@ def include_all_counters(self) -> SubscriptionIncludeBuilder: self._include_all_counters("") return self + def include_time_series_by_range_type_and_time( + self, name: str, ts_type: TimeSeriesRangeType, time: TimeValue + ) -> SubscriptionIncludeBuilder: + self._include_time_series_by_range_type_and_time("", name, ts_type, time) + return self + + def include_time_series_by_range_type_and_count( + self, name: str, ts_type: TimeSeriesRangeType, count: int + ) -> SubscriptionIncludeBuilder: + self._include_time_series_by_range_type_and_count("", name, ts_type, count) + return self + + def include_all_time_series_by_range_type_and_count( + self, ts_type: TimeSeriesRangeType, count: int + ) -> SubscriptionIncludeBuilder: + self._include_time_series_by_range_type_and_count("", constants.TimeSeries.ALL, ts_type, count) + return self -# def include_time_series( -# self, -# name:str, -# ts_type: TimeSeriesRangeType, -# time: TimeValue -# ) -> SubscriptionIncludeBuilder: -# self._include_time_series_by_range_type_and_time("", name, ts_type, time) -# return self -# -# def include_time_series_by_range_type_and_count( -# self, -# name:str, -# ts_type: TimeSeriesRangeType, -# time: TimeValue -# ) -> SubscriptionIncludeBuilder: -# self._include_time_series_by_range_type_and_count("", name, type, count) + def include_all_time_series_by_range_type_and_time( + self, ts_type: TimeSeriesRangeType, time: TimeValue + ) -> SubscriptionIncludeBuilder: + self._include_time_series_by_range_type_and_time("", constants.TimeSeries.ALL, ts_type, time) + return self class TimeSeriesIncludeBuilder(IncludeBuilderBase): diff --git a/ravendb/documents/subscriptions/document_subscriptions.py b/ravendb/documents/subscriptions/document_subscriptions.py index 80df2844..e18cdc70 100644 --- a/ravendb/documents/subscriptions/document_subscriptions.py +++ b/ravendb/documents/subscriptions/document_subscriptions.py @@ -2,6 +2,7 @@ from typing import Optional, Type, TypeVar, Dict, List, TYPE_CHECKING from ravendb.documents.operations.ongoing_tasks import ToggleOngoingTaskStateOperation, OngoingTaskType +from ravendb.documents.session.tokens.query_tokens.definitions import CounterIncludesToken, TimeSeriesIncludesToken from ravendb.documents.session.tokens.query_tokens.query_token import QueryToken from ravendb.documents.session.utils.includes_util import IncludesUtil from ravendb.documents.commands.subscriptions import ( @@ -96,11 +97,11 @@ def ensure_criteria(self, criteria: SubscriptionCreationOptions, object_type: Ty number_of_includes_added = 0 - if builder._documents_to_include is not None and not len(builder._documents_to_include) == 0: + if builder.documents_to_include is not None and not len(builder.documents_to_include) == 0: query_builder.append(os.linesep) query_builder.append("include ") - for inc in builder._documents_to_include: + for inc in builder.documents_to_include: include = "doc." + inc if number_of_includes_added > 0: query_builder.append(",") @@ -115,41 +116,41 @@ def ensure_criteria(self, criteria: SubscriptionCreationOptions, object_type: Ty query_builder.append(f"'{include}'" if QueryToken.is_keyword(include) else include) number_of_includes_added += 1 - # todo: uncomment on Counters and TimeSeries development - # if builder._is_all_counters: - # if number_of_includes_added == 0: - # query_builder.append(os.linesep) - # query_builder.append("include ") - # - # token = CountersIncludesToken.all("") - # token.write_to(query_builder) - # number_of_includes_added += 1 - # - # elif builder._counters_to_include: - # if number_of_includes_added: - # query_builder.append(os.linesep) - # query_builder.append("include ") - # - # for counter_name in builder._counters_to_include: - # if number_of_includes_added > 0: - # query_builder.append(",") - # - # token = CountersToIncludeToken.create("", counter_name) - # token.write_to(query_builder) - # - # number_of_includes_added += 1 - # - # if builder._time_series_to_include: - # for time_series_range in builder._time_series_to_include: - # if number_of_includes_added == 0: - # query_builder.append(os.linesep) - # query_builder.append("include ") - # - # if number_of_includes_added > 0: - # query_builder.append(",") - # - # token = TimeSeriesIncludeToken.create("", time_series_range) - # token.write_to(query_builder) + + if builder.is_all_counters: + if number_of_includes_added == 0: + query_builder.append(os.linesep) + query_builder.append("include ") + + token = CounterIncludesToken.all("") + token.write_to(query_builder) + number_of_includes_added += 1 + + elif builder.counters_to_include: + if number_of_includes_added: + query_builder.append(os.linesep) + query_builder.append("include ") + + for counter_name in builder.counters_to_include: + if number_of_includes_added > 0: + query_builder.append(",") + + token = CounterIncludesToken.create("", counter_name) + token.write_to(query_builder) + + number_of_includes_added += 1 + + if builder.time_series_to_include: + for time_series_range in builder.time_series_to_include: + if number_of_includes_added == 0: + query_builder.append(os.linesep) + query_builder.append("include ") + + if number_of_includes_added > 0: + query_builder.append(",") + + token = TimeSeriesIncludesToken.create("", time_series_range) + token.write_to(query_builder) criteria.query = "".join(query_builder) return criteria diff --git a/ravendb/tests/jvm_migrated_tests/client_tests/documents_tests/commands_tests/test_get_statistics_command.py b/ravendb/tests/jvm_migrated_tests/client_tests/documents_tests/commands_tests/test_get_statistics_command.py index c9b55c6b..dd3502fd 100644 --- a/ravendb/tests/jvm_migrated_tests/client_tests/documents_tests/commands_tests/test_get_statistics_command.py +++ b/ravendb/tests/jvm_migrated_tests/client_tests/documents_tests/commands_tests/test_get_statistics_command.py @@ -1,5 +1,8 @@ +from datetime import datetime, timedelta + from ravendb import GetStatisticsOperation from ravendb.documents.smuggler.common import DatabaseItemType +from ravendb.infrastructure.entities import User from ravendb.infrastructure.operations import CreateSampleDataOperation from ravendb.tests.test_base import TestBase @@ -74,3 +77,17 @@ def test_can_get_stats(self): self.assertIsNotNone(index_information.type) self.assertIsNotNone(index_information.last_indexing_time) + + def test_can_get_stats_for_counters_and_time_series(self): + with self.store.open_session() as session: + session.store(User(), "users/1") + session.counters_for("users/1").increment("c1") + session.counters_for("users/1").increment("c2") + tsf = session.time_series_for("users/1", "Heartrate") + tsf.append(datetime.now(), [70]) + tsf.append(datetime.now() + timedelta(minutes=1), [20]) + session.save_changes() + + db_statistics = self.store.maintenance.send(GetStatisticsOperation()) + self.assertEqual(1, db_statistics.count_of_counter_entries) + self.assertEqual(1, db_statistics.count_of_time_series_segments) diff --git a/ravendb/tests/jvm_migrated_tests/client_tests/subscriptions_tests/test_basic_subscription.py b/ravendb/tests/jvm_migrated_tests/client_tests/subscriptions_tests/test_basic_subscription.py index 2294dd31..70bd698d 100644 --- a/ravendb/tests/jvm_migrated_tests/client_tests/subscriptions_tests/test_basic_subscription.py +++ b/ravendb/tests/jvm_migrated_tests/client_tests/subscriptions_tests/test_basic_subscription.py @@ -7,6 +7,7 @@ from typing import Optional, List from ravendb.documents.session.event_args import BeforeRequestEventArgs +from ravendb.documents.session.time_series import TimeSeriesRangeType from ravendb.documents.subscriptions.options import ( SubscriptionCreationOptions, SubscriptionWorkerOptions, @@ -20,7 +21,9 @@ ) from ravendb.infrastructure.entities import User from ravendb.infrastructure.orders import Company +from ravendb.primitives.time_series import TimeValue from ravendb.tests.test_base import TestBase +from ravendb.tools.raven_test_helper import RavenTestHelper class TestBasicSubscription(TestBase): @@ -585,3 +588,47 @@ def __subscription_callback(x: SubscriptionBatch): subscription.run(__subscription_callback) third_user_processed.wait(timeout=5) + + def test_can_create_subscription_with_include_time_series_last_range_by_time(self): + now = RavenTestHelper.utc_today() + + subscription_creation_options = SubscriptionCreationOptions() + subscription_creation_options.includes = lambda b: b.include_time_series_by_range_type_and_time( + "stock_price", TimeSeriesRangeType.LAST, TimeValue.of_months(1) + ) + + name = self.store.subscriptions.create_for_options_autocomplete_query(Company, subscription_creation_options) + + with self.store.subscriptions.get_subscription_worker_by_name(name, Company) as worker: + event = Event() + + def __subscription_callback(batch: SubscriptionBatch[Company]): + with batch.open_session() as session: + self.assertEqual(0, session.advanced.number_of_requests) + company = session.load("companies/1", Company) + self.assertEqual(0, session.advanced.number_of_requests) + + time_series = session.time_series_for_entity(company, "stock_price") + time_series_entries = time_series.get(now - datetime.timedelta(days=7), None) + + self.assertEqual(1, len(time_series_entries)) + self.assertEqual(now, time_series_entries[0].timestamp) + self.assertEqual(10, time_series_entries[0].value) + + self.assertEqual(0, session.advanced.number_of_requests) + + event.set() + + worker.run(__subscription_callback) + + with self.store.open_session() as session: + company = Company() + company.Id = "companies/1" + company.name = "HR" + + session.store(company) + + session.time_series_for_entity(company, "stock_price").append_single(now, 10) + session.save_changes() + + self.assertTrue(event.wait(30)) diff --git a/ravendb/tests/jvm_migrated_tests/client_tests/time_series_tests/test_time_series_includes.py b/ravendb/tests/jvm_migrated_tests/client_tests/time_series_tests/test_time_series_includes.py index 629f9bf2..9ccdcdc8 100644 --- a/ravendb/tests/jvm_migrated_tests/client_tests/time_series_tests/test_time_series_includes.py +++ b/ravendb/tests/jvm_migrated_tests/client_tests/time_series_tests/test_time_series_includes.py @@ -4,7 +4,7 @@ from ravendb.infrastructure.orders import Company, Order from ravendb.primitives.constants import int_max from ravendb.primitives.time_series import TimeValue -from ravendb.tests.test_base import TestBase, User +from ravendb.tests.test_base import TestBase document_id = "users/gracjan" company_id = "companies/1-A" @@ -17,6 +17,13 @@ tag3 = "watches/bitfit" +class User: + def __init__(self, Id: str = None, name: str = None, works_at: str = None): + self.Id = Id + self.name = name + self.works_at = works_at + + class TestTimeSeriesIncludes(TestBase): def setUp(self): super(TestTimeSeriesIncludes, self).setUp() @@ -1184,3 +1191,69 @@ def test_should_throw_on_including_time_series_with_negative_count(self): TimeSeriesRangeType.LAST, -1024 ), ) + + def test_include_time_series_and_documents_and_counters(self): + with self.store.open_session() as session: + user = User() + user.name = "Oren" + user.works_at = "companies/1" + session.store(user, "users/ayende") + + company = Company(name="HR") + session.store(company, "companies/1") + + session.save_changes() + + with self.store.open_session() as session: + tsf = session.time_series_for("users/ayende", "Heartrate") + + for i in range(360): + tsf.append_single(base_line + timedelta(seconds=i * 10), 67, "watches/fitbit") + + session.counters_for("users/ayende").increment("likes", 100) + session.counters_for("users/ayende").increment("dislikes", 5) + session.save_changes() + + with self.store.open_session() as session: + user = session.load( + "users/ayende", + User, + lambda i: i.include_documents("works_at") + .include_time_series("Heartrate", base_line, base_line + timedelta(minutes=30)) + .include_counter("likes") + .include_counter("dislikes"), + ) + + self.assertEqual(1, session.advanced.number_of_requests) + + self.assertEqual("Oren", user.name) + + # should not go to server + + company = session.load(user.works_at, Company) + self.assertEqual(1, session.advanced.number_of_requests) + + self.assertEqual("HR", company.name) + + # should not go to server + vals = session.time_series_for("users/ayende", "Heartrate").get( + base_line, base_line + timedelta(minutes=30) + ) + self.assertEqual(1, session.advanced.number_of_requests) + + self.assertEqual(181, len(vals)) + + self.assertEqual(base_line, vals[0].timestamp) + self.assertEqual("watches/fitbit", vals[0].tag) + self.assertEqual(67, vals[0].values[0]) + self.assertEqual(base_line + timedelta(minutes=30), vals[180].timestamp) + + # should not go to server + counters = session.counters_for("users/ayende").get_all() + + self.assertEqual(1, session.advanced.number_of_requests) + + counter = counters.get("likes") + self.assertEqual(100, counter) + counter = counters.get("dislikes") + self.assertEqual(5, counter)