Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ravendb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
IndexSourceType,
AutoIndexDefinition,
AutoIndexFieldOptions,
ArchivedDataProcessingBehavior,
)
from ravendb.documents.indexes.abstract_index_creation_tasks import (
AbstractIndexDefinitionBuilder,
Expand Down Expand Up @@ -176,6 +177,9 @@
IndexInformation,
GetDetailedStatisticsOperation,
DetailedDatabaseStatistics,
GetEssentialStatisticsOperation,
EssentialDatabaseStatistics,
EssentialIndexInformation,
)
from ravendb.documents.queries.explanation import ExplanationOptions, Explanations
from ravendb.documents.queries.facets.builders import RangeBuilder, FacetBuilder, FacetOperations
Expand Down
9 changes: 9 additions & 0 deletions ravendb/documents/indexes/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@ def __str__(self):
return self.value


class ArchivedDataProcessingBehavior(Enum):
EXCLUDE_ARCHIVED = "ExcludeArchived"
INCLUDE_ARCHIVED = "IncludeArchived"
ARCHIVED_ONLY = "ArchivedOnly"

def __str__(self):
return self.value


class IndexType(Enum):
NONE = "None"
AUTO_MAP = "AutoMap"
Expand Down
112 changes: 111 additions & 1 deletion ravendb/documents/operations/statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@

import requests

from ravendb.documents.indexes.definitions import IndexPriority, IndexLockMode, IndexType, IndexSourceType, IndexState
from ravendb.documents.indexes.definitions import (
ArchivedDataProcessingBehavior,
IndexPriority,
IndexLockMode,
IndexType,
IndexSourceType,
IndexState,
)
from ravendb.documents.operations.definitions import MaintenanceOperation
from ravendb.http.raven_command import RavenCommand
from ravendb.http.server_node import ServerNode
Expand Down Expand Up @@ -96,6 +103,109 @@ def from_json(cls, json_dict) -> DatabaseStatistics:
)


class EssentialIndexInformation:
def __init__(
self,
name: str = None,
lock_mode: IndexLockMode = None,
priority: IndexPriority = None,
index_type: IndexType = None,
source_type: IndexSourceType = None,
archived_data_processing_behavior: Optional[ArchivedDataProcessingBehavior] = None,
):
self.name = name
self.lock_mode = lock_mode
self.priority = priority
self.type = index_type
self.source_type = source_type
self.archived_data_processing_behavior = archived_data_processing_behavior

@classmethod
def from_json(cls, json_dict: dict) -> "EssentialIndexInformation":
return cls(
name=json_dict.get("Name"),
lock_mode=IndexLockMode(json_dict["LockMode"]) if json_dict.get("LockMode") else None,
priority=IndexPriority(json_dict["Priority"]) if json_dict.get("Priority") else None,
index_type=IndexType(json_dict["Type"]) if json_dict.get("Type") else None,
source_type=IndexSourceType(json_dict["SourceType"]) if json_dict.get("SourceType") else None,
archived_data_processing_behavior=(
ArchivedDataProcessingBehavior(json_dict["ArchivedDataProcessingBehavior"])
if json_dict.get("ArchivedDataProcessingBehavior")
else None
),
)


class EssentialDatabaseStatistics:
def __init__(
self,
count_of_indexes: int = None,
count_of_documents: int = None,
count_of_revision_documents: int = None,
count_of_documents_conflicts: int = None,
count_of_tombstones: int = None,
count_of_conflicts: int = None,
count_of_attachments: int = None,
count_of_counter_entries: int = None,
count_of_time_series_segments: int = None,
indexes: List["EssentialIndexInformation"] = None,
):
self.count_of_indexes = count_of_indexes
self.count_of_documents = count_of_documents
self.count_of_revision_documents = count_of_revision_documents
self.count_of_documents_conflicts = count_of_documents_conflicts
self.count_of_tombstones = count_of_tombstones
self.count_of_conflicts = count_of_conflicts
self.count_of_attachments = count_of_attachments
self.count_of_counter_entries = count_of_counter_entries
self.count_of_time_series_segments = count_of_time_series_segments
self.indexes = indexes

