TypeDAL 4.9.2__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.
Files changed (80) hide show
  1. typedal-4.9.4/.crush/.gitignore +1 -0
  2. typedal-4.9.4/.crush/crush.db-shm +0 -0
  3. typedal-4.9.4/.crush/crush.db-wal +0 -0
  4. typedal-4.9.4/.crush/logs/crush.log +37 -0
  5. {typedal-4.9.2 → typedal-4.9.4}/CHANGELOG.md +12 -0
  6. {typedal-4.9.2 → typedal-4.9.4}/PKG-INFO +1 -1
  7. {typedal-4.9.2 → typedal-4.9.4}/src/typedal/__about__.py +1 -1
  8. {typedal-4.9.2 → typedal-4.9.4}/src/typedal/query_builder.py +70 -5
  9. typedal-4.9.4/tests/__init__.py +0 -0
  10. {typedal-4.9.2 → typedal-4.9.4}/tests/test_query_builder.py +43 -0
  11. {typedal-4.9.2 → typedal-4.9.4}/tests/test_relationships.py +27 -0
  12. /typedal-4.9.2/src/typedal/py.typed → /typedal-4.9.4/.crush/init +0 -0
  13. {typedal-4.9.2 → typedal-4.9.4}/.github/workflows/su6.yml +0 -0
  14. {typedal-4.9.2 → typedal-4.9.4}/.gitignore +0 -0
  15. {typedal-4.9.2 → typedal-4.9.4}/.readthedocs.yml +0 -0
  16. {typedal-4.9.2 → typedal-4.9.4}/README.md +0 -0
  17. {typedal-4.9.2 → typedal-4.9.4}/coverage.svg +0 -0
  18. {typedal-4.9.2 → typedal-4.9.4}/docs/10_advanced_apis.md +0 -0
  19. {typedal-4.9.2 → typedal-4.9.4}/docs/1_getting_started.md +0 -0
  20. {typedal-4.9.2 → typedal-4.9.4}/docs/2_defining_tables.md +0 -0
  21. {typedal-4.9.2 → typedal-4.9.4}/docs/3_building_queries.md +0 -0
  22. {typedal-4.9.2 → typedal-4.9.4}/docs/4_relationships.md +0 -0
  23. {typedal-4.9.2 → typedal-4.9.4}/docs/5_py4web.md +0 -0
  24. {typedal-4.9.2 → typedal-4.9.4}/docs/6_migrations.md +0 -0
  25. {typedal-4.9.2 → typedal-4.9.4}/docs/7_configuration.md +0 -0
  26. {typedal-4.9.2 → typedal-4.9.4}/docs/8_mixins.md +0 -0
  27. {typedal-4.9.2 → typedal-4.9.4}/docs/9_memoization.md +0 -0
  28. {typedal-4.9.2 → typedal-4.9.4}/docs/css/code_blocks.css +0 -0
  29. {typedal-4.9.2 → typedal-4.9.4}/docs/index.md +0 -0
  30. {typedal-4.9.2 → typedal-4.9.4}/docs/requirements.txt +0 -0
  31. {typedal-4.9.2 → typedal-4.9.4}/example_new.py +0 -0
  32. {typedal-4.9.2 → typedal-4.9.4}/example_old.py +0 -0
  33. {typedal-4.9.2 → typedal-4.9.4}/mkdocs.yml +0 -0
  34. {typedal-4.9.2 → typedal-4.9.4}/pyproject.toml +0 -0
  35. {typedal-4.9.2 → typedal-4.9.4}/src/typedal/__init__.py +0 -0
  36. {typedal-4.9.2 → typedal-4.9.4}/src/typedal/caching.py +0 -0
  37. {typedal-4.9.2 → typedal-4.9.4}/src/typedal/cli.py +0 -0
  38. {typedal-4.9.2 → typedal-4.9.4}/src/typedal/config.py +0 -0
  39. {typedal-4.9.2 → typedal-4.9.4}/src/typedal/constants.py +0 -0
  40. {typedal-4.9.2 → typedal-4.9.4}/src/typedal/core.py +0 -0
  41. {typedal-4.9.2 → typedal-4.9.4}/src/typedal/define.py +0 -0
  42. {typedal-4.9.2 → typedal-4.9.4}/src/typedal/enum_helpers.py +0 -0
  43. {typedal-4.9.2 → typedal-4.9.4}/src/typedal/fields.py +0 -0
  44. {typedal-4.9.2 → typedal-4.9.4}/src/typedal/for_py4web.py +0 -0
  45. {typedal-4.9.2 → typedal-4.9.4}/src/typedal/for_web2py.py +0 -0
  46. {typedal-4.9.2 → typedal-4.9.4}/src/typedal/helpers.py +0 -0
  47. {typedal-4.9.2 → typedal-4.9.4}/src/typedal/mixins.py +0 -0
  48. /typedal-4.9.2/tests/__init__.py → /typedal-4.9.4/src/typedal/py.typed +0 -0
  49. {typedal-4.9.2 → typedal-4.9.4}/src/typedal/relationships.py +0 -0
  50. {typedal-4.9.2 → typedal-4.9.4}/src/typedal/rows.py +0 -0
  51. {typedal-4.9.2 → typedal-4.9.4}/src/typedal/serializers/as_json.py +0 -0
  52. {typedal-4.9.2 → typedal-4.9.4}/src/typedal/serializers/typescript.py +0 -0
  53. {typedal-4.9.2 → typedal-4.9.4}/src/typedal/tables.py +0 -0
  54. {typedal-4.9.2 → typedal-4.9.4}/src/typedal/types.py +0 -0
  55. {typedal-4.9.2 → typedal-4.9.4}/src/typedal/web2py_py4web_shared.py +0 -0
  56. {typedal-4.9.2 → typedal-4.9.4}/tasks.py +0 -0
  57. {typedal-4.9.2 → typedal-4.9.4}/tests/configs/simple.toml +0 -0
  58. {typedal-4.9.2 → typedal-4.9.4}/tests/configs/valid.env +0 -0
  59. {typedal-4.9.2 → typedal-4.9.4}/tests/configs/valid.toml +0 -0
  60. {typedal-4.9.2 → typedal-4.9.4}/tests/conftest.py +0 -0
  61. {typedal-4.9.2 → typedal-4.9.4}/tests/py314_tests.py +0 -0
  62. {typedal-4.9.2 → typedal-4.9.4}/tests/test_cli.py +0 -0
  63. {typedal-4.9.2 → typedal-4.9.4}/tests/test_config.py +0 -0
  64. {typedal-4.9.2 → typedal-4.9.4}/tests/test_docs_examples.py +0 -0
  65. {typedal-4.9.2 → typedal-4.9.4}/tests/test_helpers.py +0 -0
  66. {typedal-4.9.2 → typedal-4.9.4}/tests/test_json.py +0 -0
  67. {typedal-4.9.2 → typedal-4.9.4}/tests/test_main.py +0 -0
  68. {typedal-4.9.2 → typedal-4.9.4}/tests/test_mixins.py +0 -0
  69. {typedal-4.9.2 → typedal-4.9.4}/tests/test_mypy.py +0 -0
  70. {typedal-4.9.2 → typedal-4.9.4}/tests/test_orm.py +0 -0
  71. {typedal-4.9.2 → typedal-4.9.4}/tests/test_py4web.py +0 -0
  72. {typedal-4.9.2 → typedal-4.9.4}/tests/test_row.py +0 -0
  73. {typedal-4.9.2 → typedal-4.9.4}/tests/test_stats.py +0 -0
  74. {typedal-4.9.2 → typedal-4.9.4}/tests/test_table.py +0 -0
  75. {typedal-4.9.2 → typedal-4.9.4}/tests/test_typescript.py +0 -0
  76. {typedal-4.9.2 → typedal-4.9.4}/tests/test_typing_mypy.md +0 -0
  77. {typedal-4.9.2 → typedal-4.9.4}/tests/test_typing_pyright.md +0 -0
  78. {typedal-4.9.2 → typedal-4.9.4}/tests/test_web2py.py +0 -0
  79. {typedal-4.9.2 → typedal-4.9.4}/tests/test_xx_others.py +0 -0
  80. {typedal-4.9.2 → typedal-4.9.4}/tests/timings.py +0 -0
