Coverage for python/lsst/dax/apdb/apdbMetadataCassandra.py: 19%
65 statements
« prev ^ index » next coverage.py v7.4.2, created at 2024-02-23 11:49 +0000
« prev ^ index » next coverage.py v7.4.2, created at 2024-02-23 11:49 +0000
1# This file is part of dax_apdb.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
22from __future__ import annotations
24__all__ = ["ApdbMetadataCassandra"]
26from collections.abc import Generator
27from typing import TYPE_CHECKING, Any
29from .apdbMetadata import ApdbMetadata
30from .apdbSchema import ApdbTables
31from .cassandra_utils import PreparedStatementCache, quote_id
33if TYPE_CHECKING: 33 ↛ 34line 33 didn't jump to line 34, because the condition on line 33 was never true
34 from .apdbCassandra import ApdbCassandraConfig
35 from .apdbCassandraSchema import ApdbCassandraSchema
38class ApdbMetadataCassandra(ApdbMetadata):
39 """Implementation of `ApdbMetadata` for Cassandra backend.
41 Parameters
42 ----------
43 session : `cassandra.cluster.Session`
44 Cassandra session instance.
45 schema : `ApdbSqlSchema`
46 Object providing access to schema details.
47 """
49 def __init__(self, session: Any, schema: ApdbCassandraSchema, config: ApdbCassandraConfig):
50 self._session = session
51 self._config = config
52 self._part = 0 # Partition for all rows
53 self._preparer = PreparedStatementCache(session)
54 # _table_clause will be None when metadata table is not configured
55 self._table_clause: str | None = None
56 if ApdbTables.metadata in schema.tableSchemas:
57 table_name = schema.tableName(ApdbTables.metadata)
58 keyspace = schema.keyspace()
59 if not keyspace:
60 self._table_clause = quote_id(table_name)
61 else:
62 self._table_clause = f"{quote_id(keyspace)}.{quote_id(table_name)}"
64 def get(self, key: str, default: str | None = None) -> str | None:
65 # Docstring is inherited.
66 if self._table_clause is None:
67 return default
68 query = f"SELECT value FROM {self._table_clause} WHERE meta_part = ? AND name = ?"
69 result = self._session.execute(
70 self._preparer.prepare(query),
71 (self._part, key),
72 timeout=self._config.read_timeout,
73 execution_profile="read_tuples",
74 )
75 if (row := result.one()) is not None:
76 return row[0]
77 else:
78 return default
80 def set(self, key: str, value: str, *, force: bool = False) -> None:
81 # Docstring is inherited.
82 if self._table_clause is None:
83 raise RuntimeError("Metadata table does not exist")
84 if not key or not value:
85 raise ValueError("name and value cannot be empty")
86 query = f"INSERT INTO {self._table_clause} (meta_part, name, value) VALUES (?, ?, ?)"
87 if not force and self.get(key) is not None:
88 raise KeyError(f"Metadata key {key!r} already exists")
89 # Race is still possible between check and insert.
90 self._session.execute(
91 self._preparer.prepare(query),
92 (self._part, key, value),
93 timeout=self._config.write_timeout,
94 execution_profile="write",
95 )
97 def delete(self, key: str) -> bool:
98 # Docstring is inherited.
99 if self._table_clause is None:
100 # Missing table means nothing to delete.
101 return False
102 if not key:
103 raise ValueError("name cannot be empty")
104 query = f"DELETE FROM {self._table_clause} WHERE meta_part = ? AND name = ?"
105 # Cassandra cannot tell how many rows are deleted, just check if row
106 # exists now.
107 exists = self.get(key) is not None
108 # Race is still possible between check and remove.
109 self._session.execute(
110 self._preparer.prepare(query),
111 (self._part, key),
112 timeout=self._config.write_timeout,
113 execution_profile="write",
114 )
115 return exists
117 def items(self) -> Generator[tuple[str, str], None, None]:
118 # Docstring is inherited.
119 if self._table_clause is None:
120 # Missing table means nothing to return.
121 return
122 query = f"SELECT name, value FROM {self._table_clause} WHERE meta_part = ?"
123 result = self._session.execute(
124 self._preparer.prepare(query),
125 (self._part,),
126 timeout=self._config.read_timeout,
127 execution_profile="read_tuples",
128 )
129 for row in result:
130 yield tuple(row)
132 def empty(self) -> bool:
133 # Docstring is inherited.
134 if self._table_clause is None:
135 # Missing table means empty.
136 return True
137 query = f"SELECT count(*) FROM {self._table_clause} WHERE meta_part = ?"
138 result = self._session.execute(
139 self._preparer.prepare(query),
140 (self._part,),
141 timeout=self._config.read_timeout,
142 execution_profile="read_tuples",
143 )
144 row = result.one()
145 return row[0] == 0
147 def table_exists(self) -> bool:
148 """Return `True` if metadata table exists."""
149 return self._table_clause is not None