diff --git a/frappe/database/query.py b/frappe/database/query.py index 08f3bce309..4d3f2ddd0b 100644 --- a/frappe/database/query.py +++ b/frappe/database/query.py @@ -172,6 +172,9 @@ BACKTICK_FIELD_PARSE_REGEX = re.compile(r"^`tab([\w\s-]+)`\.(`?)(\w+)\2$") # Group 3: Fieldname CHILD_TABLE_FIELD_PATTERN = re.compile(r'^[`"]?tab([\w\s]+)[`"]?\.([`"]?)(\w+)\2$') +# Maximum value of an unsigned 64-bit integer +MAX_LIMIT = 18446744073709551615 + # Direct mapping from uppercase function names to pypika function classes FUNCTION_MAPPING = { "COUNT": functions.Count, @@ -299,6 +302,11 @@ class Engine: if offset: if not isinstance(offset, int) or offset < 0: frappe.throw(_("Offset must be a non-negative integer"), TypeError) + + # In MariaDB and SQLite, offset requires limit + if not self.is_postgres and not limit: + self.query = self.query.limit(MAX_LIMIT) + self.query = self.query.offset(offset) if distinct: diff --git a/frappe/tests/test_query.py b/frappe/tests/test_query.py index 58f41f1278..18ab24c332 100644 --- a/frappe/tests/test_query.py +++ b/frappe/tests/test_query.py @@ -2625,6 +2625,19 @@ class TestQuery(IntegrationTestCase): ) self.assertFalse(index_exists) + def test_limit_offset_query(self): + """Test if query builder correctly uses limit with offset in MariaDB and SQLite when limit is omitted.""" + from frappe.database.query import MAX_LIMIT + + query = frappe.qb.get_query("Doctype", offset=10).get_sql() + if frappe.db.db_type != "postgres": + self.assertIn(f"LIMIT {MAX_LIMIT} OFFSET 10", query) + query = frappe.qb.get_query("Doctype", limit=10, offset=10).get_sql() + self.assertIn("LIMIT 10 OFFSET 10", query) + else: + self.assertNotIn("LIMIT", query) + self.assertIn("OFFSET 10", query) + # This function is used as a permission query condition hook def test_permission_hook_condition(user):