Skip to content
150 changes: 117 additions & 33 deletions dictsqlite/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,28 +393,8 @@
self.db = db
self.table_name = table_name

def get_raw_value(self, key):
"""DBから生の値を取得し、必要に応じて復号/デコードして返す。"""
result_queue = queue.Queue()
self.db.operation_queue.put((
self.db._fetchone, # pylint: disable=protected-access
(
(
"SELECT value FROM "
f"{self.db._quote_ident(self.table_name)} " # nosec B608 - safely quoted
"WHERE key = ?"
),
(key,),
),
{}, result_queue
))
result = result_queue.get()
if isinstance(result, Exception):
raise result
if result is None:
raise KeyError(f"Key {key} not found in table {self.table_name}.")

value_str = result[0]
def _deserialize_value(self, value_str, key=None):
"""文字列形式の値をデシリアライズして返す。"""
if self.db.password is not None:
value_str = self.db._decrypt(value_str) # pylint: disable=protected-access

Expand All @@ -431,7 +411,6 @@
if isinstance(value_str, str):
# base64またはlatin1でエンコードされたpickleデータ
value_bytes = base64.b64decode(value_str)
# または: value_bytes = value_str.encode('latin1')
else:
value_bytes = value_str
# 安全なUnpicklerで復元
Expand All @@ -454,6 +433,29 @@
# pickleデコードも失敗した場合は文字列として返す
return value_str

def get_raw_value(self, key):
"""DBから生の値を取得し、必要に応じて復号/デコードして返す。"""
result_queue = queue.Queue()
self.db.operation_queue.put((
self.db._fetchone, # pylint: disable=protected-access
(
(
"SELECT value FROM "
f"{self.db._quote_ident(self.table_name)} " # nosec B608 - safely quoted
"WHERE key = ?"
),
(key,),
),
{}, result_queue
))
result = result_queue.get()
if isinstance(result, Exception):
raise result
if result is None:
raise KeyError(f"Key {key} not found in table {self.table_name}.")

return self._deserialize_value(result[0], key)

def __getitem__(self, key):
raw_value = self.get_raw_value(key)
# 生の値をプロキシオブジェクトでラップして返す
Expand Down Expand Up @@ -500,15 +502,21 @@
def __delitem__(self, key):
self.db.operation_queue.put((
self.db._execute, # pylint: disable=protected-access
(f"DELETE FROM {self.db._quote_ident(self.table_name)} WHERE key = ?", (key,)),
( # nosec B608
f"DELETE FROM {self.db._quote_ident(self.table_name)} WHERE key = ?",

Check warning

Code scanning / Bandit

Possible SQL injection vector through string-based query construction. Warning

Possible SQL injection vector through string-based query construction.
(key,)
),
{}, None
))

def __contains__(self, key):
result_queue = queue.Queue()
self.db.operation_queue.put((
self.db._fetchone, # pylint: disable=protected-access
(f"SELECT 1 FROM {self.db._quote_ident(self.table_name)} WHERE key = ?", (key,)),
( # nosec B608
f"SELECT 1 FROM {self.db._quote_ident(self.table_name)} WHERE key = ?",

Check warning

Code scanning / Bandit

Possible SQL injection vector through string-based query construction. Warning

Possible SQL injection vector through string-based query construction.
(key,)
),
{}, result_queue
))
result = result_queue.get()
Expand All @@ -534,24 +542,69 @@
return default

def __repr__(self):
return f"{dict(self)}"
"""テーブル内容を辞書形式の文字列で返す。大規模テーブルでは注意。"""
return f"{dict(self.items())}"

def __iter__(self):
"""キーを逐次イテレートするジェネレータを返す(メモリ効率良)。"""
for row in self.get_all_rows():
yield row[0]

def __len__(self):
"""テーブル内のエントリ数を返す(COUNT使用で効率的)。"""
result_queue = queue.Queue()
self.db.operation_queue.put((
self.db._fetchone, # pylint: disable=protected-access
(f"SELECT COUNT(*) FROM {self.db._quote_ident(self.table_name)}",), # nosec B608
{}, result_queue
))
result = result_queue.get()
if isinstance(result, Exception):
raise result
return result[0] if result else 0

def __eq__(self, other):
"""dictまたはTableProxyとの等価比較。全データをメモリにロードする。"""
if isinstance(other, dict):
return dict(self.items()) == other
if isinstance(other, DictSQLite.TableProxy):
return dict(self.items()) == dict(other.items())
return NotImplemented
Comment on lines +566 to +572

