#!/usr/bin/env python3 # ============================================================================= # Minimal client-side reproduction: run the stored function and procedure via # the server-side prepared protocol (COM_STMT_EXECUTE), which triggers the # per-sub-statement audit-event multiplication. # # Example: # pip install mysql-connector-python # DB_PASSWORD=secret python3 run_repro.py --host 127.0.0.1 --user root # # Prerequisites (create the schema used by this script): # # CREATE DATABASE audit_test; # USE audit_test; # CREATE TABLE t (id INT PRIMARY KEY); # # DELIMITER // # CREATE PROCEDURE test_proc(IN p_id INT) # BEGIN # INSERT INTO t(id) VALUES (p_id); # SELECT COUNT(*) INTO @c FROM t WHERE id = p_id; # UPDATE t SET id = p_id; # DELETE FROM t; # END// # CREATE FUNCTION test_fn(p INT) RETURNS INT DETERMINISTIC # BEGIN # DECLARE r1 INT; DECLARE r2 INT; DECLARE r3 INT; # SELECT 1 INTO r1; # SELECT 2 INTO r2; # SELECT 3 INTO r3; # RETURN r1 + r2 + r3 + p; # END// # DELIMITER ; # ============================================================================= import argparse import os import mysql.connector def parse_args(): p = argparse.ArgumentParser(description="Run test_fn and test_proc via prepared protocol.") p.add_argument("--host", default=os.getenv("DB_HOST", "127.0.0.1")) p.add_argument("--port", type=int, default=int(os.getenv("DB_PORT", "3306"))) p.add_argument("--user", default=os.getenv("DB_USER", "root")) p.add_argument("--password", default=os.getenv("DB_PASSWORD", "")) p.add_argument("--database", default=os.getenv("DB_NAME", "audit_test")) return p.parse_args() def drain(cur): """Consume any result set(s) so the next execute won't hit 'Unread result found'.""" while True: try: cur.fetchall() except mysql.connector.Error: pass try: if not cur.nextset(): break except mysql.connector.Error: break args = parse_args() cnx = mysql.connector.connect( host=args.host, port=args.port, user=args.user, password=args.password, database=args.database, ) cur = cnx.cursor(prepared=True) # server-side prepared -> COM_STMT_EXECUTE cur.execute("SELECT test_fn(?)", (1,)) drain(cur) cur.execute("CALL test_proc(?)", (1,)) drain(cur) cur.close() cnx.commit() cnx.close()