diff --git a/pyproject.toml b/pyproject.toml index 65780b7..b9c800a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "plexosdb" -version = "0.0.6" +version = "0.0.7" readme = "README.md" license = {file = "LICENSE.txt"} keywords = [] diff --git a/src/plexosdb/schema.sql b/src/plexosdb/schema.sql index b2192a4..ebc76b2 100644 --- a/src/plexosdb/schema.sql +++ b/src/plexosdb/schema.sql @@ -231,7 +231,7 @@ CREATE TABLE `t_category` CREATE TABLE `t_object` ( - `object_id` INTEGER, + `object_id` INTEGER UNIQUE, `class_id` INT NULL, `name` VARCHAR(512) NULL COLLATE NOCASE, `category_id` INT NULL, @@ -241,6 +241,7 @@ CREATE TABLE `t_object` `X` INT NULL, `Y` INT NULL, `Z` INT NULL, + UNIQUE (`class_id`, `name`) CONSTRAINT PK_t_object PRIMARY KEY (`object_id`) ); diff --git a/src/plexosdb/sqlite.py b/src/plexosdb/sqlite.py index b3d2839..5be818e 100644 --- a/src/plexosdb/sqlite.py +++ b/src/plexosdb/sqlite.py @@ -37,6 +37,8 @@ def __init__( super().__init__() self._conn = sqlite3.connect(":memory:") self._sqlite_config() + self._QUERY_CACHE: dict[tuple, int] = {} + if create_collations: self._create_collations() self._create_table_schema() @@ -444,6 +446,11 @@ def add_object( ----- By default, we add all objects to the system membership. + Raises + ------ + sqlite.IntegrityError + if an object is inserted without a unique name/class pair + Returns ------- int @@ -795,6 +802,11 @@ def _get_id( "object_name": object_name, } + # tuple that should be unique for any id returned + query_key = (table.name, object_name, class_name, parent_class_name, child_class_name) + if query_key in self._QUERY_CACHE: + return self._QUERY_CACHE[query_key] + query = f"SELECT {column_name} FROM `{table_name}`" conditions = [] join_clauses = [] @@ -837,7 +849,6 @@ def _get_id( if conditions else f" WHERE {table_name}.name = :object_name" ) - result = self.query(query, params) if not result: @@ -847,7 +858,12 @@ def _get_id( if len(result) > 1: msg = f"Multiple ids returned for {object_name} and {class_name}. Try passing addtional filters" raise ValueError(msg) - return result[0][0] # Get first element of tuple + + ret: int = result[0][0] # Get first element of tuple + + self._QUERY_CACHE[query_key] = ret + + return ret def get_membership_id( self, @@ -1054,8 +1070,6 @@ def query(self, query_string: str, params=None) -> list[tuple]: String to get passed to the database connector. params Tuple or dict for passing - fetchone - Return firstrow Note ---- @@ -1065,7 +1079,9 @@ def query(self, query_string: str, params=None) -> list[tuple]: """ with self._conn as conn: res = conn.execute(query_string, params) if params else conn.execute(query_string) - return res.fetchall() + ret = res.fetchall() + + return ret def ingest_from_records(self, tag: str, record_data: Sequence): """Insert elements from xml to database.""" @@ -1088,6 +1104,7 @@ def ingest_from_records(self, tag: str, record_data: Sequence): conn.execute(ingestion_sql, record) except sqlite3.Error as err: raise err + logger.trace("Finished ingesting {}", tag) return diff --git a/tests/data/plexosdb.xml b/tests/data/plexosdb.xml index 6870ce6..358b8d3 100644 --- a/tests/data/plexosdb.xml +++ b/tests/data/plexosdb.xml @@ -82,7 +82,7 @@ 3 2 - SolarPV01 + SolarPV02 97 40d15a07-8ccc-460e-919a-ec8e211899a8 diff --git a/tests/test_plexosdb_sqlite.py b/tests/test_plexosdb_sqlite.py index 25c5291..2708058 100644 --- a/tests/test_plexosdb_sqlite.py +++ b/tests/test_plexosdb_sqlite.py @@ -1,7 +1,11 @@ import pytest +import shutil import xml.etree.ElementTree as ET # noqa: N817 from plexosdb.enums import ClassEnum, CollectionEnum, Schema from plexosdb.sqlite import PlexosSQLite +from sqlite3 import IntegrityError +from collections.abc import Generator +from pathlib import Path DB_FILENAME = "plexosdb.xml" @@ -12,8 +16,13 @@ def db_empty() -> "PlexosSQLite": @pytest.fixture -def db(data_folder) -> PlexosSQLite: - return PlexosSQLite(xml_fname=data_folder.joinpath(DB_FILENAME)) +def db(data_folder: Path, tmp_path: Path) -> Generator[PlexosSQLite, None, None]: + xml_fname = data_folder / DB_FILENAME + xml_copy = tmp_path / f"copy_{DB_FILENAME}" + shutil.copy(xml_fname, xml_copy) + db = PlexosSQLite(xml_fname=str(xml_copy)) + yield db + xml_copy.unlink() def test_database_initialization(db): @@ -80,10 +89,6 @@ def test_check_id_exists(db): assert isinstance(system_check, bool) assert not system_check - # Check that returns ValueError if multiple object founds - with pytest.raises(ValueError): - _ = db.check_id_exists(Schema.Objects, "SolarPV01", class_name=ClassEnum.Generator) - @pytest.mark.get_functions def test_get_id(db): @@ -156,18 +161,9 @@ def test_get_collection_id(db): @pytest.mark.get_functions def test_get_object_id(db): - gen_01_name = "gen1" - gen_id = db.add_object( - gen_01_name, ClassEnum.Generator, CollectionEnum.Generators, description="Test Gen" - ) - assert gen_id - - gen_id_get = db.get_object_id(gen_01_name, class_name=ClassEnum.Generator) - assert gen_id == gen_id_get - - # Add generator with same name different category gen_01_name = "gen1" category_name = "PV Gens" + gen_id = db.add_object( gen_01_name, ClassEnum.Generator, @@ -175,8 +171,19 @@ def test_get_object_id(db): description="Test Gen", category_name=category_name, ) - with pytest.raises(ValueError): - _ = db.get_object_id(gen_01_name, class_name=ClassEnum.Generator) + assert gen_id + + gen_id_get = db.get_object_id(gen_01_name, class_name=ClassEnum.Generator) + assert gen_id == gen_id_get + + # Add generator with same name and no category + with pytest.raises(IntegrityError): + gen_id = db.add_object( + gen_01_name, + ClassEnum.Generator, + CollectionEnum.Generators, + description="Test Gen", + ) max_rank = db.get_category_max_id(ClassEnum.Generator) assert max_rank == 2 # Data has ranks 0, 1. 2 is with the new category