Merge pull request #56696 from mihir-kandoi/pg/index-search

perf(postgres): partial/covering indexes + trigram item search
This commit is contained in:
Mihir Kandoi
2026-07-02 00:23:18 +05:30
committed by GitHub
4 changed files with 64 additions and 1 deletions

View File

@@ -471,6 +471,25 @@ def on_doctype_update():
frappe.db.add_index("GL Entry", ["posting_date", "company"])
frappe.db.add_index("GL Entry", ["party_type", "party"])
if frappe.db.db_type == "postgres":
# Postgres-only partial/covering indexes for the financial reports (General Ledger, Trial
# Balance, Balance Sheet, P&L), which always filter `is_cancelled = 0` and scope by company.
# `where`/`include` are no-ops on MariaDB and its optimizer ignores these anyway, so they are
# added only on postgres to avoid dead write overhead on this insert-hot table.
frappe.db.add_index(
"GL Entry",
["company", "posting_date", "account"],
index_name="gle_active_detail",
where="is_cancelled = 0",
)
frappe.db.add_index(
"GL Entry",
["company", "account", "posting_date"],
index_name="gle_active_cover",
where="is_cancelled = 0",
include=["debit", "credit"],
)
def rename_gle_sle_docs():
for doctype in ["GL Entry", "Stock Ledger Entry"]:

View File

@@ -1372,7 +1372,8 @@ def get_purchase_voucher_details(doctype, item_code, document_name=None):
query = query.select(parent_doc.transaction_date)
query = query.orderby(parent_doc.transaction_date, parent_doc.name, order=Order.desc)
return query.run(as_dict=1)
# only the latest ([0]) row is ever used, so fetch just that instead of every purchase of the item
return query.limit(1).run(as_dict=1)
def check_stock_uom_with_bin(item, stock_uom):
@@ -1762,3 +1763,13 @@ def get_default_warehouse_for_opening_stock(item, company: str, warehouse: str |
"No warehouse found for company {0}. Please set a Default Warehouse in Item Defaults or Stock Settings."
).format(frappe.bold(company))
)
def on_doctype_update():
if frappe.db.db_type == "postgres":
# The Item link-search (erpnext.controllers.queries.item_query) filters
# `item_code/item_name LIKE '%txt%'` -- a leading-wildcard LIKE no btree can serve. pg_trgm
# GIN indexes accelerate it. Item is read-heavy/write-light master data, so GIN maintenance
# cost is negligible. Postgres-only (`using` is a no-op on MariaDB, which has its own FULLTEXT).
frappe.db.add_index("Item", ["item_code"], using="gin_trgm")
frappe.db.add_index("Item", ["item_name"], using="gin_trgm")

View File

@@ -1814,6 +1814,27 @@ class SerialandBatchBundle(Document):
self.set("entries", [])
def on_doctype_update():
if frappe.db.db_type == "postgres":
# Bundle-direct lookups (get_ledgers_from_serial_batch_bundle, get_picked_*) always filter
# `is_cancelled = 0` and scope by voucher_no or item_code+warehouse -- none of which the parent
# bundle is otherwise indexed on (only voucher_type/voucher_detail_no are). Partial indexes keep
# only the active bundles. Postgres-only (`where` is a no-op on MariaDB, and MariaDB's optimizer
# ignores partial predicates anyway).
frappe.db.add_index(
"Serial and Batch Bundle",
["voucher_no"],
index_name="sabb_active_voucher",
where="is_cancelled = 0",
)
frappe.db.add_index(
"Serial and Batch Bundle",
["item_code", "warehouse"],
index_name="sabb_active_item_wh",
where="is_cancelled = 0",
)
@frappe.whitelist()
def download_blank_csv_template(content: str | list):
csv_data = []

View File

@@ -364,3 +364,15 @@ class StockLedgerEntry(Document):
def on_doctype_update():
frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"])
frappe.db.add_index("Stock Ledger Entry", ["item_code", "warehouse", "posting_datetime", "creation"])
if frappe.db.db_type == "postgres":
# Postgres-only partial index for date-range stock reports (Stock Ledger / Stock Balance)
# that scan across all items: they filter `is_cancelled = 0` and sort by posting_datetime.
# The existing item_code-leading composite can't serve an all-items date scan. `where` is a
# no-op on MariaDB, so this is added only on postgres.
frappe.db.add_index(
"Stock Ledger Entry",
["company", "posting_datetime", "creation"],
index_name="sle_active_posting",
where="is_cancelled = 0",
)