Cat Feeder  1.0.0
The Cat feeder project
Loading...
Searching...
No Matches
sql_cache_orchestrator.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_cache_orchestrator.py
14# CREATION DATE: 18-11-2025
15# LAST Modified: 14:51:59 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: File that contains the class in charge of calling the redis cache if present and fallback to sql querying if absent.
21# // AR
22# +==== END CatFeeder =================+
23"""
24
25# typing helper
26from typing import List, Dict, Union, Any, Tuple, Optional, Literal, overload, Callable
27
28
29# logger class
30from display_tty import Disp, initialise_logger
31
32# sql wrapper components
33from .sql_injection import SQLInjection
34from .sql_constants import GET_TABLE_SIZE_ERROR
35from .sql_query_boilerplates import SQLQueryBoilerplates
36from .sql_redis_cache_rebinds import SQLRedisCacheRebinds
37from .sql_sanitisation_functions import SQLSanitiseFunctions
38
39
41
42 disp: Disp = initialise_logger(__qualname__, False)
43
44 def __init__(self, sql_query_boilerplates: SQLQueryBoilerplates, redis_cacher: Optional[SQLRedisCacheRebinds] = None, success: int = 0, error: int = 84, debug: bool = False) -> None:
45 # ------------------------ The logging function ------------------------
46 self.disp.update_disp_debug(debug)
47 self.disp.log_debug("Initialising...")
48 # -------------------------- Inherited values --------------------------
49 self.success: int = success
50 self.error = error
51 self.debug = debug
52 # ---------------------- SQL boilerplate handler ----------------------
53 self._sql_query_boilerplate: SQLQueryBoilerplates = sql_query_boilerplates
54 # ---------------------- Reddis caching instance ----------------------
55 self._redis_cacher: Optional[SQLRedisCacheRebinds] = redis_cacher
56
57 # ---------------------- The anty injection class ----------------------
58 self.sql_injection: SQLInjection = SQLInjection(
59 self.error,
60 self.success,
61 self.debug
62 )
63 # -------------------- Keyword sanitizing functions --------------------
64 self.sanitize_functions: SQLSanitiseFunctions = SQLSanitiseFunctions(
65 success=self.success, error=self.error, debug=self.debug
66 )
67 self.disp.log_debug("Initialised")
68
69 def _normalize_cell(self, cell: object) -> Union[str, None, int, float]:
70 """Normalize a cell value for parameter binding.
71
72 Converts special tokens (e.g., 'now', 'current_date') and preserves numeric
73 types. Returns None for null-like inputs.
74
75 Args:
76 cell (object): The cell value to normalize.
77
78 Returns:
79 Union[str, None, int, float]: Normalized cell value.
80 """
81 if cell is None:
82 return None
83 if isinstance(cell, (int, float)):
84 return cell
85 s = str(cell)
86 sl = s.lower()
87 if sl in ("now", "now()"):
88 return self.sanitize_functions.sql_time_manipulation.get_correct_now_value()
89 if sl in ("current_date", "current_date()"):
90 return self.sanitize_functions.sql_time_manipulation.get_correct_current_date_value()
91 return s
92
93 def _parse_and_validate_where(self, where: Union[str, List[str]]) -> Tuple[Union[str, List[str]], List[Union[str, int, float, None]]]:
94 """Parse WHERE clause, validate column names, and extract values for parameterization.
95
96 This method:
97 1. Parses WHERE clause to extract column names and values
98 2. Validates column names for SQL injection (not values)
99 3. Returns the original WHERE clause and extracted values list
100
101 Args:
102 where (Union[str, List[str]]): WHERE clause content.
103
104 Returns:
105 Tuple[Union[str, List[str]], List[Union[str, int, float, None]]]:
106 - Original WHERE clause (unchanged, will be parameterized later)
107 - List of extracted values (empty if validation fails or no values)
108
109 Raises:
110 RuntimeError: If SQL injection detected in column names.
111 """
112 if where == "" or (isinstance(where, list) and len(where) == 0):
113 return where, []
114
115 # Convert to list for uniform processing
116 if isinstance(where, str):
117 where_list = [where]
118 else:
119 where_list = list(where)
120
121 column_names: List[str] = []
122
123 for clause in where_list:
124 clause_str = str(clause).strip()
125
126 # Extract column name from comparison
127 if "=" in clause_str:
128 parts = clause_str.split("=", maxsplit=1)
129 if len(parts) == 2:
130 column_name = parts[0].strip()
131 column_names.append(column_name)
132
133 # Validate only column names for injection (not values)
134 if column_names and self.sql_injection.check_if_injections_in_strings(column_names):
135 self.disp.log_error(
136 "SQL injection detected in WHERE column names.")
137 raise RuntimeError("SQL injection detected in WHERE clause")
138
139 # Return original WHERE clause - parameterization happens in boilerplate layer
140 return where, []
141
142 def update_redis_cacher(self, redis_cacher: Optional[SQLRedisCacheRebinds] = None) -> None:
143 """Update the redis caching instance only with an initialised SQLRedisCacheRebinds class.
144 This function has no effect if a non-initialised SQLRedisCacheRebinds class or other arguments are passed.
145
146 Args:
147 redis_cacher (Optional[SQLRedisCacheRebinds], optional): The initialised SQLRedisCacheRebinds class instance. Defaults to None.
148 """
149 if isinstance(redis_cacher, SQLRedisCacheRebinds) and not callable(redis_cacher):
150 self._redis_cacher = redis_cacher
151
152 def get_database_version(self) -> Optional[Tuple[int, int, int]]:
153 """Fetch and parse the database version.
154
155 Returns:
156 Optional[Tuple[int, int, int]]: A tuple representing the database version,
157 or None if the query fails.
158 """
159 sql_function: Callable = self._sql_query_boilerplate.get_database_version
160 resp: Optional[Tuple[int, int, int]] = None
161 if self._redis_cacher:
162 self.disp.log_debug("Cacher instance is defined, calling.")
164 fetcher=sql_function
165 )
166 else:
167 self.disp.log_debug(
168 "No cacher instance defined, calling sql boilerplate directly."
169 )
170 resp = sql_function()
171 return resp
172
173 def get_table_column_names(self, table_name: str) -> Union[List[str], int]:
174 """Return the list of column names for a given table.
175
176 Args:
177 table_name (str): Name of the table to retrieve column names from.
178
179 Returns:
180 Union[List[str], int]: List of column names on success, or `self.error` on failure.
181 """
182 sql_function: Callable = self._sql_query_boilerplate.get_table_column_names
183 resp: Union[List[str], int] = self.error
184 if self._redis_cacher:
185 self.disp.log_debug("Cacher instance is defined, calling.")
187 table_name=table_name,
188 fetcher=sql_function,
189 error_token=self.error
190 )
191 else:
192 self.disp.log_debug(
193 "No cacher instance defined, calling sql boilerplate directly."
194 )
195 resp = sql_function(table_name)
196 return resp
197
198 def get_table_names(self) -> Union[int, List[str]]:
199 """Retrieve the names of all tables in the database.
200
201 Returns:
202 Union[int, List[str]]: List of table names on success, or `self.error` on failure.
203 """
204 sql_function: Callable = self._sql_query_boilerplate.get_table_names
205 resp: Union[int, List[str]] = self.error
206 if self._redis_cacher:
207 self.disp.log_debug("Cacher instance is defined, calling.")
208 resp = self._redis_cacher.get_table_names(
209 fetcher=sql_function,
210 error_token=self.error
211 )
212 else:
213 self.disp.log_debug(
214 "No cacher instance defined, calling sql boilerplate directly."
215 )
216 resp = sql_function()
217 return resp
218
219 def get_triggers(self) -> Union[int, Dict[str, str]]:
220 """Retrieve all triggers and their SQL definitions.
221
222 Returns:
223 Union[int, Dict[str, str]]: Dictionary of {trigger_name: sql_definition},
224 or `self.error` on failure.
225 """
226 sql_function: Callable = self._sql_query_boilerplate.get_triggers
227 resp: Union[int, Dict[str, str]] = self.error
228 if self._redis_cacher:
229 self.disp.log_debug("Cacher instance is defined, calling.")
230 resp = self._redis_cacher.get_triggers(
231 fetcher=sql_function,
232 error_token=self.error
233 )
234 else:
235 self.disp.log_debug(
236 "No cacher instance defined, calling sql boilerplate directly."
237 )
238 resp = sql_function()
239 return resp
240
241 def get_trigger(self, trigger_name: str, db_name: Optional[str] = None) -> Union[int, str]:
242 """Retrieve the SQL definition of a specific trigger.
243
244 Args:
245 trigger_name (str): The trigger name to fetch.
246 db_name (Optional[str], optional): Database name. Defaults to None.
247
248 Returns:
249 Union[int, str]: The SQL definition, or `self.error` on failure.
250 """
251 sql_function: Callable = self._sql_query_boilerplate.get_trigger
252 resp: Union[int, str] = self.error
253 if not trigger_name:
254 self.disp.log_error("Trigger name cannot be empty.")
255 return self.error
256
257 to_check: List[str] = [trigger_name]
258 if db_name:
259 to_check.append(db_name)
260
261 if self.sql_injection.check_if_injections_in_strings(to_check):
262 self.disp.log_error("SQL Injection detected in trigger name.")
263 return self.error
264 if self._redis_cacher:
265 self.disp.log_debug("Cacher instance is defined, calling.")
266 resp = self._redis_cacher.get_trigger(
267 trigger_name=trigger_name,
268 fetcher=sql_function,
269 error_token=self.error
270 )
271 else:
272 self.disp.log_debug(
273 "No cacher instance defined, calling sql boilerplate directly."
274 )
275 resp = sql_function(trigger_name)
276 return resp
277
278 def get_trigger_names(self, db_name: Optional[str] = None) -> Union[int, List[str]]:
279 """Return a list of trigger names in the current or specified MySQL database.
280
281 Args:
282 db_name (Optional[str], optional):
283 Name of the database/schema to query.
284 Defaults to None, which uses the currently selected database.
285
286 Returns:
287 Union[int, List[str]]: List of trigger names, or ``self.error`` on failure.
288 """
289 sql_function: Callable = self._sql_query_boilerplate.get_trigger_names
290 resp: Union[int, List[str]] = self.error
291 if db_name:
292 if self.sql_injection.check_if_injections_in_strings([db_name]):
293 self.disp.log_error(
294 "SQL Injection detected in database name."
295 )
296 return self.error
297 if self._redis_cacher:
298 self.disp.log_debug("Cacher instance is defined, calling.")
300 fetcher=sql_function,
301 error_token=self.error
302 )
303 else:
304 self.disp.log_debug(
305 "No cacher instance defined, calling sql boilerplate directly."
306 )
307 resp = sql_function(db_name)
308 return resp
309
310 def describe_table(self, table: str) -> Union[int, List[Any]]:
311 """Fetch the headers (description) of a table from the database.
312
313 Args:
314 table (str): The name of the table to describe.
315
316 Returns:
317 Union[int, List[Any]]: A list containing the description of the table, or self.error if an error occurs.
318 """
319 sql_function: Callable = self._sql_query_boilerplate.describe_table
320 resp: Union[List[str], int] = self.error
321 if self.sql_injection.check_if_sql_injection(table) is True:
322 self.disp.log_error("Injection detected.", "sql")
323 return self.error
324 if self._redis_cacher:
325 self.disp.log_debug("Cacher instance is defined, calling.")
326 resp = self._redis_cacher.describe_table(
327 table=table,
328 fetcher=sql_function,
329 error_token=self.error
330 )
331 else:
332 self.disp.log_debug(
333 "No cacher instance defined, calling sql boilerplate directly."
334 )
335 resp = sql_function(table)
336 return resp
337
338 def create_table(self, table: str, columns: List[Tuple[str, str]]) -> int:
339 """Create a new table in the MySQL database, compatible with MySQL 5.0+.
340
341 Args:
342 table (str): Name of the new table.
343 columns (List[Tuple[str, str]]): List of (column_name, column_type) pairs.
344
345 Returns:
346 int: ``self.success`` on success, or ``self.error`` on failure.
347
348 Example:
349 .. code-block:: python
350
351 table_name = "users"
352 columns = [
353 ("id", "INT AUTO_INCREMENT PRIMARY KEY"),
354 ("username", "VARCHAR(255) NOT NULL UNIQUE"),
355 ("email", "VARCHAR(255) NOT NULL"),
356 ("created_at", "DATETIME DEFAULT CURRENT_TIMESTAMP")
357 ]
358
359 result = self.create_table(table_name, columns)
360 if result == self.success:
361 print(f"Table '{table_name}' created successfully.")
362 else:
363 print(f"Failed to create table '{table_name}'.")
364
365 Notes:
366 - Protects against SQL injection using :class:`SQLInjection`.
367 - Escapes table and column names with backticks for MySQL.
368 - Includes version-aware fallback for MySQL 5.0 compatibility.
369 """
370 sql_function: Callable[
371 [str, List[Tuple[str, str]]],
372 int
373 ] = self._sql_query_boilerplate.create_table
374 resp: int = self.error
375 if self.sql_injection.check_if_injections_in_strings([table]):
376 self.disp.log_error("Injection detected in table name.")
377 return self.error
378 if self._redis_cacher:
379 self.disp.log_debug("Cacher instance is defined, calling.")
380 resp = self._redis_cacher.create_table(
381 table=table,
382 columns=columns,
383 writer=sql_function
384 )
385 else:
386 self.disp.log_debug(
387 "No cacher instance defined, calling sql boilerplate directly."
388 )
389 resp = sql_function(table, columns)
390 return resp
391
392 def insert_data_into_table(self, table: str, data: Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]], column: Union[List[str], None] = None) -> int:
393 """Insert data into a table.
394
395 Args:
396 table (str): Name of the table.
397 data (Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]]): Data to insert.
398 column (Union[List[str], None], optional): List of column names. Defaults to None.
399
400 Returns:
401 int: ``self.success`` on success, or ``self.error`` on failure.
402 """
403 sql_function: Callable = self._sql_query_boilerplate.insert_data_into_table
404 resp: int = self.error
405 if column is None:
406 column = []
407 check_data: Union[
408 List[List[Union[str, None, int, float]]],
409 List[Union[str, None, int, float]]
410 ] = [table]
411 if column is not None:
412 check_data.extend(column)
413 if isinstance(data, List):
414 for i in data:
415 if isinstance(i, List):
416 check_data.extend(i)
417 else:
418 check_data.append(i)
419 if self.sql_injection.check_if_injections_in_strings(check_data) is True:
420 self.disp.log_error("Injection detected.", "sql")
421 return self.error
422 if self._redis_cacher:
423 self.disp.log_debug("Cacher instance is defined, calling.")
425 table=table,
426 data=data,
427 column=column,
428 writer=sql_function
429 )
430 else:
431 self.disp.log_debug(
432 "No cacher instance defined, calling sql boilerplate directly."
433 )
434 resp = sql_function(table, data, column)
435 return resp
436
437 def insert_trigger(self, trigger_name: str, table_name: str, timing_event: str, body: str) -> int:
438 """Insert (create) a new SQL trigger into a MySQL or MariaDB database.
439
440 Args:
441 trigger_name (str): The name of the trigger to create.
442 table_name (str): The name of the table the trigger is being applied to.
443 timing_event (str): The rule when the event is to be triggered. e.g., 'BEFORE INSERT'.
444 body (str): The full SQL CREATE TRIGGER statement.
445
446 Returns:
447 int: ``self.success`` on success, or ``self.error`` on failure.
448 """
449 sql_function: Callable = self._sql_query_boilerplate.insert_trigger
450 resp: int = self.error
451 if not all([trigger_name, table_name, timing_event, body]):
452 self.disp.log_error("All parameters must be provided.")
453 return self.error
454 if self._redis_cacher:
455 self.disp.log_debug("Cacher instance is defined, calling.")
456 resp = self._redis_cacher.insert_trigger(
457 trigger_name=trigger_name,
458 table_name=table_name,
459 timing_event=timing_event,
460 body=body,
461 writer=sql_function
462 )
463 else:
464 self.disp.log_debug(
465 "No cacher instance defined, calling sql boilerplate directly."
466 )
467 resp = sql_function(trigger_name, table_name, timing_event, body)
468 return resp
469
470 @overload
472 self,
473 table: str,
474 column: Union[str, List[str]],
475 where: Union[str, List[str]] = "",
476 beautify: Literal[True] = True,
477 ) -> Union[int, List[Dict[str, Any]]]: ...
478
479 @overload
481 self,
482 table: str,
483 column: Union[str, List[str]],
484 where: Union[str, List[str]] = "",
485 beautify: Literal[False] = False,
486 ) -> Union[int, List[Tuple[Any, ...]]]: ...
487
488 def get_data_from_table(self, table: str, column: Union[str, List[str]], where: Union[str, List[str]] = "", beautify: bool = True) -> Union[int, Union[List[Dict[str, Any]], List[Tuple[Any, ...]]]]:
489 """Fetch rows from a table.
490
491 Args:
492 table (str): Name of the table to query.
493 column (Union[str, List[str]]): Column selector; a single column name or a list of column names.
494 where (Union[str, List[str]]): Optional WHERE clause content; string or list joined by ``AND``.
495 beautify (bool): When True, return a list of dict rows; when False, return a list of tuples.
496
497 Returns:
498 Union[int, List[Dict[str, Any]], List[Tuple[Any, Any]]]: Query result on success, or ``self.error`` on failure.
499 """
500 sql_function: Callable = self._sql_query_boilerplate.get_data_from_table
501 resp: Union[
502 int,
503 Union[
504 List[Dict[str, Any]],
505 List[Tuple[Any, Any]]
506 ]
507 ] = self.error
508 # Check table/column names before caching
509 if self.sql_injection.check_if_injections_in_strings([table, column]) is True:
510 self.disp.log_error("Injection detected.", "sql")
511 return self.error
512
513 # Parse and validate WHERE clause (checks column names, not values)
514 try:
515 validated_where, _ = self._parse_and_validate_where(where)
516 except RuntimeError:
517 self.disp.log_error("Injection detected in WHERE clause.", "sql")
518 return self.error
519 if self._redis_cacher:
520 self.disp.log_debug("Cacher instance is defined, calling.")
522 table=table,
523 column=column,
524 where=validated_where,
525 beautify=beautify,
526 fetcher=sql_function,
527 error_token=self.error
528 )
529 else:
530 self.disp.log_debug(
531 "No cacher instance defined, calling sql boilerplate directly."
532 )
533 resp = sql_function(table, column, validated_where, beautify)
534 return resp
535
536 def get_table_size(self, table: str, column: Union[str, List[str]], where: Union[str, List[str]] = "") -> int:
537 """Return the row count for a table.
538
539 Args:
540 table (str): Name of the table to count rows in.
541 column (Union[str, List[str]]): Column expression passed to ``COUNT(...)``.
542 where (Union[str, List[str]]): Optional WHERE clause content; string or list joined by ``AND``.
543
544 Returns:
545 int: Number of rows on success, or ``GET_TABLE_SIZE_ERROR`` on failure.
546 """
547 sql_function: Callable = self._sql_query_boilerplate.get_table_size
548 resp: int = self.error
549 # Check table/column names before caching
550 if self.sql_injection.check_if_injections_in_strings([table, column]) is True:
551 self.disp.log_error("Injection detected.", "sql")
552 return GET_TABLE_SIZE_ERROR
553
554 # Parse and validate WHERE clause (checks column names, not values)
555 try:
556 validated_where, _ = self._parse_and_validate_where(where)
557 except RuntimeError:
558 self.disp.log_error("Injection detected in WHERE clause.", "sql")
559 return GET_TABLE_SIZE_ERROR
560 if self._redis_cacher:
561 self.disp.log_debug("Cacher instance is defined, calling.")
562 resp = self._redis_cacher.get_table_size(
563 table=table,
564 column=column,
565 where=validated_where,
566 fetcher=sql_function
567 )
568 else:
569 self.disp.log_debug(
570 "No cacher instance defined, calling sql boilerplate directly."
571 )
572 resp = sql_function(table, column, validated_where)
573 return resp
574
575 def update_data_in_table(self, table: str, data: Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]], column: List[str], where: Union[str, List[str]] = "") -> int:
576 """Update rows in a table.
577
578 Args:
579 table (str): Name of the table to update.
580 data (Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]]): Values aligned to ``column`` order; single row or list of rows.
581 column (List[str]): Column names to update.
582 where (Union[str, List[str]]): Optional WHERE clause content; string or list joined by ``AND``.
583
584 Returns:
585 int: ``self.success`` on success, or ``self.error`` on failure.
586 """
587 sql_function: Callable = self._sql_query_boilerplate.update_data_in_table
588 resp: int = self.error
589 if column is None:
590 column = ""
591
592 # Check table/column names before caching
593 check_items = [table]
594 if isinstance(column, list):
595 check_items.extend([str(c) for c in column])
596 else:
597 check_items.append(str(column))
598 if self.sql_injection.check_if_injections_in_strings(check_items):
599 self.disp.log_error("Injection detected.", "sql")
600 return self.error
601
602 # Parse and validate WHERE clause (checks column names, not values)
603 try:
604 validated_where, _ = self._parse_and_validate_where(where)
605 except RuntimeError:
606 self.disp.log_error("Injection detected in WHERE clause.", "sql")
607 return self.error
608 if self._redis_cacher:
609 self.disp.log_debug("Cacher instance is defined, calling.")
611 table=table,
612 data=data,
613 column=column,
614 where=validated_where,
615 writer=sql_function
616 )
617 else:
618 self.disp.log_debug(
619 "No cacher instance defined, calling sql boilerplate directly."
620 )
621 resp = sql_function(table, data, column, validated_where)
622 return resp
623
624 def insert_or_update_data_into_table(self, table: str, data: Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]], columns: Union[List[str], None] = None) -> int:
625 """Insert or update rows using the first column as key.
626
627 Args:
628 table (str): Table name.
629 data (Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]]): Single row or list of rows to upsert.
630 columns (Union[List[str], None], optional): Column names for ``data``; when None, infer columns from the table.
631
632 Returns:
633 int: ``self.success`` on success, or ``self.error`` on failure.
634 """
635 sql_function: Callable = self._sql_query_boilerplate.insert_or_update_data_into_table
636 resp: int = self.error
637 check_list = [table]
638 if columns:
639 check_list.extend(columns)
640 if self.sql_injection.check_if_injections_in_strings(check_list):
641 self.disp.log_error("SQL Injection detected.", "sql")
642 return self.error
643 if self._redis_cacher:
644 self.disp.log_debug("Cacher instance is defined, calling.")
646 table=table,
647 data=data,
648 columns=columns,
649 writer=sql_function
650 )
651 else:
652 self.disp.log_debug(
653 "No cacher instance defined, calling sql boilerplate directly."
654 )
655 resp = sql_function(table, data, columns)
656 return resp
657
658 def insert_or_update_trigger(self, trigger_name: str, table_name: str, timing_event: str, body: str) -> int:
659 """Insert (create) or update an SQL trigger into a MySQL or MariaDB database.
660
661 Args:
662 trigger_name (str): The name of the trigger to create.
663 table_name (str): The name of the table the trigger is being applied to.
664 timing_event (str): The rule when the event is to be triggered. e.g., 'BEFORE INSERT'.
665 body (str): The full SQL CREATE TRIGGER statement.
666
667 Returns:
668 int: ``self.success`` on success, or ``self.error`` on failure.
669 """
670 sql_function: Callable = self._sql_query_boilerplate.insert_or_update_trigger
671 resp: int = self.error
672 if self.sql_injection.check_if_injections_in_strings([trigger_name, table_name]):
673 self.disp.log_error("SQL injection detected.")
674 return self.error
675
676 if self.sql_injection.check_if_symbol_and_logic_gate_injection(timing_event):
677 self.disp.log_error("SQL injection detected")
678 return self.error
679 if self._redis_cacher:
680 self.disp.log_debug("Cacher instance is defined, calling.")
682 trigger_name=trigger_name,
683 table_name=table_name,
684 timing_event=timing_event,
685 body=body,
686 writer=sql_function
687 )
688 else:
689 self.disp.log_debug(
690 "No cacher instance defined, calling sql boilerplate directly."
691 )
692 resp = sql_function(trigger_name, table_name, timing_event, body)
693 return resp
694
695 def remove_data_from_table(self, table: str, where: Union[str, List[str]] = "") -> int:
696 """Delete rows from a table.
697
698 Args:
699 table (str): Table name.
700 where (Union[str, List[str]]): Optional WHERE clause to limit deletions.
701
702 Returns:
703 int: ``self.success`` on success, or ``self.error`` on failure.
704 """
705 sql_function: Callable = self._sql_query_boilerplate.remove_data_from_table
706 resp: int = self.error
707 # Check table name before caching
708 if self.sql_injection.check_if_sql_injection(table) is True:
709 self.disp.log_error("Injection detected.", "sql")
710 return self.error
711
712 # Parse and validate WHERE clause (checks column names, not values)
713 try:
714 validated_where, _ = self._parse_and_validate_where(where)
715 except RuntimeError:
716 self.disp.log_error("Injection detected in WHERE clause.", "sql")
717 return self.error
718 if self._redis_cacher:
719 self.disp.log_debug("Cacher instance is defined, calling.")
721 table=table,
722 where=validated_where,
723 writer=sql_function
724 )
725 else:
726 self.disp.log_debug(
727 "No cacher instance defined, calling sql boilerplate directly."
728 )
729 resp = sql_function(table, validated_where)
730 return resp
731
732 def remove_table(self, table: str) -> int:
733 """Drop a table from the MySQL database.
734
735 Args:
736 table (str): Name of the table to drop.
737
738 Returns:
739 int: ``self.success`` on success, or ``self.error`` on failure.
740
741 Notes:
742 - Performs SQL injection detection on the table name.
743 - Uses ``DROP TABLE IF EXISTS`` to avoid errors when the table is missing.
744 """
745 sql_function: Callable = self._sql_query_boilerplate.remove_table
746 resp: int = self.error
747 if self.sql_injection.check_if_injections_in_strings([table]):
748 self.disp.log_error("Injection detected in table name.")
749 return self.error
750 if self._redis_cacher:
751 self.disp.log_debug("Cacher instance is defined, calling.")
752 resp = self._redis_cacher.remove_table(
753 table=table,
754 writer=sql_function
755 )
756 else:
757 self.disp.log_debug(
758 "No cacher instance defined, calling sql boilerplate directly."
759 )
760 resp = sql_function(table)
761 return resp
762
763 def remove_trigger(self, trigger_name: str) -> int:
764 """Drop/Remove an existing SQL trigger if it exists.
765
766 Args:
767 trigger_name (str): Name of the trigger to drop.
768
769 Returns:
770 int: ``self.success`` on success, or ``self.error`` on error.
771 """
772 sql_function: Callable = self._sql_query_boilerplate.remove_trigger
773 resp: int = self.error
774 if not trigger_name:
775 self.disp.log_error("Trigger name cannot be empty.")
776 return self.error
777 if self.sql_injection.check_if_injections_in_strings([trigger_name]):
778 self.disp.log_error("SQL Injection detected in trigger name.")
779 return self.error
780 if self._redis_cacher:
781 self.disp.log_debug("Cacher instance is defined, calling.")
782 resp = self._redis_cacher.remove_trigger(
783 trigger_name=trigger_name,
784 writer=sql_function
785 )
786 else:
787 self.disp.log_debug(
788 "No cacher instance defined, calling sql boilerplate directly."
789 )
790 resp = sql_function(trigger_name)
791 return resp
Union[int, str] get_trigger(self, str trigger_name, Optional[str] db_name=None)
int insert_or_update_trigger(self, str trigger_name, str table_name, str timing_event, str body)
int insert_trigger(self, str trigger_name, str table_name, str timing_event, str body)
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, Union[List[str], None] columns=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="")
None update_redis_cacher(self, Optional[SQLRedisCacheRebinds] redis_cacher=None)
None __init__(self, SQLQueryBoilerplates sql_query_boilerplates, Optional[SQLRedisCacheRebinds] redis_cacher=None, int success=0, int error=84, bool debug=False)
Tuple[Union[str, List[str]], List[Union[str, int, float, None]]] _parse_and_validate_where(self, Union[str, List[str]] where)
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)
Union[str, None, int, float] _normalize_cell(self, object cell)
int create_table(self, str table, List[Tuple[str, str]] columns)
Union[int, List[str]] get_trigger_names(self, Optional[str] db_name=None)
int insert_data_into_table(self, str table, Union[List[List[Union[str, None, int, float]]], List[Union[str, None, int, float]]] data, Union[List[str], None] column=None)
int get_table_size(self, str table, Union[str, List[str]] column, Union[str, List[str]] where="")
int remove_data_from_table(self, str table, Union[str, List[str]] where="")