Copilot AI Dec 6, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing documentation: The __eq__ method lacks a docstring. While there's a brief inline comment, a proper docstring should document the behavior, including what types it can compare with (dict, TableProxy) and that it loads all table data into memory for comparison.

Copilot uses AI. Check for mistakes.

def keys(self):
"""全キーをリストで返す。イテレータが必要なら iter(table) を使用。"""
return list(self)

def values(self):
"""全値をリストで返す。大規模テーブルではメモリ注意。"""
result = []
for row in self.get_all_rows():
key = row[0]
key, raw_value_str = row[0], row[1]
try:
# __getitem__ を経由して正しいプロキシオブジェクトを取得
yield key, self[key]
except (KeyError, json.JSONDecodeError):
# デコードできない値はそのまま返す
yield key, row[1]
raw_value = self._deserialize_value(raw_value_str, key)
result.append(raw_value)
except (json.JSONDecodeError, pickle.UnpicklingError, ValueError, TypeError):
result.append(raw_value_str)
return result

def items(self):
"""全(key, value)ペアをリストで返す。大規模テーブルではメモリ注意。"""
result = []
for row in self.get_all_rows():
key, raw_value_str = row[0], row[1]
try:
raw_value = self._deserialize_value(raw_value_str, key)
result.append((key, raw_value))
except (json.JSONDecodeError, pickle.UnpicklingError, ValueError, TypeError):
result.append((key, raw_value_str))
return result
Comment on lines +574 to +600

Copilot AI Dec 6, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing documentation: The keys(), values(), and items() methods lack docstrings. While brief inline comments mention memory concerns, proper docstrings would better document the return types, behavior differences from standard dict (returns list instead of dict_keys/dict_values/dict_items views), and memory implications for large tables.

Copilot uses AI. Check for mistakes.

def get_all_rows(self):
"""テーブル内の全行を (key, value) のタプルで返す。"""
result_queue = queue.Queue()
self.db.operation_queue.put((
self.db._fetchall, # pylint: disable=protected-access
(f"SELECT key, value FROM {self.db._quote_ident(self.table_name)}",),
(f"SELECT key, value FROM {self.db._quote_ident(self.table_name)}",), # nosec B608
{}, result_queue
))
result = result_queue.get()
Expand Down Expand Up @@ -789,6 +842,17 @@
raise result
return result is not None

def __eq__(self, other):
"""dictまたはDictSQLiteとの等価比較。全データをメモリにロードする。"""
if isinstance(other, dict):
proxy = self.TableProxy(self, self.table_name)
return dict(proxy.items()) == other
if isinstance(other, DictSQLite):
proxy1 = self.TableProxy(self, self.table_name)
proxy2 = other.TableProxy(other, other.table_name)
return dict(proxy1.items()) == dict(proxy2.items())
return NotImplemented
Comment on lines +845 to +854

Copilot AI Dec 6, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance concern: Similar to TableProxy's __eq__, this method loads all table data into memory via proxy.items(). For large tables, this could cause memory issues. Consider adding a docstring warning about this behavior or implementing a more efficient streaming comparison.

Copilot uses AI. Check for mistakes.
Comment on lines +845 to +854

Copilot AI Dec 6, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing documentation: The __eq__ method lacks a docstring explaining its behavior, particularly that it loads all table data into memory for comparison. This is important information for users working with large datasets. Consider adding a docstring that documents the memory implications and the types it can compare with (dict, DictSQLite).

Copilot uses AI. Check for mistakes.

def __repr__(self):
if self.version == 2:
result = {}
Expand Down Expand Up @@ -894,6 +958,26 @@
raise result
return [row[0] for row in result]

def table(self, table_name):
"""指定したテーブルのTableProxyを返す。テーブルが存在しない場合は作成する。

Args:
table_name: テーブル名

Returns:
TableProxy: 指定したテーブルへのプロキシオブジェクト
"""
# 常に CREATE TABLE IF NOT EXISTS を実行(競合状態を回避)
schema = '(key TEXT PRIMARY KEY, value TEXT)'
create_table_sql = (
"CREATE TABLE IF NOT EXISTS "
f"{self._quote_ident(table_name)} "
f"{schema}"
)
self.operation_queue.put((self._execute, (create_table_sql,), {}, None))
self.operation_queue.join()
return self.TableProxy(self, table_name)

def clear_table(self, table_name=None):
"""指定テーブル(未指定なら現行)の全データを削除。"""
if table_name is None:
Expand Down
Loading
Loading