Skip to content

Commit f2bea35

Browse files
committed
RDBC-1021: raise InvalidOperationException when includes are used in a no_tracking session
session.load(..., includes=...) and query._include() silently discarded includes when no_tracking=True, giving callers a false sense of correctness. C# raises InvalidOperationException with "Cannot register includes when NoTracking is enabled." Added the same guard to both the load() path and the query _include() method, using the full C# exception message. Regression test: test_no_tracking_includes_guard.py verifies that include calls raise InvalidOperationException on no-tracking sessions for load, query, and document_query paths.
1 parent 0cfd03a commit f2bea35

4 files changed

Lines changed: 87 additions & 4 deletions

File tree

ravendb/documents/session/document_session.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,13 @@ def load(
316316
result = load_operation.get_documents(object_type)
317317
return result.popitem()[1] if len(result) == 1 else result if result else None
318318

319+
if self.no_tracking:
320+
raise InvalidOperationException(
321+
"Cannot register includes when NoTracking is enabled. "
322+
"Included documents are not tracked, so subsequent Load operations for that data will still trigger additional server requests. "
323+
"To avoid confusion, 'Include' operations are disallowed when tracking is disabled on the session or query."
324+
)
325+
319326
include_builder = IncludeBuilder(self.conventions)
320327
includes(include_builder)
321328

ravendb/documents/session/query.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
SuggestionBuilder,
5353
)
5454
from ravendb.documents.queries.utils import QueryFieldUtil
55+
from ravendb.exceptions.exceptions import InvalidOperationException
5556
from ravendb.documents.session.event_args import BeforeQueryEventArgs
5657
from ravendb.documents.session.loaders.include import IncludeBuilderBase, QueryIncludeBuilder
5758
from ravendb.documents.session.misc import MethodCall, CmpXchg, OrderingType, DocumentQueryCustomization
@@ -368,6 +369,12 @@ def __action():
368369
return MoreLikeThisScope(token, self.__add_query_parameter, __action)
369370

370371
def _include(self, path_or_include_builder: Union[str, IncludeBuilderBase]) -> None:
372+
if self._the_session is not None and self._the_session.no_tracking:
373+
raise InvalidOperationException(
374+
"Cannot register includes when NoTracking is enabled. "
375+
"Included documents are not tracked, so subsequent Load operations for that data will still trigger additional server requests. "
376+
"To avoid confusion, 'Include' operations are disallowed when tracking is disabled on the session or query."
377+
)
371378
if isinstance(path_or_include_builder, str):
372379
self._document_includes.add(path_or_include_builder)
373380
elif isinstance(path_or_include_builder, IncludeBuilderBase):

ravendb/tests/jvm_migrated_tests/issues_tests/test_ravenDB_11217.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def test_session_wide_no_tracking_should_work(self):
3737
with self.store.open_session(session_options=no_tracking_options) as session:
3838
self.assertEqual(0, session.advanced.number_of_requests)
3939

40-
product1 = session.load("products/1-A", Product, lambda b: b.include_documents("supplier"))
40+
product1 = session.load("products/1-A", Product)
4141

4242
self.assertEqual(1, session.advanced.number_of_requests)
4343

@@ -52,7 +52,7 @@ def test_session_wide_no_tracking_should_work(self):
5252
self.assertEqual(2, session.advanced.number_of_requests)
5353
self.assertFalse(session.advanced.is_loaded(supplier.Id))
5454

55-
product2 = session.load("products/1-A", Product, lambda b: b.include_documents(("supplier")))
55+
product2 = session.load("products/1-A", Product)
5656
self.assertNotEqual(product2, product1)
5757

5858
with self.store.open_session(session_options=no_tracking_options) as session:
@@ -79,7 +79,7 @@ def test_session_wide_no_tracking_should_work(self):
7979

8080
with self.store.open_session(session_options=no_tracking_options) as session:
8181
self.assertEqual(0, session.advanced.number_of_requests)
82-
products = list(session.query(object_type=Product).include("supplier"))
82+
products = list(session.query(object_type=Product))
8383

8484
self.assertEqual(1, session.advanced.number_of_requests)
8585
self.assertEqual(1, len(products))
@@ -95,7 +95,7 @@ def test_session_wide_no_tracking_should_work(self):
9595
self.assertEqual(2, session.advanced.number_of_requests)
9696
self.assertFalse(session.advanced.is_loaded(supplier.Id))
9797

98-
products = list(session.query(object_type=Product).include("supplier"))
98+
products = list(session.query(object_type=Product))
9999
self.assertEqual(1, len(products))
100100

101101
product2 = products[0]
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""
2+
No-tracking session: using includes raises when session.no_tracking is True.
3+
4+
C# reference: SlowTests/Issues/RavenDB_21339.cs
5+
Using_Includes_In_Non_Tracking_Session_Should_Throw
6+
"""
7+
8+
from ravendb.documents.session.misc import SessionOptions
9+
from ravendb.tests.test_base import TestBase
10+
11+
12+
class Manager:
13+
def __init__(self, name: str = None):
14+
self.name = name
15+
16+
17+
class Employee:
18+
def __init__(self, name: str = None, manager_id: str = None):
19+
self.name = name
20+
self.manager_id = manager_id
21+
22+
23+
class TestRavenDB21339(TestBase):
24+
def setUp(self):
25+
super().setUp()
26+
27+
def _populate(self):
28+
with self.store.open_session() as session:
29+
session.store(Manager(name="Boss"), "managers/1")
30+
session.store(Employee(name="Alice", manager_id="managers/1"), "employees/1")
31+
session.save_changes()
32+
33+
def test_load_with_includes_in_no_tracking_session_should_throw(self):
34+
"""
35+
session.load() with an include builder in a no_tracking session must
36+
raise an exception, not silently ignore the includes."""
37+
self._populate()
38+
39+
with self.store.open_session(session_options=SessionOptions(no_tracking=True)) as session:
40+
with self.assertRaises(Exception) as ctx:
41+
session.load(
42+
"employees/1",
43+
Employee,
44+
includes=lambda b: b.include_documents("manager_id"),
45+
)
46+
self.assertIn("Cannot register includes when NoTracking is enabled", str(ctx.exception))
47+
48+
def test_query_with_include_in_no_tracking_session_should_throw(self):
49+
"""
50+
query.include() in a no_tracking session must raise an exception,
51+
not silently add the include to an unused tracking cache."""
52+
self._populate()
53+
54+
with self.store.open_session(session_options=SessionOptions(no_tracking=True)) as session:
55+
with self.assertRaises(Exception) as ctx:
56+
list(session.query(object_type=Employee).include("manager_id").wait_for_non_stale_results())
57+
self.assertIn("Cannot register includes when NoTracking is enabled", str(ctx.exception))
58+
59+
def test_document_query_with_include_in_no_tracking_session_should_throw(self):
60+
"""
61+
C# spec: session.Advanced.DocumentQuery<Product>().Include(x => x.Supplier).ToList()
62+
in a no_tracking session must raise InvalidOperationException.
63+
"""
64+
self._populate()
65+
66+
with self.store.open_session(session_options=SessionOptions(no_tracking=True)) as session:
67+
with self.assertRaises(Exception) as ctx:
68+
list(session.advanced.document_query(object_type=Employee).include("manager_id"))
69+
self.assertIn("Cannot register includes when NoTracking is enabled", str(ctx.exception))

0 commit comments

Comments
 (0)