Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes to SQLite-based attribute container store #58

Merged
merged 1 commit into from
Jan 21, 2024
Merged
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
6 changes: 3 additions & 3 deletions acstore/helpers/json_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ def ConvertAttributeContainerToJSON(cls, attribute_container):

for attribute_name, attribute_value in attribute_container.GetAttributes():
data_type = schema.get(attribute_name, None)
if data_type:
serializer = schema_helper.SchemaHelper.GetAttributeSerializer(
data_type, 'json')
serializer = schema_helper.SchemaHelper.GetAttributeSerializer(
data_type, 'json')

if serializer:
attribute_value = serializer.SerializeValue(attribute_value)

# JSON will not serialize certain runtime types like set, therefore
Expand Down
26 changes: 4 additions & 22 deletions acstore/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,16 +108,16 @@ def _SetAttributeContainerNextSequenceNumber(
container_type] = next_sequence_number

@abc.abstractmethod
def _WriteNewAttributeContainer(self, container):
"""Writes a new attribute container to the store.
def _WriteExistingAttributeContainer(self, container):
"""Writes an existing attribute container to the store.

Args:
container (AttributeContainer): attribute container.
"""

@abc.abstractmethod
def _WriteExistingAttributeContainer(self, container):
"""Writes an existing attribute container to the store.
def _WriteNewAttributeContainer(self, container):
"""Writes a new attribute container to the store.

Args:
container (AttributeContainer): attribute container.
Expand Down Expand Up @@ -150,12 +150,6 @@ def GetAttributeContainerByIdentifier(self, container_type, identifier):

Returns:
AttributeContainer: attribute container or None if not available.

Raises:
IOError: when the store is closed or if an unsupported identifier is
provided.
OSError: when the store is closed or if an unsupported identifier is
provided.
"""

@abc.abstractmethod
Expand All @@ -168,10 +162,6 @@ def GetAttributeContainerByIndex(self, container_type, index):

Returns:
AttributeContainer: attribute container or None if not available.

Raises:
IOError: when the store is closed.
OSError: when the store is closed.
"""

@abc.abstractmethod
Expand All @@ -185,10 +175,6 @@ def GetAttributeContainers(self, container_type, filter_expression=None):

Returns:
generator(AttributeContainer): attribute container generator.

Raises:
IOError: when the store is closed.
OSError: when the store is closed.
"""

@abc.abstractmethod
Expand Down Expand Up @@ -280,10 +266,6 @@ def _GetCachedAttributeContainer(self, container_type, index):

Returns:
AttributeContainer: attribute container or None if not available.

Raises:
IOError: when there is an error querying the attribute container store.
OSError: when there is an error querying the attribute container store.
"""
lookup_key = f'{container_type:s}.{index:d}'
attribute_container = self._attribute_container_cache.get(lookup_key, None)
Expand Down
73 changes: 40 additions & 33 deletions acstore/sqlite_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,42 @@ def _GetAttributeContainersWithFilter(
if self._storage_profiler:
self._storage_profiler.StopTiming('get_containers')

def _GetNumberOfAttributeContainerRows(self, container_type):
"""Retrieves the number of attribute container rows.

Args:
container_type (str): attribute container type.

Returns:
int: the number of rows of a specified attribute container type.

Raises:
IOError: when there is an error querying the attribute container store.
OSError: when there is an error querying the attribute container store.
"""
self._CommitWriteCache(container_type)

if not self._HasTable(container_type):
return 0

# Note that this is SQLite specific, and will give inaccurate results if
# there are DELETE commands run on the table. acstore does not run any
# DELETE commands.
query = f'SELECT MAX(_ROWID_) FROM {container_type:s} LIMIT 1'

try:
self._cursor.execute(query)
except (sqlite3.InterfaceError, sqlite3.OperationalError) as exception:
raise IOError((
f'Unable to query attribute container store with error: '
f'{exception!s}'))

row = self._cursor.fetchone()
if not row:
return 0

return row[0] or 0

def _HasTable(self, table_name):
"""Determines if a specific table exists.

Expand Down Expand Up @@ -938,33 +974,8 @@ def GetNumberOfAttributeContainers(self, container_type):

Returns:
int: the number of containers of a specified type.