@classmethod
def from_json(cls, json_dict: dict) -> "EssentialDatabaseStatistics":
return cls(
count_of_indexes=json_dict.get("CountOfIndexes"),
count_of_documents=json_dict.get("CountOfDocuments"),
count_of_revision_documents=json_dict.get("CountOfRevisionDocuments"),
count_of_documents_conflicts=json_dict.get("CountOfDocumentsConflicts"),
count_of_tombstones=json_dict.get("CountOfTombstones"),
count_of_conflicts=json_dict.get("CountOfConflicts"),
count_of_attachments=json_dict.get("CountOfAttachments"),
count_of_counter_entries=json_dict.get("CountOfCounterEntries"),
count_of_time_series_segments=json_dict.get("CountOfTimeSeriesSegments"),
indexes=(
[EssentialIndexInformation.from_json(x) for x in json_dict["Indexes"]]
if "Indexes" in json_dict
else None
),
)


class GetEssentialStatisticsOperation(MaintenanceOperation["EssentialDatabaseStatistics"]):
def __init__(self, debug_tag: str = None):
self._debug_tag = debug_tag

def get_command(self, conventions: "DocumentConventions") -> RavenCommand["EssentialDatabaseStatistics"]:
return self._GetEssentialStatisticsCommand(self._debug_tag)

class _GetEssentialStatisticsCommand(RavenCommand["EssentialDatabaseStatistics"]):
def __init__(self, debug_tag: Optional[str] = None):
super().__init__(EssentialDatabaseStatistics)
self._debug_tag = debug_tag

def create_request(self, node: ServerNode) -> requests.Request:
url = f"{node.url}/databases/{node.database}/stats/essential"
if self._debug_tag is not None:
url += f"?{self._debug_tag}"
return requests.Request("GET", url)

def set_response(self, response: str, from_cache: bool) -> None:
self.result = EssentialDatabaseStatistics.from_json(json.loads(response))

def is_read_request(self) -> bool:
return True


class GetStatisticsOperation(MaintenanceOperation[DatabaseStatistics]):
def __init__(self, debug_tag: str = None, node_tag: str = None):
self.debug_tag = debug_tag
Expand Down
174 changes: 174 additions & 0 deletions ravendb/tests/issue_tests/test_RDBC_1033.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
"""
RDBC-1033: GetEssentialStatisticsOperation hits /stats/essential.

C# reference: SlowTests.Issues/Issues/RavenDB_18648.cs
Can_Get_Essential_Database_Statistics()
"""

import unittest

from ravendb.documents.indexes.definitions import (
ArchivedDataProcessingBehavior,
IndexLockMode,
IndexPriority,
IndexType,
IndexSourceType,
)
from ravendb.documents.operations.statistics import (
EssentialDatabaseStatistics,
EssentialIndexInformation,
GetEssentialStatisticsOperation,
)
from ravendb.tests.test_base import TestBase


class TestEssentialStatisticsUnit(unittest.TestCase):
"""Unit tests — no server required."""

def test_essential_database_statistics_from_json(self):
stats = EssentialDatabaseStatistics.from_json(
{
"CountOfIndexes": 5,
"CountOfDocuments": 42,
"CountOfRevisionDocuments": 10,
"CountOfDocumentsConflicts": 1,
"CountOfTombstones": 3,
"CountOfConflicts": 1,
"CountOfAttachments": 3,
"CountOfCounterEntries": 7,
"CountOfTimeSeriesSegments": 2,
}
)
self.assertEqual(5, stats.count_of_indexes)
self.assertEqual(42, stats.count_of_documents)
self.assertEqual(10, stats.count_of_revision_documents)
self.assertEqual(1, stats.count_of_documents_conflicts)
self.assertEqual(3, stats.count_of_tombstones)
self.assertEqual(1, stats.count_of_conflicts)
self.assertEqual(3, stats.count_of_attachments)
self.assertEqual(7, stats.count_of_counter_entries)
self.assertEqual(2, stats.count_of_time_series_segments)
self.assertIsNone(stats.indexes)

def test_essential_statistics_empty_json(self):
stats = EssentialDatabaseStatistics.from_json({})
self.assertIsNone(stats.count_of_documents)
self.assertIsNone(stats.count_of_indexes)
self.assertIsNone(stats.count_of_revision_documents)
self.assertIsNone(stats.count_of_tombstones)
self.assertIsNone(stats.indexes)

