pg-idx-manager 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,67 @@
1
+ Metadata-Version: 2.4
2
+ Name: pg_idx_manager
3
+ Version: 0.1.0
4
+ Summary: A lightweight, framework-agnostic developer linter and cleanup tool for PostgreSQL indexes.
5
+ Author-email: Pierpaolo <tua_email@example.com>
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Topic :: Database :: Database Engines/Servers
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: psycopg2-binary>=2.9.0
14
+
15
+ # PG Index Manager & Janitor ๐Ÿš€
16
+
17
+ A lightweight, framework-agnostic Python library designed for developers to audit database queries, surface runtime performance bottlenecks, and safely maintain PostgreSQL indexes.
18
+
19
+ Unlike heavy Application Performance Monitoring (APM) tools that require invasive database extensions (like HypoPG) or expensive SaaS subscriptions, this library operates entirely inside your application or via a clean CLI using native PostgreSQL capabilities.
20
+
21
+ ## The Problem It Solves: "ORM Blindness"
22
+ Modern Object-Relational Mappers (ORMs) like Django or SQLAlchemy maximize developer productivity but hide the underlying SQL execution plan. A seemingly innocent line of Python code can silently trigger a **Sequential Scan (Full Table Scan)** across millions of rows, saturating server CPU and disk I/O.
23
+
24
+ Since Large Language Models (AI) cannot inspect your production database cardinality, table sizes, or live index catalogs, they cannot reliably predict query performance. This library bridges that gap by running live `EXPLAIN ANALYZE` inspections directly on the database engine.
25
+
26
+ ## Key Features
27
+
28
+ 1. **Agnostic Performance Auditing**: Intercepts raw SQL queries, recursively parses the native PostgreSQL execution tree, and catches performance anomalies (**Sequential Scans**) with exact millisecond runtimes.
29
+ 2. **Safe Janitor Mode (Interactive CLI)**: Scans PostgreSQL system catalogs (`pg_stat_user_indexes`) to discover dead, unused indexes that are slowing down your `INSERT`/`UPDATE` mutations. It allows you to drop them interactively using `DROP INDEX CONCURRENTLY` without locking tables or risking production application downtime.
30
+ 3. **Zero-Dependency Architecture**: Does not require root or superuser privileges on the database server. If you can connect to the database, you can run this library.
31
+
32
+ ## Architectural Architecture: Where does it live?
33
+ Because the core engine requires only a raw query string and a standard database connection, it is completely independent of your web framework. You can integrate it at the lowest layer of your infrastructure:
34
+
35
+ * **At the Driver Level (`psycopg2`)**: By extending the native database cursor, you can automatically audit **100% of your application queries** (Django, FastAPI, Flask, or raw SQL) before they hit the wire.
36
+ * **At the ORM Engine Level**: Easily hooks into global ORM events (e.g., SQLAlchemy's `before_cursor_execute`) to catch hidden query costs implicitly across all Services and Repositories with zero business-code modification.
37
+
38
+ ---
39
+
40
+ ## Getting Started & Testing Locally ๐Ÿงช
41
+
42
+ This repository includes a fully containerized testing environment to observe performance optimization in real-time.
43
+
44
+ ### 1. Spin up the isolated test database
45
+ ```bash
46
+ docker compose up -d
47
+ ```
48
+
49
+ ### 2. Install the library locally in editable mode
50
+ ```bash
51
+ pip install -e .
52
+ ```
53
+
54
+ ### 3. Run the Core Agnostic Test
55
+ This script populates the database with **10,000 mock records**, executes an unindexed query, catches the Sequential Scan risk, and launches the persistent interactive CLI:
56
+ ```bash
57
+ python tests/run_test.py
58
+ ```
59
+
60
+ ### 4. Run the ORM Integration Test
61
+ Observe how the library seamlessly intercepts real-time query metrics generated implicitly by SQLAlchemy ORM models:
62
+ ```bash
63
+ python tests/test_orm.py
64
+ ```
65
+
66
+ ## License
67
+ MIT License. Free to use and extend.
@@ -0,0 +1,53 @@
1
+ # PG Index Manager & Janitor ๐Ÿš€
2
+
3
+ A lightweight, framework-agnostic Python library designed for developers to audit database queries, surface runtime performance bottlenecks, and safely maintain PostgreSQL indexes.
4
+
5
+ Unlike heavy Application Performance Monitoring (APM) tools that require invasive database extensions (like HypoPG) or expensive SaaS subscriptions, this library operates entirely inside your application or via a clean CLI using native PostgreSQL capabilities.
6
+
7
+ ## The Problem It Solves: "ORM Blindness"
8
+ Modern Object-Relational Mappers (ORMs) like Django or SQLAlchemy maximize developer productivity but hide the underlying SQL execution plan. A seemingly innocent line of Python code can silently trigger a **Sequential Scan (Full Table Scan)** across millions of rows, saturating server CPU and disk I/O.
9
+
10
+ Since Large Language Models (AI) cannot inspect your production database cardinality, table sizes, or live index catalogs, they cannot reliably predict query performance. This library bridges that gap by running live `EXPLAIN ANALYZE` inspections directly on the database engine.
11
+
12
+ ## Key Features
13
+
14
+ 1. **Agnostic Performance Auditing**: Intercepts raw SQL queries, recursively parses the native PostgreSQL execution tree, and catches performance anomalies (**Sequential Scans**) with exact millisecond runtimes.
15
+ 2. **Safe Janitor Mode (Interactive CLI)**: Scans PostgreSQL system catalogs (`pg_stat_user_indexes`) to discover dead, unused indexes that are slowing down your `INSERT`/`UPDATE` mutations. It allows you to drop them interactively using `DROP INDEX CONCURRENTLY` without locking tables or risking production application downtime.
16
+ 3. **Zero-Dependency Architecture**: Does not require root or superuser privileges on the database server. If you can connect to the database, you can run this library.
17
+
18
+ ## Architectural Architecture: Where does it live?
19
+ Because the core engine requires only a raw query string and a standard database connection, it is completely independent of your web framework. You can integrate it at the lowest layer of your infrastructure:
20
+
21
+ * **At the Driver Level (`psycopg2`)**: By extending the native database cursor, you can automatically audit **100% of your application queries** (Django, FastAPI, Flask, or raw SQL) before they hit the wire.
22
+ * **At the ORM Engine Level**: Easily hooks into global ORM events (e.g., SQLAlchemy's `before_cursor_execute`) to catch hidden query costs implicitly across all Services and Repositories with zero business-code modification.
23
+
24
+ ---
25
+
26
+ ## Getting Started & Testing Locally ๐Ÿงช
27
+
28
+ This repository includes a fully containerized testing environment to observe performance optimization in real-time.
29
+
30
+ ### 1. Spin up the isolated test database
31
+ ```bash
32
+ docker compose up -d
33
+ ```
34
+
35
+ ### 2. Install the library locally in editable mode
36
+ ```bash
37
+ pip install -e .
38
+ ```
39
+
40
+ ### 3. Run the Core Agnostic Test
41
+ This script populates the database with **10,000 mock records**, executes an unindexed query, catches the Sequential Scan risk, and launches the persistent interactive CLI:
42
+ ```bash
43
+ python tests/run_test.py
44
+ ```
45
+
46
+ ### 4. Run the ORM Integration Test
47
+ Observe how the library seamlessly intercepts real-time query metrics generated implicitly by SQLAlchemy ORM models:
48
+ ```bash
49
+ python tests/test_orm.py
50
+ ```
51
+
52
+ ## License
53
+ MIT License. Free to use and extend.
@@ -0,0 +1,5 @@
1
+ from .core import IndexManagerCore
2
+ from .cli import run_cli
3
+
4
+ __all__ = ["IndexManagerCore", "run_cli"]
5
+
@@ -0,0 +1,103 @@
1
+ import sys
2
+ from .core import IndexManagerCore
3
+
4
+ def is_safe_query(query: str) -> bool:
5
+ """Sanity check to ensure the tool only processes read-only SELECT queries."""
6
+ clean_query = query.strip().upper()
7
+ if len(clean_query) < 6:
8
+ return True
9
+ dangerous_keywords = ["DROP TABLE", "DROP DATABASE", "DELETE FROM", "INSERT INTO", "UPDATE ", "TRUNCATE "]
10
+ for keyword in dangerous_keywords:
11
+ if keyword in clean_query:
12
+ return False
13
+ return True
14
+
15
+ def run_cli(connection):
16
+ """Launches a persistent, continuous loop for interactive DB optimization."""
17
+ manager = IndexManagerCore(connection, min_table_rows=0)
18
+
19
+ try:
20
+ while True:
21
+ print("\n" + "="*40)
22
+ print("๐Ÿš€ PERSISTENT PG INDEX MANAGER CLI")
23
+ print("="*40)
24
+ print("1. Audit single SQL query efficiency")
25
+ print("2. Scan and clean up dead indexes (Janitor Mode)")
26
+ print("Type 'quit' at any prompt to exit.")
27
+ print("="*40)
28
+
29
+ choice = input("\nSelect option (1/2 or 'quit'): ").strip().lower()
30
+
31
+ if choice == "quit":
32
+ print("\n๐Ÿ‘‹ Exiting Index Manager. Keep your database clean!")
33
+ break
34
+
35
+ elif choice == "1":
36
+ while True:
37
+ query = input("\nPaste SQL query to analyze (type 'back' for main menu, 'quit' to exit):\n> ").strip()
38
+
39
+ if query.lower() == 'quit':
40
+ print("\n๐Ÿ‘‹ Exiting Index Manager. Keep your database clean!")
41
+ return
42
+ if query.lower() == 'back':
43
+ break
44
+ if not query:
45
+ continue
46
+ if not is_safe_query(query):
47
+ print("\nโŒ SECURITY ERROR: Only read-only SELECT queries are allowed for auditing.")
48
+ continue
49
+
50
+ try:
51
+ anomalies, execution_time = manager.analyze_query(query)
52
+
53
+ if not anomalies:
54
+ print("\n" + "="*40)
55
+ print("โœ… SUCCESS: The query is properly indexed and optimized.")
56
+ print(f" Execution Time: {execution_time} ms (Index Scan active)")
57
+ print("="*40)
58
+ continue
59
+
60
+ for am in anomalies:
61
+ print("\n" + "="*40)
62
+ print(f"โš ๏ธ ANOMALY DETECTED: Seq Scan on table '{am['table']}'")
63
+ print(f" Execution Time: {am.get('execution_time', 0.0)} ms")
64
+
65
+ q_filter = am.get('filter')
66
+ if q_filter:
67
+ print(f" Offending filter clause: {q_filter}")
68
+ else:
69
+ print(" Offending filter clause: [Full Table Scan - No filter constraint applied]")
70
+ print("="*40)
71
+
72
+ except Exception as e:
73
+ connection.rollback()
74
+ print(f"\nโŒ SQL SYNTAX OR DATABASE ERROR: {e}")
75
+ print("Please verify your table names, column names, or SQL syntax.")
76
+ continue
77
+
78
+ elif choice == "2":
79
+ print("\n๐Ÿ”Ž Scanning relational schemas for unused metadata indexes...")
80
+ unused = manager.get_unused_indexes()
81
+ if not unused:
82
+ print("โœ… Perfect! No dead indexes found in the database catalog.")
83
+ continue
84
+
85
+ print(f"โš ๏ธ Identified {len(unused)} unused indexes slowing down write operations:")
86
+ for idx in unused:
87
+ print(f"- '{idx['index']}' on table '{idx['table']}'")
88
+ confirm = input(f" Drop index asynchronously (CONCURRENTLY)? [s/N]: ").lower().strip()
89
+ if confirm == 's':
90
+ try:
91
+ print(f" Removing index metadata in background...")
92
+ manager.drop_index_safely(idx['index'], idx['schema'])
93
+ print(f" ๐Ÿš€ Index '{idx['index']}' dropped successfully!")
94
+ except Exception as e:
95
+ print(f" โŒ Runtime execution error: {e}")
96
+ else:
97
+ print(" Skipped.")
98
+ else:
99
+ print("\nโŒ Invalid choice. Please enter 1, 2, or 'quit'.")
100
+
101
+ except KeyboardInterrupt:
102
+ print("\n\n๐Ÿ›‘ [Control+C] Interrupted by user. Exiting cleanly. Goodbye!")
103
+ sys.exit(0)
@@ -0,0 +1,107 @@
1
+ import logging
2
+
3
+ class IndexManagerCore:
4
+ """
5
+ Core engine handling PostgreSQL index auditing and safe background cleanup.
6
+ Does not require superuser privileges or invasive external extensions.
7
+ """
8
+ def __init__(self, connection, min_table_rows=1000):
9
+ self.conn = connection
10
+ self.min_table_rows = min_table_rows
11
+
12
+ def _get_table_rows(self, cursor, table_name):
13
+ """Queries PostgreSQL system catalogs to fetch live tuple statistics."""
14
+ try:
15
+ cursor.execute(
16
+ "SELECT n_live_tup FROM pg_stat_user_tables WHERE relname = %s",
17
+ (table_name,)
18
+ )
19
+ res = cursor.fetchone()
20
+ # res รจ una tupla tipo (10000,). Estraiamo il primo elemento res[0]
21
+ return res[0] if res else 0
22
+ except Exception:
23
+ return 0
24
+
25
+
26
+ def parse_explain_plan(self, node, anomalies=None):
27
+ """Recursively traverses the EXPLAIN JSON tree to detect Sequential Scans."""
28
+ if anomalies is None:
29
+ anomalies = []
30
+
31
+ if node.get("Node Type") == "Seq Scan":
32
+ anomalies.append({
33
+ "table": node.get("Relation Name"),
34
+ "filter": node.get("Filter")
35
+ })
36
+
37
+ if "Plans" in node:
38
+ for sub_node in node["Plans"]:
39
+ self.parse_explain_plan(sub_node, anomalies)
40
+
41
+ return anomalies
42
+
43
+ def analyze_query(self, query):
44
+ """
45
+ Executes an EXPLAIN (ANALYZE, FORMAT JSON) on the target query.
46
+ Safely unpacks the nested driver structure without bracket syntax bugs.
47
+ """
48
+ anomalies_detected = []
49
+ with self.conn.cursor() as cursor:
50
+ cursor.execute(f"EXPLAIN (ANALYZE, FORMAT JSON) {query}")
51
+ raw_result = cursor.fetchone()
52
+
53
+ # Step 1: Extract the list from the psycopg2 tuple
54
+ if isinstance(raw_result, tuple) and len(raw_result) > 0:
55
+ raw_result = raw_result[0]
56
+
57
+ # Step 2: Extract the internal dictionary from the list
58
+ if isinstance(raw_result, list) and len(raw_result) > 0:
59
+ raw_result = raw_result[0]
60
+
61
+ # Final validation: check if we successfully unpacked into a dictionary
62
+ if not isinstance(raw_result, dict):
63
+ raise ValueError("Failed to parse PostgreSQL JSON plan into a dictionary configuration.")
64
+
65
+ plan = raw_result.get("Plan")
66
+ execution_time_ms = raw_result.get("Execution Time", 0.0)
67
+
68
+ if not plan:
69
+ raise ValueError("The 'Plan' root node is missing from the database engine response.")
70
+
71
+ raw_anomalies = self.parse_explain_plan(plan)
72
+
73
+ for anomaly in raw_anomalies:
74
+ table = anomaly["table"]
75
+ rows = self._get_table_rows(cursor, table)
76
+
77
+ if rows >= self.min_table_rows:
78
+ anomaly["table_rows"] = rows
79
+ anomaly["execution_time"] = execution_time_ms
80
+ anomalies_detected.append(anomaly)
81
+
82
+ return anomalies_detected, execution_time_ms
83
+
84
+
85
+
86
+ def get_unused_indexes(self):
87
+ """Scans pg_stat_user_indexes for dead indexes (idx_scan = 0)."""
88
+ sql = """
89
+ SELECT schemaname, relname AS table_name, indexrelname AS index_name
90
+ FROM pg_stat_user_indexes
91
+ JOIN pg_index ON pg_stat_user_indexes.indexrelid = pg_index.indexrelid
92
+ WHERE idx_scan = 0 AND indisunique = FALSE
93
+ ORDER BY relname ASC;
94
+ """
95
+ unused = []
96
+ with self.conn.cursor() as cursor:
97
+ cursor.execute(sql)
98
+ for row in cursor.fetchall():
99
+ unused.append({"schema": row[0], "table": row[1], "index": row[2]})
100
+ return unused
101
+
102
+ def drop_index_safely(self, index_name, schema="public"):
103
+ """Drops an index asynchronously without acquiring exclusive locks."""
104
+ sql = f"DROP INDEX CONCURRENTLY {schema}.{index_name};"
105
+ with self.conn.cursor() as cursor:
106
+ cursor.execute(sql)
107
+ self.conn.commit()
@@ -0,0 +1,67 @@
1
+ Metadata-Version: 2.4
2
+ Name: pg_idx_manager
3
+ Version: 0.1.0
4
+ Summary: A lightweight, framework-agnostic developer linter and cleanup tool for PostgreSQL indexes.
5
+ Author-email: Pierpaolo <tua_email@example.com>
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Topic :: Database :: Database Engines/Servers
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: psycopg2-binary>=2.9.0
14
+
15
+ # PG Index Manager & Janitor ๐Ÿš€
16
+
17
+ A lightweight, framework-agnostic Python library designed for developers to audit database queries, surface runtime performance bottlenecks, and safely maintain PostgreSQL indexes.
18
+
19
+ Unlike heavy Application Performance Monitoring (APM) tools that require invasive database extensions (like HypoPG) or expensive SaaS subscriptions, this library operates entirely inside your application or via a clean CLI using native PostgreSQL capabilities.
20
+
21
+ ## The Problem It Solves: "ORM Blindness"
22
+ Modern Object-Relational Mappers (ORMs) like Django or SQLAlchemy maximize developer productivity but hide the underlying SQL execution plan. A seemingly innocent line of Python code can silently trigger a **Sequential Scan (Full Table Scan)** across millions of rows, saturating server CPU and disk I/O.
23
+
24
+ Since Large Language Models (AI) cannot inspect your production database cardinality, table sizes, or live index catalogs, they cannot reliably predict query performance. This library bridges that gap by running live `EXPLAIN ANALYZE` inspections directly on the database engine.
25
+
26
+ ## Key Features
27
+
28
+ 1. **Agnostic Performance Auditing**: Intercepts raw SQL queries, recursively parses the native PostgreSQL execution tree, and catches performance anomalies (**Sequential Scans**) with exact millisecond runtimes.
29
+ 2. **Safe Janitor Mode (Interactive CLI)**: Scans PostgreSQL system catalogs (`pg_stat_user_indexes`) to discover dead, unused indexes that are slowing down your `INSERT`/`UPDATE` mutations. It allows you to drop them interactively using `DROP INDEX CONCURRENTLY` without locking tables or risking production application downtime.
30
+ 3. **Zero-Dependency Architecture**: Does not require root or superuser privileges on the database server. If you can connect to the database, you can run this library.
31
+
32
+ ## Architectural Architecture: Where does it live?
33
+ Because the core engine requires only a raw query string and a standard database connection, it is completely independent of your web framework. You can integrate it at the lowest layer of your infrastructure:
34
+
35
+ * **At the Driver Level (`psycopg2`)**: By extending the native database cursor, you can automatically audit **100% of your application queries** (Django, FastAPI, Flask, or raw SQL) before they hit the wire.
36
+ * **At the ORM Engine Level**: Easily hooks into global ORM events (e.g., SQLAlchemy's `before_cursor_execute`) to catch hidden query costs implicitly across all Services and Repositories with zero business-code modification.
37
+
38
+ ---
39
+
40
+ ## Getting Started & Testing Locally ๐Ÿงช
41
+
42
+ This repository includes a fully containerized testing environment to observe performance optimization in real-time.
43
+
44
+ ### 1. Spin up the isolated test database
45
+ ```bash
46
+ docker compose up -d
47
+ ```
48
+
49
+ ### 2. Install the library locally in editable mode
50
+ ```bash
51
+ pip install -e .
52
+ ```
53
+
54
+ ### 3. Run the Core Agnostic Test
55
+ This script populates the database with **10,000 mock records**, executes an unindexed query, catches the Sequential Scan risk, and launches the persistent interactive CLI:
56
+ ```bash
57
+ python tests/run_test.py
58
+ ```
59
+
60
+ ### 4. Run the ORM Integration Test
61
+ Observe how the library seamlessly intercepts real-time query metrics generated implicitly by SQLAlchemy ORM models:
62
+ ```bash
63
+ python tests/test_orm.py
64
+ ```
65
+
66
+ ## License
67
+ MIT License. Free to use and extend.
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ pg_idx_manager/__init__.py
4
+ pg_idx_manager/cli.py
5
+ pg_idx_manager/core.py
6
+ pg_idx_manager.egg-info/PKG-INFO
7
+ pg_idx_manager.egg-info/SOURCES.txt
8
+ pg_idx_manager.egg-info/dependency_links.txt
9
+ pg_idx_manager.egg-info/requires.txt
10
+ pg_idx_manager.egg-info/top_level.txt
11
+ tests/test_orm.py
@@ -0,0 +1 @@
1
+ psycopg2-binary>=2.9.0
@@ -0,0 +1 @@
1
+ pg_idx_manager
@@ -0,0 +1,24 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pg_idx_manager"
7
+ version = "0.1.0"
8
+ description = "A lightweight, framework-agnostic developer linter and cleanup tool for PostgreSQL indexes."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ authors = [{ name = "Pierpaolo", email = "tua_email@example.com" }]
12
+ classifiers = [
13
+ "Programming Language :: Python :: 3",
14
+ "License :: OSI Approved :: MIT License",
15
+ "Operating System :: OS Independent",
16
+ "Intended Audience :: Developers",
17
+ "Topic :: Database :: Database Engines/Servers",
18
+ ]
19
+ dependencies = [
20
+ "psycopg2-binary>=2.9.0"
21
+ ]
22
+
23
+ [tool.setuptools]
24
+ packages = ["pg_idx_manager"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,44 @@
1
+ from sqlalchemy import create_engine, Column, Integer, String, Numeric
2
+ from sqlalchemy.orm import declarative_base, sessionmaker
3
+ from pg_index_manager.decorators import audit_index
4
+
5
+ # 1. Database Connection Configuration
6
+ DATABASE_URL = "postgresql+psycopg2://tester:supersecretpassword@localhost:5432/testing_perf"
7
+ engine = create_engine(DATABASE_URL)
8
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
9
+ Base = declarative_base()
10
+
11
+ # 2. ORM Model Mapping
12
+ class Order(Base):
13
+ __tablename__ = "orders"
14
+ id = Column(Integer, primary_key=True, index=True)
15
+ customer_name = Column(String)
16
+ status = Column(String)
17
+ amount = Column(Numeric)
18
+
19
+ # 3. Intercepted Function
20
+ @audit_index(conn_param="raw_conn", min_rows=0)
21
+ def execute_backend_task(raw_conn, query):
22
+ with raw_conn.cursor() as cursor:
23
+ cursor.execute(query)
24
+ return cursor.fetchall()
25
+
26
+ if __name__ == "__main__":
27
+ db_session = SessionLocal()
28
+
29
+ try:
30
+ # Recuperiamo la connessione psycopg2 grezza in modo moderno
31
+ raw_connection = db_session.connection()._dbapi_connection
32
+
33
+ # Metti qui la tua query da testare
34
+ orm_query = db_session.query(Order).filter(Order.customer_name == "pierpaolo")
35
+
36
+ # Compile and bind parameters to get the raw SQL string
37
+ sql_string = str(orm_query.statement.compile(engine, compile_kwargs={"literal_binds": True}))
38
+
39
+ # Execute: the decorator handles the audit output automatically
40
+ execute_backend_task(raw_conn=raw_connection, query=sql_string)
41
+
42
+ finally:
43
+ db_session.close()
44
+