Raises:
IOError: when there is an error querying the attribute container store.
OSError: when there is an error querying the attribute container store.
"""
self._CommitWriteCache(container_type)

if not self._HasTable(container_type):
return 0

# Note that this is SQLite specific, and will give inaccurate results if
# there are DELETE commands run on the table. acstore does not run any
# DELETE commands.
query = f'SELECT MAX(_ROWID_) FROM {container_type:s} LIMIT 1'

try:
self._cursor.execute(query)
except (sqlite3.InterfaceError, sqlite3.OperationalError) as exception:
raise IOError((
f'Unable to query attribute container store with error: '
f'{exception!s}'))

row = self._cursor.fetchone()
if not row:
return 0

return row[0] or 0
return self._attribute_container_sequence_numbers[container_type]

def HasAttributeContainers(self, container_type):
"""Determines if store contains a specific type of attribute containers.
Expand All @@ -975,13 +986,8 @@ def HasAttributeContainers(self, container_type):
Returns:
bool: True if the store contains the specified type of attribute
containers.

Raises:
IOError: when there is an error querying the attribute container store.
OSError: when there is an error querying the attribute container store.
"""
count = self.GetNumberOfAttributeContainers(container_type)
return count > 0
return self._attribute_container_sequence_numbers[container_type] > 0

def Open(self, path=None, read_only=True, **unused_kwargs): # pylint: disable=arguments-differ
"""Opens the store.
Expand Down Expand Up @@ -1062,6 +1068,7 @@ def Open(self, path=None, read_only=True, **unused_kwargs): # pylint: disable=a
# Initialize next_sequence_number based on the file contents so that
# AttributeContainerIdentifier points to the correct attribute container.
for container_type in self._containers_manager.GetContainerTypes():
next_sequence_number = self.GetNumberOfAttributeContainers(container_type)
next_sequence_number = self._GetNumberOfAttributeContainerRows(
container_type)
self._SetAttributeContainerNextSequenceNumber(
container_type, next_sequence_number)
35 changes: 35 additions & 0 deletions tests/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,40 @@ def testSetStorageProfiler(self):
test_store.SetStorageProfiler(None)


class AttributeContainerStoreWithReadCacheTest(test_lib.BaseTestCase):
"""Tests for the attribute container store with read cache."""

# pylint: disable=protected-access

def testCacheAttributeContainerByIndex(self):
"""Tests the _CacheAttributeContainerByIndex function."""
attribute_container = test_lib.TestAttributeContainer()

with test_lib.TempDirectory():
test_store = interface.AttributeContainerStoreWithReadCache()

self.assertEqual(len(test_store._attribute_container_cache), 0)

test_store._CacheAttributeContainerByIndex(attribute_container, 0)
self.assertEqual(len(test_store._attribute_container_cache), 1)

def testGetCachedAttributeContainer(self):
"""Tests the _GetCachedAttributeContainer function."""
attribute_container = test_lib.TestAttributeContainer()

with test_lib.TempDirectory():
test_store = interface.AttributeContainerStoreWithReadCache()

cached_container = test_store._GetCachedAttributeContainer(
attribute_container.CONTAINER_TYPE, 1)
self.assertIsNone(cached_container)

test_store._CacheAttributeContainerByIndex(attribute_container, 1)

cached_container = test_store._GetCachedAttributeContainer(
attribute_container.CONTAINER_TYPE, 1)
self.assertIsNotNone(cached_container)


if __name__ == '__main__':
unittest.main()
62 changes: 30 additions & 32 deletions tests/sqlite_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,18 +116,6 @@ def tearDown(self):
containers_manager.AttributeContainersManager.DeregisterAttributeContainer(
test_lib.TestAttributeContainer)

def testCacheAttributeContainerByIndex(self):
"""Tests the _CacheAttributeContainerByIndex function."""
attribute_container = test_lib.TestAttributeContainer()

with test_lib.TempDirectory():
test_store = sqlite_store.SQLiteAttributeContainerStore()

self.assertEqual(len(test_store._attribute_container_cache), 0)

test_store._CacheAttributeContainerByIndex(attribute_container, 0)
self.assertEqual(len(test_store._attribute_container_cache), 1)