def test_essential_statistics_with_indexes(self):
stats = EssentialDatabaseStatistics.from_json(
{
"CountOfIndexes": 1,
"CountOfDocuments": 0,
"Indexes": [
{
"Name": "Orders/ByCompany",
"LockMode": "Unlock",
"Priority": "Normal",
"Type": "Map",
"SourceType": "Documents",
}
],
}
)
self.assertIsNotNone(stats.indexes)
self.assertEqual(1, len(stats.indexes))
idx = stats.indexes[0]
self.assertEqual("Orders/ByCompany", idx.name)
self.assertEqual(IndexLockMode.UNLOCK, idx.lock_mode)
self.assertEqual(IndexPriority.NORMAL, idx.priority)
self.assertEqual(IndexType.MAP, idx.type)
self.assertEqual(IndexSourceType.DOCUMENTS, idx.source_type)
self.assertIsNone(idx.archived_data_processing_behavior)

def test_essential_index_information_archived_behavior(self):
idx = EssentialIndexInformation.from_json(
{
"Name": "idx",
"LockMode": "Unlock",
"Priority": "Normal",
"Type": "Map",
"SourceType": "Documents",
"ArchivedDataProcessingBehavior": "IncludeArchived",
}
)
self.assertEqual(ArchivedDataProcessingBehavior.INCLUDE_ARCHIVED, idx.archived_data_processing_behavior)

def test_essential_statistics_empty_indexes_list(self):
stats = EssentialDatabaseStatistics.from_json({"CountOfIndexes": 0, "Indexes": []})
self.assertIsNotNone(stats.indexes)
self.assertEqual([], stats.indexes)

def test_get_essential_statistics_operation_importable(self):
op = GetEssentialStatisticsOperation()
self.assertIsNotNone(op)

def test_get_essential_statistics_url_contains_debug_tag(self):
from ravendb.http.server_node import ServerNode
from ravendb.documents.conventions import DocumentConventions

node = ServerNode("http://localhost:8080", "testdb")
op = GetEssentialStatisticsOperation(debug_tag="src=test")
cmd = op.get_command(DocumentConventions())
req = cmd.create_request(node)
self.assertIn("/stats/essential", req.url)
self.assertIn("src=test", req.url)

def test_get_essential_statistics_url_no_debug_tag(self):
from ravendb.http.server_node import ServerNode
from ravendb.documents.conventions import DocumentConventions

node = ServerNode("http://localhost:8080", "testdb")
op = GetEssentialStatisticsOperation()
cmd = op.get_command(DocumentConventions())
req = cmd.create_request(node)
self.assertEqual("http://localhost:8080/databases/testdb/stats/essential", req.url)

def test_top_level_package_exports(self):
import ravendb

self.assertTrue(hasattr(ravendb, "GetEssentialStatisticsOperation"))
self.assertTrue(hasattr(ravendb, "EssentialDatabaseStatistics"))
self.assertTrue(hasattr(ravendb, "EssentialIndexInformation"))
self.assertTrue(hasattr(ravendb, "ArchivedDataProcessingBehavior"))


class TestEssentialStatistics(TestBase):
"""Integration tests — require a live server."""

def setUp(self):
super().setUp()
self.store = self.get_document_store()

def tearDown(self):
super().tearDown()
self.store.close()

def test_get_essential_statistics_operation_returns_result(self):
from ravendb.infrastructure.orders import Product

with self.store.open_session() as session:
p = Product()
p.name = "Widget"
session.store(p, "products/1")
session.save_changes()

stats = self.store.maintenance.send(GetEssentialStatisticsOperation())

self.assertIsInstance(stats, EssentialDatabaseStatistics)
self.assertGreaterEqual(stats.count_of_documents, 1)
self.assertIsNotNone(stats.count_of_indexes)

def test_get_essential_statistics_empty_database(self):
stats = self.store.maintenance.send(GetEssentialStatisticsOperation())
self.assertIsInstance(stats, EssentialDatabaseStatistics)
self.assertEqual(0, stats.count_of_documents)
self.assertEqual(0, stats.count_of_tombstones)
self.assertEqual(0, stats.count_of_conflicts)


if __name__ == "__main__":
unittest.main()