import random import re import sqlite_static_helper def _quote_str(text: str) -> str: """O'Reilly -> 'O''Reilly'""" return "'" + text.replace("'", "''") + "'" _TOKEN_RE = re.compile( r""" (?P--[^\n]*) | (?P/\*.*?\*/) | (?P'(?:[^']|'')*') | (?P"(?:[^"]|"")*") | (?P\[[^\]]*\]) | (?P`(?:[^`]|``)*`) | (?P[xX]'[0-9a-fA-F]*') | (?P\b\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b) | (?P[A-Za-z_][A-Za-z0-9_]*) | (?P[<>!=]=|<>|\|\||::|->>?|[+\-*/%<>=&|^~,.;()@]) | (?P\s+) | (?P.) """, re.VERBOSE | re.DOTALL, ) def _tokenize(s: str) -> list[tuple[str | None, str]]: """[("line_comment", "-- This is a comment!"), ("number", 0), ...]""" return [(m.lastgroup, m.group()) for m in _TOKEN_RE.finditer(s)] _UNSAFE_KINDS = { "line_comment", "block_comment", "string", "dquoted", "bracket", "backtick", "blob" } def _sub_in_safe(s: str, pattern: re.Pattern, repl, max_subs: int = 1) -> str: """Run `pattern.sub(repl, …)` only outside strings/comments/blob literals.""" if max_subs <= 0: return s parts: list[str] = [] done = 0 for kind, text in _tokenize(s): if done >= max_subs or kind in _UNSAFE_KINDS: parts.append(text) continue new_text, n = pattern.subn(repl, text, count=max_subs - done) parts.append(new_text) done += n return ''.join(parts) _NUM_RE = re.compile(r'\b\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b') def mut_replace_num_with_edge_int(s: str) -> str: """Replace the left-most number with a random edge-case integer""" return _sub_in_safe( s, _NUM_RE, lambda m: str(random.choice(sqlite_static_helper.EDGE_INTS)), 1) def mut_replace_num_with_random_int(s: str) -> str: """Replace the left-most number with a random integer""" return _sub_in_safe(s, _NUM_RE, lambda m: str(random.randint(-(2**63), 2**63 - 1)), 1) def mut_replace_num_with_edge_float(s: str) -> str: """Replace the left-most number with a random edge-case floating-point number""" return _sub_in_safe( s, _NUM_RE, lambda m: repr(random.choice(sqlite_static_helper.EDGE_FLOATS)), 1) def mut_replace_num_with_edge_string(s: str) -> str: """Replace the left-most number with a random edge-case string""" return _sub_in_safe( s, _NUM_RE, lambda m: _quote_str(random.choice(sqlite_static_helper.EDGE_STRINGS)), 1) def mut_replace_num_with_random_hex(s: str) -> str: """Replace the left-most number with a random hex blob""" n = random.randint(0, 16) val = "x'" + ''.join(random.choices('0123456789abcdef', k=n * 2)) + "'" return _sub_in_safe(s, _NUM_RE, lambda m: val, 1) def mut_replace_num_with_null(s: str) -> str: """Replace the left-most number with NULL""" return _sub_in_safe(s, _NUM_RE, lambda m: 'NULL', 1) def mut_wrap_num_in_cast(s: str) -> str: """Cast the left-most number to a different data type""" target = random.choice(sqlite_static_helper.TYPES) return _sub_in_safe(s, _NUM_RE, lambda m: f'CAST({m.group(0)} AS {target})', 1) def _wrap_value(s: str, templates: list[str]) -> str: tpl = random.choice(templates) return _sub_in_safe(s, _NUM_RE, lambda m: tpl.replace('{v}', m.group(0)), 1) def mut_wrap_num_in_datetime_func(s: str) -> str: """Wrap the left-most number in a date/time function""" return _wrap_value(s, sqlite_static_helper.DATETIME_FUNCS) def mut_wrap_num_in_core_func(s: str) -> str: """Wrap the left-most number in core function""" return _wrap_value(s, sqlite_static_helper.CORE_FUNCS) def mut_wrap_num_in_math_func(s: str) -> str: """Wrap the left-most number in a math function""" return _wrap_value(s, sqlite_static_helper.MATH_FUNCS) def mut_wrap_num_in_json_func(s: str) -> str: """Wrap the left-most number in a JSON function""" return _wrap_value(s, sqlite_static_helper.JSON_VALUE_FUNCS)