@@ -0,0 +1 @@
1
+ *
Binary file
Binary file
@@ -0,0 +1,37 @@
1
+ {"time":"2026-01-20T14:19:21.359487106+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":55},"msg":"Fetching providers from Catwalk"}
2
+ {"time":"2026-01-20T14:19:21.518115889+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":63},"msg":"Catwalk providers not modified"}
3
+ {"time":"2026-01-20T14:19:21.52626179+01:00","level":"INFO","msg":"OK 20250424200609_initial.sql (1.52ms)"}
4
+ {"time":"2026-01-20T14:19:21.526651475+01:00","level":"INFO","msg":"OK 20250515105448_add_summary_message_id.sql (330.71µs)"}
5
+ {"time":"2026-01-20T14:19:21.526995425+01:00","level":"INFO","msg":"OK 20250624000000_add_created_at_indexes.sql (325.29µs)"}
6
+ {"time":"2026-01-20T14:19:21.527303588+01:00","level":"INFO","msg":"OK 20250627000000_add_provider_to_messages.sql (293.15µs)"}
7
+ {"time":"2026-01-20T14:19:21.52790648+01:00","level":"INFO","msg":"OK 20250810000000_add_is_summary_message.sql (495.71µs)"}
8
+ {"time":"2026-01-20T14:19:21.528316924+01:00","level":"INFO","msg":"OK 20250812000000_add_todos_to_sessions.sql (389.38µs)"}
9
+ {"time":"2026-01-20T14:19:21.528324939+01:00","level":"INFO","msg":"goose: successfully migrated database to version: 20250812000000"}
10
+ {"time":"2026-01-20T14:19:21.528356959+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).initLSPClients","file":"github.com/charmbracelet/crush/internal/app/lsp.go","line":21},"msg":"LSP clients initialization started in background"}
11
+ {"time":"2026-01-20T14:19:21.52843831+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.New.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":102},"msg":"Initializing MCP clients"}
12
+ {"time":"2026-01-20T14:21:06.413595152+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
13
+ {"time":"2026-01-20T14:21:14.539289381+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
14
+ {"time":"2026-01-20T14:21:15.710688424+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/agent/tools.init.func1","file":"github.com/charmbracelet/crush/internal/agent/tools/rg.go","line":18},"msg":"Ripgrep (rg) not found in $PATH. Some grep features might be limited or slower."}
15
+ {"time":"2026-01-20T14:21:17.08089755+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
16
+ {"time":"2026-01-20T14:21:17.151106375+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
17
+ {"time":"2026-01-20T14:22:05.503761296+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
18
+ {"time":"2026-01-20T14:26:05.030425805+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
19
+ {"time":"2026-01-20T14:36:26.778665189+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
20
+ {"time":"2026-01-20T14:38:51.314473736+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
21
+ {"time":"2026-01-20T15:02:22.602405814+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).Shutdown.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":403},"msg":"Shutdown took 6.62949ms"}
22
+ {"time":"2026-01-20T15:02:23.719585648+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":55},"msg":"Fetching providers from Catwalk"}
23
+ {"time":"2026-01-20T15:02:23.881789126+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":63},"msg":"Catwalk providers not modified"}
24
+ {"time":"2026-01-20T15:02:23.884044691+01:00","level":"INFO","msg":"goose: no migrations to run. current version: 20250812000000"}
25
+ {"time":"2026-01-20T15:02:23.884082792+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).initLSPClients","file":"github.com/charmbracelet/crush/internal/app/lsp.go","line":21},"msg":"LSP clients initialization started in background"}
26
+ {"time":"2026-01-20T15:02:23.884273487+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.New.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":102},"msg":"Initializing MCP clients"}
27
+ {"time":"2026-01-20T15:04:13.024457515+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
28
+ {"time":"2026-01-20T15:04:14.232072885+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/agent/tools.init.func1","file":"github.com/charmbracelet/crush/internal/agent/tools/rg.go","line":18},"msg":"Ripgrep (rg) not found in $PATH. Some grep features might be limited or slower."}
29
+ {"time":"2026-01-20T15:11:24.612161916+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).Shutdown.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":403},"msg":"Shutdown took 9.537141ms"}
30
+ {"time":"2026-01-20T15:11:25.263960575+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":55},"msg":"Fetching providers from Catwalk"}
31
+ {"time":"2026-01-20T15:11:25.431990051+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":63},"msg":"Catwalk providers not modified"}
32
+ {"time":"2026-01-20T15:11:25.434282035+01:00","level":"INFO","msg":"goose: no migrations to run. current version: 20250812000000"}
33
+ {"time":"2026-01-20T15:11:25.434366662+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).initLSPClients","file":"github.com/charmbracelet/crush/internal/app/lsp.go","line":21},"msg":"LSP clients initialization started in background"}
34
+ {"time":"2026-01-20T15:11:25.434579879+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.New.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":102},"msg":"Initializing MCP clients"}
35
+ {"time":"2026-01-20T15:24:40.456010578+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
36
+ {"time":"2026-01-20T15:24:51.673808433+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/agent/tools.init.func1","file":"github.com/charmbracelet/crush/internal/agent/tools/rg.go","line":18},"msg":"Ripgrep (rg) not found in $PATH. Some grep features might be limited or slower."}
37
+ {"time":"2026-01-20T15:26:03.868420566+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).Cancel","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":907},"msg":"Request cancellation initiated","session_id":"4c2d1684-8db2-4ffd-83bb-9c08fe850f29"}
@@ -2,6 +2,18 @@
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
+
11
+ ## v4.9.3 (2026-07-03)
12
+
13
+ ### Fix
14
+
15
+ * Pagination count with joined relationships ([`2eaefc0`](https://github.com/trialandsuccess/TypeDAL/commit/2eaefc051a94d4a4204164e0773a8d7691aae63e))
16
+
5
17
  ## v4.9.2 (2026-07-02)
6
18
 
7
19
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TypeDAL
3
- Version: 4.9.2
3
+ Version: 4.9.4
4
4
  Summary: Typing support for PyDAL
5
5
  Project-URL: Documentation, https://typedal.readthedocs.io/
6
6
  Project-URL: Issues, https://github.com/trialandsuccess/TypeDAL/issues
@@ -5,4 +5,4 @@ This file contains the Version info for this package.
5
5
  # SPDX-FileCopyrightText: 2023-present Robin van der Noord <robinvandernoord@gmail.com>
6
6
  #
7
7
  # SPDX-License-Identifier: MIT
8
- __version__ = "4.9.2"
8
+ __version__ = "4.9.4"
@@ -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,
@@ -837,8 +877,13 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
837
877
 
838
878
  if joins:
839
879
  kwargs["join"] = joins
880
+ kwargs["distinct"] = True
881
+
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)
840
886
 
841
- ids = db(query)._select(model.id, **kwargs)
842
887
  query = model.id.belongs(ids)
843
888
  metadata["ids"] = ids
844
889
 
@@ -1128,19 +1173,31 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
1128
1173
  """
1129
1174
  yield from self.collect()
1130
1175
 
1131
- def __count(self, db: TypeDAL, distinct: t.Optional[bool] = None) -> Query:
1176
+ def __count(
1177
+ self,
1178
+ db: TypeDAL,
1179
+ distinct: t.Optional[bool] = None,
1180
+ *,
1181
+ include_left_for_distinct: bool = True,
1182
+ ) -> Query:
1132
1183
  # internal, shared logic between .count and ._count
1133
1184
  model = self.model
1134
1185
  query = self.query
1135
1186
  for key, relation in self.relationships.items():
1136
- if (not relation.condition or relation.join != "inner") and not distinct:
1187
+ if not relation.condition:
1188
+ continue
1189
+
1190
+ include_left_join = distinct and include_left_for_distinct
1191
+ if relation.join != "inner" and not include_left_join:
1137
1192
  continue
1138
1193
 
1139
1194
  other = relation.get_table(db)
1140
1195
  if not distinct:
1141
1196
  # todo: can this lead to other issues?
1142
1197
  other = other.with_alias(f"{key}_{hash(relation)}")
1143
- query &= relation.condition(model, other)
1198
+
1199
+ if relation.condition is not None:
1200
+ query &= relation.condition(model, other)
1144
1201
 
1145
1202
  return query
1146
1203
 
@@ -1175,12 +1232,20 @@ class QueryBuilder[T_MetaInstance: _TypedTable]:
1175
1232
  require_permission(self._permissions, "read")
1176
1233
  return bool(self.count())
1177
1234
 
1235
+ def __pagination_count(self) -> int:
1236
+ if not self.relationships:
1237
+ return self.count()
1238
+
1239
+ db = self._get_db()
1240
+ query = self.__count(db, distinct=self.model.id, include_left_for_distinct=False)
1241
+ return db(query).count(self.model.id)
1242
+
1178
1243
  def __paginate(
1179
1244
  self,
1180
1245
  limit: int,
1181
1246
  page: int = 1,
1182
1247
  ) -> "QueryBuilder[T_MetaInstance]":
1183
- available = self.count()
1248
+ available = self.__pagination_count()
1184
1249
 
1185
1250
  _from = limit * (page - 1)
1186
1251
  _to = (limit * page) if limit else available
File without changes
@@ -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
 
@@ -888,6 +888,33 @@ def test_join_with_select():
888
888
  assert not hasattr(user.articles[0], "title")
889
889
 
890
890
 
891
+ def test_paginate_with_inner_join_uses_root_entities():
892
+ _setup_data()
893
+
894
+ page1 = User.join("articles", method="inner").orderby(~User.id).paginate(limit=2, page=1)
895
+ page2 = page1.next()
896
+ page1_again = page2.previous()
897
+
898
+ assert [user.id for user in page1] == [4, 3]
899
+ assert [user.id for user in page2] == [2]
900
+ assert [user.id for user in page1_again] == [4, 3]
901
+
902
+ assert sorted(article.title for article in page1.first().articles) == [
903
+ "Untagged Article 1",
904
+ "Untagged Article 2",
905
+ ]
906
+ assert [article.title for article in page2.first().articles] == ["Article 1"]
907
+
908
+ assert page1.pagination["total_items"] == 3
909
+ assert page1.pagination["total_pages"] == 2
910
+ assert page1.pagination["has_next_page"] is True
911
+ assert page2.pagination["has_next_page"] is False
912
+ assert page2.pagination["has_prev_page"] is True
913
+
914
+ with pytest.raises(StopIteration):
915
+ page2.next()
916
+
917
+
891
918
  def test_count_with_join():
892
919
  _setup_data()
893
920
 
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