def testCheckStorageMetadata(self):
"""Tests the _CheckStorageMetadata function."""
with test_lib.TempDirectory():
Expand Down Expand Up @@ -219,22 +207,36 @@ def testGetAttributeContainersWithFilter(self):
finally:
test_store.Close()

def testGetCachedAttributeContainer(self):
"""Tests the _GetCachedAttributeContainer function."""
def testGetNumberOfAttributeContainerRows(self):
"""Tests the _GetNumberOfAttributeContainerRows function."""
attribute_container = test_lib.TestAttributeContainer()

with test_lib.TempDirectory():
with test_lib.TempDirectory() as temp_directory:
test_path = os.path.join(temp_directory, 'acstore.sqlite')
test_store = sqlite_store.SQLiteAttributeContainerStore()
test_store.Open(path=test_path, read_only=False)

cached_container = test_store._GetCachedAttributeContainer(
attribute_container.CONTAINER_TYPE, 1)
self.assertIsNone(cached_container)
try:
number_of_containers = test_store._GetNumberOfAttributeContainerRows(
attribute_container.CONTAINER_TYPE)
self.assertEqual(number_of_containers, 0)

test_store._CacheAttributeContainerByIndex(attribute_container, 1)
test_store.AddAttributeContainer(attribute_container)

cached_container = test_store._GetCachedAttributeContainer(
attribute_container.CONTAINER_TYPE, 1)
self.assertIsNotNone(cached_container)
number_of_containers = test_store._GetNumberOfAttributeContainerRows(
attribute_container.CONTAINER_TYPE)
self.assertEqual(number_of_containers, 1)

# Test for a supported container type that does not have a table
# present in the storage file.
query = f'DROP TABLE {attribute_container.CONTAINER_TYPE:s}'
test_store._cursor.execute(query)
number_of_containers = test_store._GetNumberOfAttributeContainerRows(
attribute_container.CONTAINER_TYPE)
self.assertEqual(number_of_containers, 0)

finally:
test_store.Close()

def testHasTable(self):
"""Tests the _HasTable function."""
Expand Down Expand Up @@ -283,19 +285,19 @@ def testWriteExistingAttributeContainer(self):
test_store.Open(path=test_path, read_only=False)

try:
number_of_containers = test_store.GetNumberOfAttributeContainers(
number_of_containers = test_store._GetNumberOfAttributeContainerRows(
attribute_container.CONTAINER_TYPE)
self.assertEqual(number_of_containers, 0)

test_store._WriteNewAttributeContainer(attribute_container)

number_of_containers = test_store.GetNumberOfAttributeContainers(
number_of_containers = test_store._GetNumberOfAttributeContainerRows(
attribute_container.CONTAINER_TYPE)
self.assertEqual(number_of_containers, 1)

test_store._WriteExistingAttributeContainer(attribute_container)

number_of_containers = test_store.GetNumberOfAttributeContainers(
number_of_containers = test_store._GetNumberOfAttributeContainerRows(
attribute_container.CONTAINER_TYPE)
self.assertEqual(number_of_containers, 1)

Expand All @@ -315,13 +317,13 @@ def testWriteNewAttributeContainer(self):
test_store.Open(path=test_path, read_only=False)

try:
number_of_containers = test_store.GetNumberOfAttributeContainers(
number_of_containers = test_store._GetNumberOfAttributeContainerRows(
attribute_container.CONTAINER_TYPE)
self.assertEqual(number_of_containers, 0)

test_store._WriteNewAttributeContainer(attribute_container)

number_of_containers = test_store.GetNumberOfAttributeContainers(
number_of_containers = test_store._GetNumberOfAttributeContainerRows(
attribute_container.CONTAINER_TYPE)
self.assertEqual(number_of_containers, 1)

Expand Down Expand Up @@ -468,12 +470,8 @@ def testGetNumberOfAttributeContainers(self):
attribute_container.CONTAINER_TYPE)
self.assertEqual(number_of_containers, 1)

# Test for a supported container type that does not have a table
# present in the storage file.
query = f'DROP TABLE {attribute_container.CONTAINER_TYPE:s}'
test_store._cursor.execute(query)
number_of_containers = test_store.GetNumberOfAttributeContainers(
attribute_container.CONTAINER_TYPE)
'bogus')
self.assertEqual(number_of_containers, 0)

finally:
Expand Down