Cat Feeder  1.0.0
The Cat feeder project
Loading...
Searching...
No Matches
sql_redis_cache_rebinds.py
Go to the documentation of this file.
1r"""
2# +==== BEGIN CatFeeder =================+
3# LOGO:
4# ..............(..../\
5# ...............)..(.')
6# ..............(../..)
7# ...............\‍(__)|
8# Inspired by Joan Stark
9# source https://www.asciiart.eu/
10# animals/cats
11# /STOP
12# PROJECT: CatFeeder
13# FILE: sql_redis_cache_rebinds.py
14# CREATION DATE: 18-11-2025
15# LAST Modified: 14:52:36 19-12-2025
16# DESCRIPTION:
17# This is the backend server in charge of making the actual website work.
18# /STOP
19# COPYRIGHT: (c) Cat Feeder
20# PURPOSE: This is the redis cache handler for the sql library using the underlying redis wrapper.
21# // AR
22# +==== END CatFeeder =================+
23"""
24from typing import Any, Callable, Dict, List, Optional, Tuple, Union, Literal, overload
25
26from display_tty import Disp, initialise_logger
27
28from redis import Redis
29
30from ..redis import RedisCaching, RedisArgs
31
32
34 """Redis cache rebind layer for the SQL library.
35
36 Thin subclass of ``RedisCaching`` that adds structured debug logging and a
37 convenience initializer for binding Redis caching to SQL fetcher/writer
38 callables. It does not alter caching semantics; all logic for key
39 construction, serialization, TTL handling, and invalidation lives in the
40 base class. This class simply centralizes instrumentation (``log_debug``)
41 and provides a clearer integration point for the SQL manager without
42 changing its source. No artificial line wrapping is used; paragraphs are
43 kept intact for readability.
44 """
45
46 disp_inner: Disp = initialise_logger(__qualname__, False)
47
48 def __new__(cls, *args, existing_instance: Optional["RedisCaching"] = None, **kwargs):
49 # If a base RedisCaching instance is passed, create a subclass instance
50 # and transparently adopt its state so the child behaves as if
51 # super().__init__ had populated inherited fields.
52 if isinstance(existing_instance, RedisCaching) and not isinstance(existing_instance, SQLRedisCacheRebinds):
53 obj = super().__new__(cls)
54 # Shallow copy instance dictionary to preserve inherited state
55 # type: ignore[attr-defined]
56 obj.__dict__ = existing_instance.__dict__.copy()
57 # Marker to let __init__ know we've already adopted base state
58 obj._adopted_from_base = True # type: ignore[attr-defined]
59 return obj
60 return super().__new__(cls)
61
62 def __init__(self, client: Optional[Union[Redis, RedisArgs]] = None, debug: bool = False, *, namespace: str = "sql", db_label: Optional[str] = None, default_ttls: Optional[Dict[str, int]] = None, existing_instance: Optional["RedisCaching"] = None,) -> None:
63 # ------------------------ The logging function ------------------------
64 self.disp.update_disp_debug(debug)
65 self.disp.log_debug("Initialising...")
66 # -------------------------- Inherited values --------------------------
67 if getattr(self, "_adopted_from_base", False):
68 # Already adopted base state in __new__; do not re-run parent init.
69 # Clean up marker to avoid leaking it.
70 if hasattr(self, "_adopted_from_base"):
71 delattr(self, "_adopted_from_base")
72 else:
73 super().__init__(
74 client,
75 debug,
76 namespace=namespace,
77 db_label=db_label,
78 default_ttls=default_ttls,
79 existing_instance=existing_instance if isinstance(
80 existing_instance, SQLRedisCacheRebinds) else None,
81 )
82 self.disp_inner.log_debug(
83 f"Initialized SQLRedisCacheRebinds namespace='{self.namespace}', db_label='{self.db_label}'"
84 )
85 self.disp.log_debug("Initialised")
86
87 # ------------------------------------------------------------------
88 # Read-through cache wrappers (to be linked with SQL manager calls)
89 # ------------------------------------------------------------------
90
92 self,
93 *,
94 fetcher: Callable[[], Optional[Tuple[int, int, int]]],
95 ttl_seconds: Optional[int] = None,
96 ) -> Optional[Tuple[int, int, int]]:
97 """Get and cache the database semantic version.
98
99 Args:
100 fetcher (Callable[[], Optional[Tuple[int,int,int]]]): Function that returns the database version as a 3-tuple or ``None``.
101 ttl_seconds (int | None): Optional TTL override in seconds.
102
103 Returns:
104 tuple[int,int,int] | None: Version tuple or ``None``.
105 """
106 key = self._key("meta", "db_version")
107 self.disp_inner.log_debug(f"Get database version using key='{key}'")
108 cached = self._get_cached(key)
109 if cached is not None:
110 self.disp_inner.log_debug("Cache hit for database version")
111 if isinstance(cached, (list, tuple)) and len(cached) == 3:
112 try:
113 major, minor, patch = (
114 int(cached[0]), int(cached[1]), int(cached[2]))
115 return (major, minor, patch)
116 except (ValueError, TypeError):
117 return tuple(cached) # type: ignore[return-value]
118 return None
119 self.disp_inner.log_debug(
120 "Cache miss for database version; invoking fetcher")
121 value = fetcher()
122 if ttl_seconds is None:
123 ttl_to_use = self.default_ttls["version"]
124 else:
125 ttl_to_use = int(ttl_seconds)
126 self.disp_inner.log_debug(
127 f"Caching database version with ttl={ttl_to_use}s")
128 self._set_cached(key, value, ttl_to_use)
129 return value
130
132 self,
133 *,
134 fetcher: Callable[[], Union[int, List[str]]],
135 ttl_seconds: Optional[int] = None,
136 error_token: Optional[int] = None,
137 ) -> Union[int, List[str]]:
138 """Get and cache the list of non-internal table names.
139
140 Args:
141 fetcher (Callable[[], int | list[str]]): Function fetching table names or an error token.
142 ttl_seconds (int | None): Optional TTL override.
143 error_token (int | None): Error sentinel; result equal to this is not cached.
144
145 Returns:
146 int | list[str]: Error token or list of table names.
147 """
148 key = self._key("schema", "table_names")
149 self.disp_inner.log_debug(f"Get table names using key='{key}'")
150 cached = self._get_cached(key)
151 if cached is not None:
152 self.disp_inner.log_debug("Cache hit for table names")
153 return cached
154 self.disp_inner.log_debug(
155 "Cache miss for table names; invoking fetcher")
156 value = fetcher()
157 if self._should_cache_union_result(value, error_token):
158 if ttl_seconds is None:
159 ttl_to_use = self.default_ttls["schema"]
160 else:
161 ttl_to_use = int(ttl_seconds)
162 self.disp_inner.log_debug(
163 f"Caching table names with ttl={ttl_to_use}s")
164 self._set_cached(key, value, ttl_to_use)
165 return value
166
168 self,
169 table_name: str,
170 *,
171 fetcher: Callable[[str], Union[List[str], int]],
172 ttl_seconds: Optional[int] = None,
173 error_token: Optional[int] = None,
174 ) -> Union[List[str], int]:
175 """Get and cache the list of column names for a table.
176
177 Args:
178 table_name (str): Target table name.
179 fetcher (Callable[[str], list[str] | int]): Function returning columns or error token.
180 ttl_seconds (int | None): Optional TTL override.
181 error_token (int | None): Error sentinel; result equal to this is not cached.
182
183 Returns:
184 list[str] | int: Column names or error token.
185 """
186 key = self._key("schema", f"columns:{table_name}")
187 self.disp_inner.log_debug(
188 f"Get column names for table='{table_name}' using key='{key}'")
189 cached = self._get_cached(key)
190 if cached is not None:
191 self.disp_inner.log_debug("Cache hit for table columns")
192 return cached
193 self.disp_inner.log_debug(
194 "Cache miss for table columns; invoking fetcher")
195 value = fetcher(table_name)
196 if self._should_cache_union_result(value, error_token):
197 if ttl_seconds is None:
198 ttl_to_use = self.default_ttls["schema"]
199 else:
200 ttl_to_use = int(ttl_seconds)
201 self.disp_inner.log_debug(
202 f"Caching columns for '{table_name}' with ttl={ttl_to_use}s")
203 self._set_cached(key, value, ttl_to_use)
204 return value
205
207 self,
208 table: str,
209 *,
210 fetcher: Callable[[str], Union[int, List[Any]]],
211 ttl_seconds: Optional[int] = None,
212 error_token: Optional[int] = None,
213 ) -> Union[int, List[Any]]:
214 """Get and cache a table schema description.
215
216 Args:
217 table (str): Table name.
218 fetcher (Callable[[str], int | list[Any]]): Function returning description rows or error token.
219 ttl_seconds (int | None): Optional TTL override.
220 error_token (int | None): Error sentinel; result equal to this is not cached.
221
222 Returns:
223 int | list[Any]: Error token or description rows.
224 """
225 key = self._key("schema", f"describe:{table}")
226 self.disp_inner.log_debug(
227 f"Describe table='{table}' using key='{key}'")
228 cached = self._get_cached(key)
229 if cached is not None:
230 self.disp_inner.log_debug("Cache hit for table description")
231 return cached
232 self.disp_inner.log_debug(
233 "Cache miss for table description; invoking fetcher")
234 value = fetcher(table)
235 if self._should_cache_union_result(value, error_token):
236 if ttl_seconds is None:
237 ttl_to_use = self.default_ttls["schema"]
238 else:
239 ttl_to_use = int(ttl_seconds)
240 self.disp_inner.log_debug(
241 f"Caching description for '{table}' with ttl={ttl_to_use}s")
242 self._set_cached(key, value, ttl_to_use)
243 return value
244
246 self,
247 *,
248 fetcher: Callable[[], Union[int, Dict[str, str]]],
249 ttl_seconds: Optional[int] = None,
250 error_token: Optional[int] = None,
251 ) -> Union[int, Dict[str, str]]:
252 """Get and cache all triggers with their SQL definitions.
253
254 Args:
255 fetcher (Callable[[], int | dict[str,str]]): Function returning mapping or error token.
256 ttl_seconds (int | None): Optional TTL override.
257 error_token (int | None): Error sentinel; result equal to this is not cached.
258
259 Returns:
260 int | dict[str, str]: Error token or triggers mapping.
261 """
262 key = self._key("schema", "triggers")
263 self.disp_inner.log_debug(f"Get triggers using key='{key}'")
264 cached = self._get_cached(key)
265 if cached is not None:
266 self.disp_inner.log_debug("Cache hit for triggers")
267 return cached
268 self.disp_inner.log_debug("Cache miss for triggers; invoking fetcher")
269 value = fetcher()
270 if self._should_cache_union_result(value, error_token):
271 if ttl_seconds is None:
272 ttl_to_use = self.default_ttls["schema"]
273 else:
274 ttl_to_use = int(ttl_seconds)
275 self.disp_inner.log_debug(
276 f"Caching triggers with ttl={ttl_to_use}s")
277 self._set_cached(key, value, ttl_to_use)
278 return value
279
281 self,
282 trigger_name: str,
283 *,
284 fetcher: Callable[[str], Union[int, str]],
285 ttl_seconds: Optional[int] = None,
286 error_token: Optional[int] = None,
287 ) -> Union[int, str]:
288 """Get and cache a single trigger definition.
289
290 Args:
291 trigger_name (str): Trigger identifier.
292 fetcher (Callable[[str], int | str]): Function returning SQL text or error token.
293 ttl_seconds (int | None): Optional TTL override.
294 error_token (int | None): Error sentinel; result equal to this is not cached.
295
296 Returns:
297 int | str: Error token or trigger SQL.
298 """
299 key = self._key("schema", f"trigger:{trigger_name}")
300 self.disp_inner.log_debug(
301 f"Get trigger='{trigger_name}' using key='{key}'")
302 cached = self._get_cached(key)
303 if cached is not None:
304 self.disp_inner.log_debug("Cache hit for trigger definition")
305 return cached
306 self.disp_inner.log_debug(
307 "Cache miss for trigger definition; invoking fetcher")
308 value = fetcher(trigger_name)
309 if self._should_cache_union_result(value, error_token):
310 ttl_to_use = self.default_ttls["schema"] if ttl_seconds is None else int(
311 ttl_seconds)
312 self.disp_inner.log_debug(
313 f"Caching trigger '{trigger_name}' with ttl={ttl_to_use}s")
314 self._set_cached(key, value, ttl_to_use)
315 return value
316
318 self,
319 *,
320 fetcher: Callable[[], Union[int, List[str]]],
321 ttl_seconds: Optional[int] = None,
322 error_token: Optional[int] = None,
323 ) -> Union[int, List[str]]:
324 """Get and cache the list of trigger names.
325
326 Args:
327 fetcher (Callable[[], int | list[str]]): Function returning trigger names or error token.
328 ttl_seconds (int | None): Optional TTL override.
329 error_token (int | None): Error sentinel; result equal to this is not cached.
330
331 Returns:
332 int | list[str]: Error token or trigger names.
333 """
334 key = self._key("schema", "trigger_names")
335 self.disp_inner.log_debug(f"Get trigger names using key='{key}'")
336 cached = self._get_cached(key)
337 if cached is not None:
338 self.disp_inner.log_debug("Cache hit for trigger names")
339 return cached
340 self.disp_inner.log_debug(
341 "Cache miss for trigger names; invoking fetcher")
342 value = fetcher()
343 if self._should_cache_union_result(value, error_token):
344 if ttl_seconds is None:
345 ttl_to_use = self.default_ttls["schema"]
346 else:
347 ttl_to_use = int(ttl_seconds)
348 self.disp_inner.log_debug(
349 f"Caching trigger names with ttl={ttl_to_use}s")
350 self._set_cached(key, value, ttl_to_use)
351 return value
352
354 self,
355 table: str,
356 column: Union[str, List[str]],
357 where: Union[str, List[str]] = "",
358 *,
359 fetcher: Callable[[str, Union[str, List[str]], Union[str, List[str]]], int],
360 ttl_seconds: Optional[int] = None,
361 ) -> int:
362 """Get and cache the number of rows matching an optional filter.
363
364 Args:
365 table (str): Table name.
366 column (str | list[str]): Column to count over (usually ``"*"``).
367 where (str | list[str]): Optional WHERE clause or conditions.
368 fetcher (Callable[[str, str | list[str], str | list[str]], int]): Function returning the count.
369 ttl_seconds (int | None): Optional TTL override.
370
371 Returns:
372 int: Row count.
373 """
374 params = {
375 "table": table,
376 "column": self._normalize_selector(column),
377 "where": self._normalize_selector(where),
378 }
379 key = self._key("count", table, params)
380 self.disp_inner.log_debug(
381 f"Get table size for table='{table}' using key='{key}'"
382 )
383 cached = self._get_cached(key)
384 if cached is not None:
385 # Count is an int; cached JSON restores it directly
386 self.disp_inner.log_debug("Cache hit for table size")
387 return int(cached)
388 self.disp_inner.log_debug(
389 "Cache miss for table size; invoking fetcher")
390 value = int(fetcher(table, column, where))
391 if ttl_seconds is None:
392 ttl_to_use = self.default_ttls["count"]
393 else:
394 ttl_to_use = int(ttl_seconds)
395 self.disp_inner.log_debug(f"Caching table size with ttl={ttl_to_use}s")
396 self._set_cached(key, value, ttl_to_use)
397 return value
398
399 @overload
401 self,
402 table: str,
403 column: Union[str, List[str]],
404 where: Union[str, List[str]] = "",
405 *,
406 beautify: Literal[True] = True,
407 fetcher: Callable[[], Union[int, List[Dict[str, Any]]]],
408 ttl_seconds: Optional[int] = None,
409 error_token: Optional[int] = None,
410 ) -> Union[int, List[Dict[str, Any]]]:
411 ...
412
413 @overload
415 self,
416 table: str,
417 column: Union[str, List[str]],
418 where: Union[str, List[str]] = "",
419 *,
420 beautify: Literal[False],
421 fetcher: Callable[[], Union[int, List[Tuple[Any, ...]]]],
422 ttl_seconds: Optional[int] = None,
423 error_token: Optional[int] = None,
424 ) -> Union[int, List[Tuple[Any, ...]]]:
425 ...
426
428 self,
429 table: str,
430 column: Union[str, List[str]],
431 where: Union[str, List[str]] = "",
432 *,
433 beautify: bool = True,
434 fetcher: Callable[[], Union[int, List[Dict[str, Any]], List[Tuple[Any, ...]]]],
435 ttl_seconds: Optional[int] = None,
436 error_token: Optional[int] = None,
437 ) -> Union[int, Union[List[Dict[str, Any]], List[Tuple[Any, ...]]]]:
438 """Get and cache rows from a table.
439
440 When ``beautify`` is True the fetcher must return a list of dictionaries. When False it must return a list of tuples (or a list of lists convertible to tuples). On error the fetcher may return an integer error token.
441
442 Args:
443 table (str): Table name.
444 column (str | list[str]): Column name(s) or ``"*"``.
445 where (str | list[str]): Optional WHERE clause or conditions.
446 beautify (bool): Whether rows are dictionary-shaped.
447 fetcher (Callable[[], int | list[dict[str, Any]] | list[tuple[Any,...]]]): Function that executes the actual SQL call.
448 ttl_seconds (int | None): Optional TTL override.
449 error_token (int | None): Error sentinel; result equal to this is not cached.
450
451 Returns:
452 int | list[dict[str, Any]] | list[tuple[Any, ...]]: Error token or rows.
453 """
454 params = {
455 "table": table,
456 "column": self._normalize_selector(column),
457 "where": self._normalize_selector(where),
458 "beautify": bool(beautify),
459 }
460 key = self._key("data", table, params)
461 self.disp_inner.log_debug(
462 f"Get data from table='{table}', beautify={bool(beautify)} using key='{key}'"
463 )
464 cached = self._get_cached(key)
465 if cached is not None:
466 self.disp_inner.log_debug("Cache hit for table data")
467 if beautify:
468 return cached
469 # Convert inner lists to tuples for non-beautified rows
470 if isinstance(cached, list) and (not cached or isinstance(cached[0], list)):
471 rows: List[Tuple[Any, ...]] = []
472 for row in cached:
473 rows.append(tuple(row))
474 self.disp_inner.log_debug(
475 "Converted cached rows to tuples for non-beautified result")
476 return rows # type: ignore[return-value]
477 return cached
478 self.disp_inner.log_debug(
479 "Cache miss for table data; invoking fetcher")
480 value = fetcher()
481 if self._should_cache_union_result(value, error_token):
482 if ttl_seconds is None:
483 ttl_to_use = self.default_ttls["data"]
484 else:
485 ttl_to_use = int(ttl_seconds)
486 self.disp_inner.log_debug(
487 f"Caching table data with ttl={ttl_to_use}s")
488 self._set_cached(key, value, ttl_to_use)
489 return value
490
491 # ------------------------------------------------------------------
492 # Write wrappers (invalidate appropriate caches on success)
493 # ------------------------------------------------------------------
495 self,
496 table: str,
497 columns: List[Tuple[str, str]],
498 *,
499 writer: Callable[[str, List[Tuple[str, str]]], int],
500 ) -> int:
501 """Create a table and invalidate related cache entries on success.
502
503 Args:
504 table (str): New table name.
505 columns (list[tuple[str,str]]): Column definitions.
506 writer (Callable[[str, list[tuple[str,str]]], int]): Function performing the SQL execution.
507
508 Returns:
509 int: Writer return code.
510 """
511 result = writer(table, columns)
512 self.disp_inner.log_debug(
513 f"Writer executed for create_table, result={result}")
514 if isinstance(result, int) and result == 0:
515 self.disp_inner.log_debug(
516 "Invalidating caches after create_table success")
517 # Invalidate table list and specific table schema
519 f"{self.namespace}:{self.db_label}:schema:table_names",
520 f"{self.namespace}:{self.db_label}:schema:columns:{table}:*",
521 f"{self.namespace}:{self.db_label}:schema:describe:{table}:*",
522 ])
523 return result
524
526 self,
527 trigger_name: str,
528 table_name: str,
529 timing_event: str,
530 body: str,
531 *,
532 writer: Callable[[str, str, str, str], int],
533 ) -> int:
534 """Insert a trigger and invalidate trigger caches on success.
535
536 Args:
537 trigger_name (str): Trigger name.
538 table_name (str): Related table name.
539 timing_event (str): Trigger timing/event (e.g., ``BEFORE INSERT``).
540 body (str): SQL body.
541 writer (Callable[[str,str,str,str], int]): Function performing the SQL execution.
542
543 Returns:
544 int: Writer return code.
545 """
546 result = writer(trigger_name, table_name, timing_event, body)
547 self.disp_inner.log_debug(
548 f"Writer executed for insert_trigger '{trigger_name}', result={result}")
549 if isinstance(result, int) and result == 0:
550 self.disp_inner.log_debug(
551 "Invalidating caches after insert_trigger success")
552 self.invalidate_trigger(trigger_name)
554 f"{self.namespace}:{self.db_label}:schema:triggers",
555 f"{self.namespace}:{self.db_label}:schema:trigger_names",
556 ])
557 return result
558
559 # create_trigger is an alias to insert_trigger to preserve API consistency
561 self,
562 trigger_name: str,
563 table_name: str,
564 timing_event: str,
565 body: str,
566 *,
567 writer: Callable[[str, str, str, str], int],
568 ) -> int:
569 """Alias to :py:meth:`insert_trigger`.
570
571 Args:
572 trigger_name (str): Trigger name.
573 table_name (str): Related table name.
574 timing_event (str): Trigger timing/event.
575 body (str): SQL body.
576 writer (Callable[[str,str,str,str], int]): SQL executor.
577
578 Returns:
579 int: Writer return code.
580 """
581 return self.insert_trigger(trigger_name, table_name, timing_event, body, writer=writer)
582
584 self,
585 table: str,
586 data: Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]],
587 column: Optional[List[str]] = None,
588 *,
589 writer: Callable[[str, Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]], Optional[List[str]]], int],
590 ) -> int:
591 """Insert row(s) into a table and invalidate related caches.
592
593 Args:
594 table (str): Target table.
595 data (list[list[str|None|int|float]] | list[str|None|int|float]): Row or rows.
596 column (list[str] | None): Optional column list.
597 writer (Callable[..., int]): Function performing the SQL execution.
598
599 Returns:
600 int: Writer return code.
601 """
602 result = writer(table, data, column)
603 self.disp_inner.log_debug(
604 f"Writer executed for insert_data_into_table on '{table}', result={result}")
605 if isinstance(result, int) and result == 0:
606 self.disp_inner.log_debug(
607 "Invalidating table caches after insert_data_into_table success")
608 self.invalidate_table(table)
609 return result
610
612 self,
613 table: str,
614 data: Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]],
615 column: List[str],
616 where: Union[str, List[str]] = "",
617 *,
618 writer: Callable[[str, Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]], List[str], Union[str, List[str]]], int],
619 ) -> int:
620 """Update row(s) in a table and invalidate related caches.
621
622 Args:
623 table (str): Target table.
624 data (list[list[str|None|int|float]] | list[str|None|int|float]): New values.
625 column (list[str]): Column names corresponding to ``data``.
626 where (str | list[str]): WHERE clause/conditions.
627 writer (Callable[..., int]): Function performing the SQL execution.
628
629 Returns:
630 int: Writer return code.
631 """
632 self.disp_inner.log_debug(
633 "Update cacher, going to run the writer function."
634 )
635 result = writer(table, data, column, where)
636 self.disp_inner.log_debug(
637 f"Writer executed for update_data_in_table on '{table}', result={result}")
638 if isinstance(result, int) and result == 0:
639 self.disp_inner.log_debug(
640 "Invalidating table caches after update_data_in_table success"
641 )
642 self.invalidate_table(table)
643 return result
644
646 self,
647 table: str,
648 data: Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]],
649 columns: Optional[List[str]] = None,
650 *,
651 writer: Callable[[str, Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]], Optional[List[str]]], int],
652 ) -> int:
653 """Insert or update row(s) and invalidate related caches.
654
655 Args:
656 table (str): Target table.
657 data (list[list[str|None|int|float]] | list[str|None|int|float]): Rows.
658 columns (list[str] | None): Optional columns.
659 writer (Callable[..., int]): Function performing the SQL execution.
660
661 Returns:
662 int: Writer return code.
663 """
664 result = writer(table, data, columns)
665 self.disp_inner.log_debug(
666 f"Writer executed for insert_or_update_data_into_table on '{table}', result={result}")
667 if isinstance(result, int) and result == 0:
668 self.disp_inner.log_debug(
669 "Invalidating table caches after insert_or_update_data_into_table success")
670 self.invalidate_table(table)
671 return result
672
674 self,
675 trigger_name: str,
676 table_name: str,
677 timing_event: str,
678 body: str,
679 *,
680 writer: Callable[[str, str, str, str], int],
681 ) -> int:
682 """Insert or update a trigger and invalidate related caches.
683
684 Args:
685 trigger_name (str): Trigger name.
686 table_name (str): Related table name.
687 timing_event (str): Trigger timing/event.
688 body (str): SQL body.
689 writer (Callable[[str,str,str,str], int]): SQL executor.
690
691 Returns:
692 int: Writer return code.
693 """
694 result = writer(trigger_name, table_name, timing_event, body)
695 self.disp_inner.log_debug(
696 f"Writer executed for insert_or_update_trigger '{trigger_name}', result={result}")
697 if isinstance(result, int) and result == 0:
698 self.disp_inner.log_debug(
699 "Invalidating caches after insert_or_update_trigger success")
700 self.invalidate_trigger(trigger_name)
702 f"{self.namespace}:{self.db_label}:schema:triggers",
703 f"{self.namespace}:{self.db_label}:schema:trigger_names",
704 ])
705 return result
706
708 self,
709 table: str,
710 where: Union[str, List[str]] = "",
711 *,
712 writer: Callable[[str, Union[str, List[str]]], int],
713 ) -> int:
714 """Delete row(s) from a table and invalidate related caches.
715
716 Args:
717 table (str): Target table.
718 where (str | list[str]): WHERE clause/conditions.
719 writer (Callable[[str, str | list[str]], int]): SQL executor.
720
721 Returns:
722 int: Writer return code.
723 """
724 result = writer(table, where)
725 self.disp_inner.log_debug(
726 f"Writer executed for remove_data_from_table on '{table}', result={result}")
727 if isinstance(result, int) and result == 0:
728 self.disp_inner.log_debug(
729 "Invalidating table caches after remove_data_from_table success")
730 self.invalidate_table(table)
731 return result
732
733 # drop_data_from_table preserved as alias to remove_data_from_table
735 self,
736 table: str,
737 *,
738 writer: Callable[[str], int],
739 ) -> int:
740 """Alias to :py:meth:`remove_data_from_table` without a WHERE clause."""
741 result = writer(table)
742 self.disp_inner.log_debug(
743 f"Writer executed for drop_data_from_table on '{table}', result={result}")
744 if isinstance(result, int) and result == 0:
745 self.disp_inner.log_debug(
746 "Invalidating table caches after drop_data_from_table success")
747 self.invalidate_table(table)
748 return result
749
751 self,
752 table: str,
753 *,
754 writer: Callable[[str], int],
755 ) -> int:
756 """Drop a table and invalidate related caches.
757
758 Args:
759 table (str): Table name.
760 writer (Callable[[str], int]): SQL executor.
761
762 Returns:
763 int: Writer return code.
764 """
765 result = writer(table)
766 self.disp_inner.log_debug(
767 f"Writer executed for remove_table '{table}', result={result}")
768 if isinstance(result, int) and result == 0:
769 self.disp_inner.log_debug(
770 "Invalidating caches after remove_table success")
771 self.invalidate_table(table)
772 # Also refresh table_names
774 [f"{self.namespace}:{self.db_label}:schema:table_names"])
775 return result
776
777 # drop_table preserved as alias to remove_table
779 self,
780 table: str,
781 *,
782 writer: Callable[[str], int],
783 ) -> int:
784 """Alias to :py:meth:`remove_table`."""
785 return self.remove_table(table, writer=writer)
786
788 self,
789 trigger_name: str,
790 *,
791 writer: Callable[[str], int],
792 ) -> int:
793 """Drop a trigger and invalidate related caches.
794
795 Args:
796 trigger_name (str): Trigger identifier.
797 writer (Callable[[str], int]): SQL executor.
798
799 Returns:
800 int: Writer return code.
801 """
802 result = writer(trigger_name)
803 self.disp_inner.log_debug(
804 f"Writer executed for remove_trigger '{trigger_name}', result={result}")
805 if isinstance(result, int) and result == 0:
806 self.disp_inner.log_debug(
807 "Invalidating caches after remove_trigger success")
808 self.invalidate_trigger(trigger_name)
810 f"{self.namespace}:{self.db_label}:schema:triggers",
811 f"{self.namespace}:{self.db_label}:schema:trigger_names",
812 ])
813 return result
814
815 # drop_trigger preserved as alias to remove_trigger
817 self,
818 trigger_name: str,
819 *,
820 writer: Callable[[str], int],
821 ) -> int:
822 """Alias to :py:meth:`remove_trigger`."""
823 return self.remove_trigger(trigger_name, writer=writer)
str _key(self, str category, str name, Optional[Any] params=None)
None _invalidate_patterns(self, List[str] patterns)
Any _normalize_selector(self, Union[str, List[str]] selector)
None _set_cached(self, str key, Any value, int ttl_seconds)
None invalidate_trigger(self, Optional[str] trigger_name=None)
bool _should_cache_union_result(self, Any result, Optional[int] error_token)
None __init__(self, Optional[Union[Redis, RedisArgs]] client=None, bool debug=False, *, str namespace="sql", Optional[str] db_label=None, Optional[Dict[str, int]] default_ttls=None, Optional["RedisCaching"] existing_instance=None)
Union[int, str] get_trigger(self, str trigger_name, *, Callable[[str], Union[int, str]] fetcher, Optional[int] ttl_seconds=None, Optional[int] error_token=None)
int insert_or_update_data_into_table(self, str table, Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]] data, Optional[List[str]] columns=None, *, Callable[[str, Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]], Optional[List[str]]], int] writer)
Union[int, List[Any]] describe_table(self, str table, *, Callable[[str], Union[int, List[Any]]] fetcher, Optional[int] ttl_seconds=None, Optional[int] error_token=None)
Union[List[str], int] get_table_column_names(self, str table_name, *, Callable[[str], Union[List[str], int]] fetcher, Optional[int] ttl_seconds=None, Optional[int] error_token=None)
int insert_trigger(self, str trigger_name, str table_name, str timing_event, str body, *, Callable[[str, str, str, str], int] writer)
int remove_data_from_table(self, str table, Union[str, List[str]] where="", *, Callable[[str, Union[str, List[str]]], int] writer)
int create_trigger(self, str trigger_name, str table_name, str timing_event, str body, *, Callable[[str, str, str, str], int] writer)
int create_table(self, str table, List[Tuple[str, str]] columns, *, Callable[[str, List[Tuple[str, str]]], int] writer)
Union[int, Dict[str, str]] get_triggers(self, *, Callable[[], Union[int, Dict[str, str]]] fetcher, Optional[int] ttl_seconds=None, Optional[int] error_token=None)
int get_table_size(self, str table, Union[str, List[str]] column, Union[str, List[str]] where="", *, Callable[[str, Union[str, List[str]], Union[str, List[str]]], int] fetcher, Optional[int] ttl_seconds=None)
int update_data_in_table(self, str table, Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]] data, List[str] column, Union[str, List[str]] where="", *, Callable[[str, Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]], List[str], Union[str, List[str]]], int] writer)
int drop_trigger(self, str trigger_name, *, Callable[[str], int] writer)
Union[int, List[str]] get_trigger_names(self, *, Callable[[], Union[int, List[str]]] fetcher, Optional[int] ttl_seconds=None, Optional[int] error_token=None)
int insert_or_update_trigger(self, str trigger_name, str table_name, str timing_event, str body, *, Callable[[str, str, str, str], int] writer)
int remove_table(self, str table, *, Callable[[str], int] writer)
int remove_trigger(self, str trigger_name, *, Callable[[str], int] writer)
int drop_data_from_table(self, str table, *, Callable[[str], int] writer)
int drop_table(self, str table, *, Callable[[str], int] writer)
Union[int, List[str]] get_table_names(self, *, Callable[[], Union[int, List[str]]] fetcher, Optional[int] ttl_seconds=None, Optional[int] error_token=None)
int insert_data_into_table(self, str table, Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]] data, Optional[List[str]] column=None, *, Callable[[str, Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]], Optional[List[str]]], int] writer)
__new__(cls, *args, Optional["RedisCaching"] existing_instance=None, **kwargs)
Union[int, List[Dict[str, Any]]] get_data_from_table(self, str table, Union[str, List[str]] column, Union[str, List[str]] where="", *, Literal[True] beautify=True, Callable[[], Union[int, List[Dict[str, Any]]]] fetcher, Optional[int] ttl_seconds=None, Optional[int] error_token=None)
Optional[Tuple[int, int, int]] get_database_version(self, *, Callable[[], Optional[Tuple[int, int, int]]] fetcher, Optional[int] ttl_seconds=None)