Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 50 additions & 8 deletions paradedb/sqlalchemy/alembic.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from __future__ import annotations

import re

from alembic.autogenerate import comparators, renderers
from alembic.operations import Operations
from alembic.operations.ops import MigrateOperation
Expand Down Expand Up @@ -210,12 +208,56 @@ def _normalize_bm25_expression(expr: str) -> str:
normalized = "".join(expr.split())
normalized = normalized.replace('"', "")
normalized = normalized.replace("::text", "")
# Ignore schema/table qualification differences, but keep tokenizer namespaces like `pdb.simple`.
previous = None
while previous != normalized:
previous = normalized
normalized = re.sub(r"(?<![A-Za-z0-9_])(?!pdb\b)[A-Za-z_][A-Za-z0-9_]*\.", "", normalized)
return normalized
return _strip_non_pdb_qualifiers(normalized)


def _strip_non_pdb_qualifiers(expr: str) -> str:
"""Strip relation qualifiers outside SQL string literals.

Preserves tokenizer namespaces like ``pdb.simple`` and leaves quoted literal
content untouched (for example regex patterns like ``'run.*'``).
"""
out: list[str] = []
i = 0
in_single = False
while i < len(expr):
ch = expr[i]

if ch == "'":
out.append(ch)
# Escaped quote inside a string literal: ''.
if in_single and i + 1 < len(expr) and expr[i + 1] == "'":
out.append("'")
i += 2
continue
in_single = not in_single
i += 1
continue

if not in_single and (ch.isalpha() or ch == "_"):
j = i + 1
while j < len(expr) and (expr[j].isalnum() or expr[j] == "_"):
j += 1

token = expr[i:j]
if j < len(expr) and expr[j] == ".":
if token.lower() != "pdb":
# Drop relation-like qualifier prefixes, e.g. public.products.
i = j + 1
continue
out.append(token)
out.append(".")
i = j + 1
continue

out.append(token)
i = j
continue

out.append(ch)
i += 1

return "".join(out)


def _normalized_expression_list(expressions: list[str]) -> list[str]:
Expand Down
12 changes: 12 additions & 0 deletions tests/unit/test_alembic_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,3 +265,15 @@ def test_suppress_standard_bm25_ops_noop_when_no_bm25():

pdb_alembic._suppress_standard_bm25_ops(upgrade_ops, set())
assert len(upgrade_ops.ops) == 1


def test_normalize_bm25_expression_keeps_dotted_literal_content():
expr = "(description)::pdb.regex_pattern('run.*')"
normalized = pdb_alembic._normalize_bm25_expression(expr)
assert normalized == "(description)::pdb.regex_pattern('run.*')"


def test_normalize_bm25_expression_strips_relation_qualifiers_only():
expr = '"public"."products"."description"::pdb.simple(\'alias=description_simple\')'
normalized = pdb_alembic._normalize_bm25_expression(expr)
assert normalized == "description::pdb.simple('alias=description_simple')"