245 lines
9.1 KiB
Python
245 lines
9.1 KiB
Python
"""
|
|
String operations for the Firefly database client.
|
|
"""
|
|
|
|
import logging
|
|
import traceback
|
|
from typing import Optional
|
|
|
|
logger = logging.getLogger("FireflyDB.StringOperations")
|
|
|
|
class StringOperations:
|
|
"""Mixin class for string operations in the Firefly database client."""
|
|
|
|
def __init__(self, client):
|
|
"""Initialize the string operations mixin.
|
|
|
|
Args:
|
|
client: The IFireflyClient instance
|
|
"""
|
|
self.client = client
|
|
self.lib = client.lib
|
|
|
|
def string_set(self, key, value):
|
|
"""Set a string value
|
|
|
|
Args:
|
|
key: The key to set
|
|
value: The value to set
|
|
|
|
Returns:
|
|
True if successful
|
|
"""
|
|
try:
|
|
self.client._check_connection()
|
|
key_bytes = self.client._to_bytes(key)
|
|
value_bytes = self.client._to_bytes(value)
|
|
|
|
# Normal mode
|
|
result = self.lib.StringSet(self.client.client, key_bytes, value_bytes)
|
|
logger.debug(f"StringSet result for key '{key}': {result}")
|
|
return result
|
|
except ConnectionError as e:
|
|
logger.error(f"Connection error in string_set: {e}")
|
|
raise
|
|
except Exception as e:
|
|
logger.error(
|
|
f"Error in string_set: {e}. Traceback:\n{traceback.format_exc()}"
|
|
)
|
|
return False
|
|
|
|
def string_get(self, key):
|
|
"""Get a string value
|
|
|
|
Args:
|
|
key: The key to get
|
|
|
|
Returns:
|
|
The value, or None if not found
|
|
"""
|
|
try:
|
|
self.client._check_connection()
|
|
key_bytes = self.client._to_bytes(key)
|
|
result = self.lib.StringGet(self.client.client, key_bytes)
|
|
logger.debug(f"StringGet raw result pointer: {result}")
|
|
|
|
if result:
|
|
try:
|
|
# If the result is already a bytes object, we don't need to free it
|
|
if isinstance(result, bytes):
|
|
logger.debug(
|
|
"Result is already a Python bytes object, no need to free"
|
|
)
|
|
value = result.decode("utf-8")
|
|
logger.debug(f"StringGet for key '{key}': {value}")
|
|
return value
|
|
|
|
# Otherwise, treat it as a C pointer that needs to be freed
|
|
value = self.client._from_bytes(result)
|
|
logger.debug(f"StringGet decoded value: {value}")
|
|
|
|
# Log before freeing
|
|
logger.debug(f"About to free string at address: {result}")
|
|
self.client._free_string(result)
|
|
logger.debug(f"StringGet for key '{key}': {value}")
|
|
return value
|
|
except Exception as decode_e:
|
|
logger.error(f"Error processing StringGet result: {decode_e}")
|
|
# Try to free anyway, but only if it's not a bytes object
|
|
try:
|
|
if not isinstance(result, bytes):
|
|
self.client._free_string(result)
|
|
except Exception as free_e:
|
|
logger.error(f"Error freeing string in StringGet: {free_e}")
|
|
return None
|
|
logger.debug(f"StringGet for key '{key}': Key not found")
|
|
return None
|
|
except ConnectionError as e:
|
|
logger.error(f"Connection error in string_get: {e}")
|
|
raise
|
|
except Exception as e:
|
|
logger.error(
|
|
f"Error in string_get: {e}. Traceback:\n{traceback.format_exc()}"
|
|
)
|
|
return None
|
|
|
|
def delete(self, key):
|
|
"""Delete a key
|
|
|
|
Args:
|
|
key: The key to delete
|
|
|
|
Returns:
|
|
The number of keys removed
|
|
"""
|
|
try:
|
|
self.client._check_connection()
|
|
key_bytes = self.client._to_bytes(key)
|
|
result = self.lib.ExecuteCommand(self.client.client, b"DEL", key_bytes)
|
|
logger.debug(f"Delete result: {result}")
|
|
|
|
if result:
|
|
try:
|
|
# Handle as bytes or C pointer
|
|
if isinstance(result, bytes):
|
|
# Directly decode bytes
|
|
response = result.decode("utf-8")
|
|
|
|
# Regular response format
|
|
try:
|
|
count = int(response.strip(":\r\n"))
|
|
except ValueError:
|
|
logger.warning(
|
|
f"Unexpected response from DEL command: {response}"
|
|
)
|
|
count = 0
|
|
else:
|
|
# Handle as C pointer
|
|
try:
|
|
response = self.client._from_bytes(result)
|
|
count = int(response.strip(":\r\n"))
|
|
self.client._free_string(result)
|
|
except ValueError:
|
|
self.client._free_string(
|
|
result
|
|
) # Free memory even on error
|
|
logger.warning(
|
|
f"Unexpected response from DEL command: {response}"
|
|
)
|
|
count = 0
|
|
|
|
logger.debug(f"Deleted key '{key}'. Count: {count}")
|
|
return count
|
|
except Exception as e:
|
|
logger.error(f"Error processing DEL result: {e}")
|
|
# Try to free if needed
|
|
if result and not isinstance(result, bytes):
|
|
try:
|
|
self.client._free_string(result)
|
|
except Exception as free_e:
|
|
logger.error(f"Error freeing DEL result: {free_e}")
|
|
return 0
|
|
logger.debug(f"Key '{key}' not found.")
|
|
return 0
|
|
except ConnectionError as e:
|
|
logger.error(f"Connection error in delete: {e}")
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Error in delete: {e}. Traceback:\n{traceback.format_exc()}")
|
|
return 0
|
|
|
|
def keys(self, pattern):
|
|
"""Get all keys matching the pattern
|
|
|
|
Args:
|
|
pattern: The pattern to match against keys
|
|
|
|
Returns:
|
|
A list of keys that match the pattern
|
|
"""
|
|
try:
|
|
self.client._check_connection()
|
|
pattern_bytes = self.client._to_bytes(pattern)
|
|
result = self.lib.Keys(self.client.client, pattern_bytes)
|
|
logger.debug(f"Keys result: {result}")
|
|
|
|
if result:
|
|
try:
|
|
# Handle as bytes or C pointer
|
|
if isinstance(result, bytes):
|
|
# Directly decode bytes
|
|
keys = result.decode("utf-8").split("\n")
|
|
logger.debug(f"Keys result as bytes: {keys}")
|
|
return keys
|
|
else:
|
|
# Handle as C pointer
|
|
keys = self.client._from_bytes(result)
|
|
logger.debug(f"Keys result as C pointer: {keys}")
|
|
return keys
|
|
except Exception as e:
|
|
logger.error(f"Error processing Keys result: {e}")
|
|
return []
|
|
else:
|
|
logger.debug(f"No keys found matching pattern '{pattern}'")
|
|
return []
|
|
except ConnectionError as e:
|
|
logger.error(f"Connection error in keys: {e}")
|
|
raise
|
|
|
|
def type(self, key):
|
|
"""Get the type of a key
|
|
|
|
Args:
|
|
key: The key to get the type of
|
|
|
|
Returns:
|
|
The type of the key (string, list, hash, etc.) or None if key doesn't exist
|
|
"""
|
|
try:
|
|
self.client._check_connection()
|
|
key_bytes = self.client._to_bytes(key)
|
|
# Use ExecuteCommand since Type is not directly exposed in the library
|
|
result = self.lib.ExecuteCommand(self.client.client, b"TYPE", key_bytes)
|
|
logger.debug(f"Type result: {result}")
|
|
|
|
if result:
|
|
try:
|
|
type_str = self.client._from_bytes(result)
|
|
# Free the string allocated by the C library
|
|
self.client._free_string(result)
|
|
return type_str
|
|
except Exception as e:
|
|
logger.error(f"Error processing TYPE result: {e}")
|
|
# Ensure we free the string even if processing fails
|
|
self.client._free_string(result)
|
|
return None
|
|
else:
|
|
logger.debug(f"Key '{key}' not found or TYPE command failed")
|
|
return None
|
|
except ConnectionError as e:
|
|
logger.error(f"Connection error in type: {e}")
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Error in type: {e}. Traceback:\n{traceback.format_exc()}")
|
|
return None
|