fix(controllers): cast idx to varchar in child-row picker for Postgres

get_filtered_child_rows searched child rows by row number with table.idx.like(...). idx is an integer column; frappe maps .like() to ILIKE on Postgres, which has no bigint ILIKE operator ('operator does not exist: bigint ~~* unknown'). Cast idx to string via frappe's Cast_ with 'varchar': a bare CAST(idx AS CHAR) is character(1) on Postgres and silently truncates a two-digit idx (11 -> '1'), dropping the row; CAST(idx AS VARCHAR) keeps the full value, and on MariaDB Cast_ rewrites to CONCAT(idx, '') matching the previous implicit coercion. MariaDB output unchanged. The test builds an order with >10 rows and searches row 11 (fails on Postgres with a char(1) cast).
This commit is contained in:
Mihir Kandoi
2026-06-23 09:13:50 +05:30
parent 9f1915800f
commit bde630b888
2 changed files with 31 additions and 1 deletions

View File

@@ -10,6 +10,7 @@ from frappe import qb, scrub
from frappe.permissions import has_permission
from frappe.query_builder import Case, Criterion, DocType
from frappe.query_builder.functions import (
Cast_,
Concat,
IfNull,
Length,
@@ -1135,7 +1136,8 @@ def get_filtered_child_rows(
if txt:
txt += "%"
query = query.where(
((table.idx.like(txt.replace("#", ""))) | (table.item_code.like(txt))) | (table.name.like(txt))
((Cast_(table.idx, "varchar").like(txt.replace("#", ""))) | (table.item_code.like(txt)))
| (table.name.like(txt))
)
return query.run(as_dict=False)

View File

@@ -93,6 +93,34 @@ class TestQueries(ERPNextTestSuite):
query = add_default_params(queries.get_purchase_invoices, "Purchase Invoice")
self.assertIsInstance(query(txt="", filters={}), list | tuple)
def test_get_filtered_child_rows_query(self):
# idx is an integer column. Searching child rows by it must run on Postgres
# (a bare LIKE rejects "bigint ILIKE text") AND cast to a full-length string:
# CAST(idx AS CHAR) is character(1) on Postgres, so a two-digit idx like 11
# would render as "1" and be missed. Build a Sales Order with >10 rows and
# search for row 11 to lock both behaviours.
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
frappe.db.set_single_value("Selling Settings", "allow_multiple_items", 1)
so = make_sales_order(
item_list=[
{"item_code": "_Test Item", "qty": 1, "rate": 100, "warehouse": "_Test Warehouse - _TC"}
for _ in range(11)
],
do_not_submit=True,
)
rows = queries.get_filtered_child_rows(
"Sales Order Item",
txt="#11",
searchfield="name",
start=0,
page_len=20,
filters={"parent": so.name},
)
# row label is "#<idx>, <item_code>"; row 11 must be present
self.assertTrue(any(str(label).startswith("#11,") for _name, label in rows))
def test_default_uoms(self):
self.assertGreaterEqual(frappe.db.count("UOM", {"enabled": 1}), 10)