TypeDAL 4.9.3__tar.gz → 4.9.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {typedal-4.9.3 → typedal-4.9.4}/CHANGELOG.md +6 -0
- {typedal-4.9.3 → typedal-4.9.4}/PKG-INFO +1 -1
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/__about__.py +1 -1
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/query_builder.py +45 -1
- {typedal-4.9.3 → typedal-4.9.4}/tests/test_query_builder.py +43 -0
- {typedal-4.9.3 → typedal-4.9.4}/.crush/.gitignore +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/.crush/crush.db-shm +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/.crush/crush.db-wal +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/.crush/init +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/.crush/logs/crush.log +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/.github/workflows/su6.yml +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/.gitignore +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/.readthedocs.yml +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/README.md +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/coverage.svg +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/docs/10_advanced_apis.md +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/docs/1_getting_started.md +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/docs/2_defining_tables.md +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/docs/3_building_queries.md +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/docs/4_relationships.md +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/docs/5_py4web.md +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/docs/6_migrations.md +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/docs/7_configuration.md +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/docs/8_mixins.md +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/docs/9_memoization.md +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/docs/css/code_blocks.css +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/docs/index.md +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/docs/requirements.txt +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/example_new.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/example_old.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/mkdocs.yml +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/pyproject.toml +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/__init__.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/caching.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/cli.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/config.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/constants.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/core.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/define.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/enum_helpers.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/fields.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/for_py4web.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/for_web2py.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/helpers.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/mixins.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/py.typed +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/relationships.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/rows.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/serializers/as_json.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/serializers/typescript.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/tables.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/types.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/src/typedal/web2py_py4web_shared.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tasks.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/__init__.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/configs/simple.toml +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/configs/valid.env +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/configs/valid.toml +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/conftest.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/py314_tests.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/test_cli.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/test_config.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/test_docs_examples.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/test_helpers.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/test_json.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/test_main.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/test_mixins.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/test_mypy.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/test_orm.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/test_py4web.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/test_relationships.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/test_row.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/test_stats.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/test_table.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/test_typescript.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/test_typing_mypy.md +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/test_typing_pyright.md +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/test_web2py.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/test_xx_others.py +0 -0
- {typedal-4.9.3 → typedal-4.9.4}/tests/timings.py +0 -0
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
<!--next-version-placeholder-->
|
|
4
4
|
|
|
5
|
+
## v4.9.4 (2026-07-03)
|
|
6
|
+
|
|
7
|
+
### Fix
|
|
8
|
+
|
|
9
|
+
* **query_builder:** Include orderby fields in distinct-id subquery for paginated joins ([`d343a43`](https://github.com/trialandsuccess/TypeDAL/commit/d343a4326fdbf93fb5fd8b5db061ea5b3355e3de))
|
|
10
|
+
|
|
5
11
|
## v4.9.3 (2026-07-03)
|
|
6
12
|
|
|
7
13
|
### Fix
|
|
@@ -818,6 +818,46 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
|
|
|
818
818
|
|
|
819
819
|
return joins
|
|
820
820
|
|
|
821
|
+
def _selectable_orderby_fields(self, orderby: OrderBy | t.Iterable[OrderBy] | None) -> list[OrderBy]:
|
|
822
|
+
"""Extract field values from pydal orderby expressions."""
|
|
823
|
+
if not orderby:
|
|
824
|
+
return []
|
|
825
|
+
|
|
826
|
+
if isinstance(orderby, (list, tuple, set)):
|
|
827
|
+
return [field for item in orderby for field in self._selectable_orderby_fields(item)]
|
|
828
|
+
|
|
829
|
+
if isinstance(orderby, pydal.objects.Field):
|
|
830
|
+
return [orderby]
|
|
831
|
+
|
|
832
|
+
fields = []
|
|
833
|
+
first = getattr(orderby, "first", None)
|
|
834
|
+
second = getattr(orderby, "second", None)
|
|
835
|
+
|
|
836
|
+
if first is not None:
|
|
837
|
+
fields.extend(self._selectable_orderby_fields(first))
|
|
838
|
+
if second is not None:
|
|
839
|
+
fields.extend(self._selectable_orderby_fields(second))
|
|
840
|
+
|
|
841
|
+
return fields
|
|
842
|
+
|
|
843
|
+
def _select_distinct_ids_with_orderby_fields(self, query: Query, select_kwargs: SelectKwargs) -> str:
|
|
844
|
+
db = self._get_db()
|
|
845
|
+
model = self.model
|
|
846
|
+
select_args: list[OrderBy] = [model.id]
|
|
847
|
+
seen = {str(model.id)}
|
|
848
|
+
|
|
849
|
+
for field in self._selectable_orderby_fields(select_kwargs.get("orderby")):
|
|
850
|
+
key = str(field)
|
|
851
|
+
if key not in seen:
|
|
852
|
+
select_args.append(field)
|
|
853
|
+
seen.add(key)
|
|
854
|
+
|
|
855
|
+
ids = db(query)._select(*select_args, **select_kwargs).rstrip(";")
|
|
856
|
+
id_column = getattr(model.id, "_raw_rname", model.id.name)
|
|
857
|
+
return f'SELECT "{id_column}" FROM ({ids}) AS typedal_paginate_ids' # nosec:
|
|
858
|
+
# id_column originates from code
|
|
859
|
+
# ids is a safe subquery, originating from code
|
|
860
|
+
|
|
821
861
|
def _apply_limitby_optimization(
|
|
822
862
|
self,
|
|
823
863
|
query: Query,
|
|
@@ -839,7 +879,11 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
|
|
|
839
879
|
kwargs["join"] = joins
|
|
840
880
|
kwargs["distinct"] = True
|
|
841
881
|
|
|
842
|
-
|
|
882
|
+
if joins and kwargs.get("orderby"):
|
|
883
|
+
ids = self._select_distinct_ids_with_orderby_fields(query, kwargs)
|
|
884
|
+
else:
|
|
885
|
+
ids = db(query)._select(model.id, **kwargs)
|
|
886
|
+
|
|
843
887
|
query = model.id.belongs(ids)
|
|
844
888
|
metadata["ids"] = ids
|
|
845
889
|
|
|
@@ -519,6 +519,10 @@ def test_orderby():
|
|
|
519
519
|
base_qt = TestQueryTable.select(TestQueryTable.id, TestQueryTable.number)
|
|
520
520
|
|
|
521
521
|
assert base_qt.count() == 5
|
|
522
|
+
assert base_qt._selectable_orderby_fields(None) == []
|
|
523
|
+
|
|
524
|
+
composite_fields = base_qt._selectable_orderby_fields(TestQueryTable.number | TestQueryTable.other)
|
|
525
|
+
assert composite_fields == [TestQueryTable.number, TestQueryTable.other]
|
|
522
526
|
|
|
523
527
|
rows1 = base_qt.orderby(TestQueryTable.id).paginate(limit=3, page=1)
|
|
524
528
|
rows2 = base_qt.select(orderby=TestQueryTable.id, limitby=(0, 3)).collect()
|
|
@@ -602,6 +606,45 @@ def test_select_kwargs_use_rname_psql(dal_psql: TypeDAL):
|
|
|
602
606
|
assert "some_table.some_name" in sql
|
|
603
607
|
|
|
604
608
|
|
|
609
|
+
def test_paginate_inner_joins_with_related_orderby_psql(dal_psql: TypeDAL):
|
|
610
|
+
db = dal_psql
|
|
611
|
+
|
|
612
|
+
@db.define()
|
|
613
|
+
class Method(TypedTable):
|
|
614
|
+
gid = TypedField(str)
|
|
615
|
+
name = TypedField(str)
|
|
616
|
+
|
|
617
|
+
@db.define()
|
|
618
|
+
class Supplier(TypedTable):
|
|
619
|
+
gid = TypedField(str)
|
|
620
|
+
name = TypedField(str)
|
|
621
|
+
|
|
622
|
+
@db.define()
|
|
623
|
+
class Product(TypedTable):
|
|
624
|
+
name = TypedField(str)
|
|
625
|
+
method: Method
|
|
626
|
+
supplier: Supplier
|
|
627
|
+
|
|
628
|
+
supplier = Supplier.insert(gid="supplier", name="Supplier")
|
|
629
|
+
slow = Method.insert(gid="slow", name="Slow")
|
|
630
|
+
fast = Method.insert(gid="fast", name="Fast")
|
|
631
|
+
Product.insert(name="Product slow", method=slow, supplier=supplier)
|
|
632
|
+
Product.insert(name="Product fast", method=fast, supplier=supplier)
|
|
633
|
+
db.commit()
|
|
634
|
+
|
|
635
|
+
builder = Product.join("method", method="inner").join("supplier", method="inner")
|
|
636
|
+
method_relation = builder.relationships["method"]
|
|
637
|
+
method_alias = method_relation.get_table(db).with_alias(f"method_{hash(method_relation)}")
|
|
638
|
+
builder = builder.orderby(method_alias.name)
|
|
639
|
+
builder = builder.select(Method.gid, Method.name)
|
|
640
|
+
builder = builder.select(Supplier.gid, Supplier.name)
|
|
641
|
+
|
|
642
|
+
page = builder.paginate(page=1, limit=1)
|
|
643
|
+
|
|
644
|
+
assert len(page) == 1
|
|
645
|
+
assert page.first().method.name == "Fast"
|
|
646
|
+
|
|
647
|
+
|
|
605
648
|
def test_execute():
|
|
606
649
|
_setup_data()
|
|
607
650
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|