Skip to content

replace str.replace() with token-based parameter substitution#6

Merged
rbs333 merged 2 commits intomainfrom
fix/replace-str-replace-with-token-based-params
Feb 6, 2026
Merged

replace str.replace() with token-based parameter substitution#6
rbs333 merged 2 commits intomainfrom
fix/replace-str-replace-with-token-based-params

Conversation

@nkanu17
Copy link
Copy Markdown
Contributor

@nkanu17 nkanu17 commented Feb 6, 2026

Fix for #5

Fix: Token-Based Parameter Substitution

Summary

Replaces naive str.replace() parameter substitution with token-based regex approach to fix two critical bugs discovered through TDD investigation.

Bugs Fixed

1. Quote Escaping Bug (CRITICAL)

Single quotes in parameters weren't escaped, breaking SQL parsing:

# Before: "WHERE name = 'O'Brien'"  ❌ Breaks parsing
# After:  "WHERE name = 'O''Brien'" ✅ Properly escaped

2. Partial Matching Bug

:id incorrectly matched inside :product_id:

# Before: str.replace(':id', '123') → "product_123" ❌
# After:  Token-based approach → "product_id = 456" ✅

Solution

Token-based approach using regex (:[a-zA-Z_][a-zA-Z0-9_]*):

  • Splits SQL on parameter patterns
  • Treats each :identifier as complete token
  • Prevents partial matching
  • Properly escapes quotes using SQL standard (''')

Why Token-Based?

Aspect Token-based sqlglot str.replace()
Lines of code ~30 ~60 ~3
Dependencies stdlib re sqlglot None
Performance Fast Slow Fast
Fixes both bugs

Decision: Best balance of simplicity, correctness, and performance.

Changes

Modified

  • sql_redis/executor.py - Replaced implementation with token-based approach, added comprehensive docs

Added

  • tests/test_parameter_substitution.py - 12 TDD tests validating bug fixes

Implementation Details

Regex Pattern: (:[a-zA-Z_][a-zA-Z0-9_]*)

  • Ensures :id and :product_id are separate tokens
  • Only matches valid identifiers

Quote Escaping: SQL standard (''')

escaped = value.replace("'", "''")
result.append(f"'{escaped}'")

Type Handling:

  • int/float → string
  • str → quoted with escaping
  • bytes → placeholder kept (for vectors)

Known Limitations

Colons in string literals: Theoretical issue with SQL like WHERE x = 'test:value'

Why it doesn't matter:

  • Users pass values via params, not hardcoded SQL
  • No real-world use cases found
  • The TDD tests pass

- Fix quote escaping bug (single quotes now properly escaped)
- Fix partial matching bug (:id no longer matches inside :product_id)
- Remove sqlglot dependency, use stdlib re module
- Add tests
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes issue #5 by replacing naive str.replace() parameter substitution with a token-based regex approach that prevents two critical bugs:

  1. Partial matching bug: The old code would incorrectly replace :id inside :product_id, resulting in malformed queries
  2. Quote escaping bug: Single quotes in parameter values (e.g., O'Brien) were not properly escaped for SQL parsing, causing syntax errors

Changes:

  • Added token-based _substitute_params() method using regex to split SQL on parameter patterns, preventing partial matches
  • Implemented SQL-standard quote escaping (single quote → double single quote) for string values
  • Added comprehensive test suite covering partial matching, quote escaping, and edge cases

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
sql_redis/executor.py Replaced naive str.replace() with token-based _substitute_params() method that uses regex splitting to prevent partial matches and properly escapes quotes
tests/test_parameter_substitution.py Added comprehensive test suite with 14 tests covering partial matching bugs, quote escaping, and edge cases like empty strings and special characters

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/test_parameter_substitution.py Outdated
index_name = "param_test"
try:
redis_client.execute_command("FT.DROPINDEX", index_name, "DD")
except redis.ResponseError:
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
except redis.ResponseError:
except redis.ResponseError:
# Ignore errors when the index does not exist; we recreate it below for tests.

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@rbs333 rbs333 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

@rbs333 rbs333 merged commit 971c539 into main Feb 6, 2026
8 checks passed
@rbs333 rbs333 deleted the fix/replace-str-replace-with-token-based-params branch February 6, 2026 15:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants