sqlspec 0.21.1__tar.gz → 0.23.0__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.

Potentially problematic release.


This version of sqlspec might be problematic. Click here for more details.

Files changed (337) hide show
  1. {sqlspec-0.21.1 → sqlspec-0.23.0}/.pre-commit-config.yaml +1 -1
  2. {sqlspec-0.21.1 → sqlspec-0.23.0}/PKG-INFO +1 -1
  3. {sqlspec-0.21.1 → sqlspec-0.23.0}/pyproject.toml +3 -1
  4. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/_sql.py +36 -0
  5. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/base.py +4 -4
  6. sqlspec-0.23.0/sqlspec/builder/mixins/_join_operations.py +388 -0
  7. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/loader.py +65 -68
  8. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/protocols.py +3 -5
  9. sqlspec-0.23.0/sqlspec/storage/__init__.py +13 -0
  10. sqlspec-0.23.0/sqlspec/storage/backends/__init__.py +1 -0
  11. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/storage/backends/fsspec.py +87 -147
  12. sqlspec-0.23.0/sqlspec/storage/backends/local.py +310 -0
  13. sqlspec-0.23.0/sqlspec/storage/backends/obstore.py +474 -0
  14. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/storage/registry.py +101 -70
  15. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/sync_tools.py +8 -5
  16. sqlspec-0.23.0/tests/integration/test_loader/test_file_system_loading.py +773 -0
  17. sqlspec-0.23.0/tests/integration/test_storage/__init__.py +1 -0
  18. sqlspec-0.23.0/tests/integration/test_storage/test_storage_integration.py +866 -0
  19. sqlspec-0.23.0/tests/unit/test_builder/test_lateral_joins.py +271 -0
  20. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_loader/test_loading_patterns.py +148 -133
  21. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_loader/test_sql_file_loader.py +207 -176
  22. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_sql_factory.py +1 -1
  23. sqlspec-0.23.0/tests/unit/test_storage/__init__.py +1 -0
  24. sqlspec-0.23.0/tests/unit/test_storage/test_fsspec_backend.py +499 -0
  25. sqlspec-0.23.0/tests/unit/test_storage/test_local_store.py +484 -0
  26. sqlspec-0.23.0/tests/unit/test_storage/test_obstore_backend.py +488 -0
  27. sqlspec-0.23.0/tests/unit/test_storage/test_storage_registry.py +238 -0
  28. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/test_sync_tools.py +9 -6
  29. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/test_type_guards.py +1 -1
  30. {sqlspec-0.21.1 → sqlspec-0.23.0}/uv.lock +333 -234
  31. sqlspec-0.21.1/sqlspec/builder/mixins/_join_operations.py +0 -268
  32. sqlspec-0.21.1/sqlspec/storage/__init__.py +0 -23
  33. sqlspec-0.21.1/sqlspec/storage/backends/obstore.py +0 -456
  34. sqlspec-0.21.1/sqlspec/storage/capabilities.py +0 -102
  35. sqlspec-0.21.1/tests/integration/test_loader/test_file_system_loading.py +0 -693
  36. sqlspec-0.21.1/tools/__init__.py +0 -0
  37. {sqlspec-0.21.1 → sqlspec-0.23.0}/.gitignore +0 -0
  38. {sqlspec-0.21.1 → sqlspec-0.23.0}/CONTRIBUTING.rst +0 -0
  39. {sqlspec-0.21.1 → sqlspec-0.23.0}/LICENSE +0 -0
  40. {sqlspec-0.21.1 → sqlspec-0.23.0}/Makefile +0 -0
  41. {sqlspec-0.21.1 → sqlspec-0.23.0}/NOTICE +0 -0
  42. {sqlspec-0.21.1 → sqlspec-0.23.0}/README.md +0 -0
  43. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/__init__.py +0 -0
  44. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/__main__.py +0 -0
  45. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/__metadata__.py +0 -0
  46. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/_serialization.py +0 -0
  47. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/_typing.py +0 -0
  48. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/__init__.py +0 -0
  49. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/adbc/__init__.py +0 -0
  50. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/adbc/_types.py +0 -0
  51. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/adbc/config.py +0 -0
  52. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/adbc/driver.py +0 -0
  53. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/aiosqlite/__init__.py +0 -0
  54. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/aiosqlite/_types.py +0 -0
  55. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/aiosqlite/config.py +0 -0
  56. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/aiosqlite/driver.py +0 -0
  57. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/aiosqlite/pool.py +0 -0
  58. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/asyncmy/__init__.py +0 -0
  59. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/asyncmy/_types.py +0 -0
  60. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/asyncmy/config.py +0 -0
  61. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/asyncmy/driver.py +0 -0
  62. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/asyncpg/__init__.py +0 -0
  63. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/asyncpg/_types.py +0 -0
  64. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/asyncpg/config.py +0 -0
  65. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/asyncpg/driver.py +0 -0
  66. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/bigquery/__init__.py +0 -0
  67. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/bigquery/_types.py +0 -0
  68. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/bigquery/config.py +0 -0
  69. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/bigquery/driver.py +0 -0
  70. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/duckdb/__init__.py +0 -0
  71. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/duckdb/_types.py +0 -0
  72. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/duckdb/config.py +0 -0
  73. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/duckdb/driver.py +0 -0
  74. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/duckdb/pool.py +0 -0
  75. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/oracledb/__init__.py +0 -0
  76. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/oracledb/_types.py +0 -0
  77. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/oracledb/config.py +0 -0
  78. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/oracledb/driver.py +0 -0
  79. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/oracledb/migrations.py +0 -0
  80. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/psqlpy/__init__.py +0 -0
  81. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/psqlpy/_types.py +0 -0
  82. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/psqlpy/config.py +0 -0
  83. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/psqlpy/driver.py +0 -0
  84. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/psycopg/__init__.py +0 -0
  85. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/psycopg/_types.py +0 -0
  86. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/psycopg/config.py +0 -0
  87. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/psycopg/driver.py +0 -0
  88. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/sqlite/__init__.py +0 -0
  89. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/sqlite/_types.py +0 -0
  90. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/sqlite/config.py +0 -0
  91. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/sqlite/driver.py +0 -0
  92. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/adapters/sqlite/pool.py +0 -0
  93. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/__init__.py +0 -0
  94. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/_base.py +0 -0
  95. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/_column.py +0 -0
  96. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/_ddl.py +0 -0
  97. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/_delete.py +0 -0
  98. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/_expression_wrappers.py +0 -0
  99. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/_insert.py +0 -0
  100. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/_merge.py +0 -0
  101. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/_parsing_utils.py +0 -0
  102. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/_select.py +0 -0
  103. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/_update.py +0 -0
  104. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/mixins/__init__.py +0 -0
  105. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/mixins/_cte_and_set_ops.py +0 -0
  106. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/mixins/_delete_operations.py +0 -0
  107. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/mixins/_insert_operations.py +0 -0
  108. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/mixins/_merge_operations.py +0 -0
  109. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/mixins/_order_limit_operations.py +0 -0
  110. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/mixins/_pivot_operations.py +0 -0
  111. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/mixins/_select_operations.py +0 -0
  112. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/mixins/_update_operations.py +0 -0
  113. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/builder/mixins/_where_clause.py +0 -0
  114. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/cli.py +0 -0
  115. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/config.py +0 -0
  116. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/core/__init__.py +0 -0
  117. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/core/cache.py +0 -0
  118. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/core/compiler.py +0 -0
  119. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/core/filters.py +0 -0
  120. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/core/hashing.py +0 -0
  121. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/core/parameters.py +0 -0
  122. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/core/result.py +0 -0
  123. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/core/splitter.py +0 -0
  124. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/core/statement.py +0 -0
  125. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/driver/__init__.py +0 -0
  126. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/driver/_async.py +0 -0
  127. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/driver/_common.py +0 -0
  128. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/driver/_sync.py +0 -0
  129. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/driver/mixins/__init__.py +0 -0
  130. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/driver/mixins/_result_tools.py +0 -0
  131. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/driver/mixins/_sql_translator.py +0 -0
  132. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/exceptions.py +0 -0
  133. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/extensions/__init__.py +0 -0
  134. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/extensions/aiosql/__init__.py +0 -0
  135. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/extensions/aiosql/adapter.py +0 -0
  136. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/extensions/litestar/__init__.py +0 -0
  137. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/extensions/litestar/_utils.py +0 -0
  138. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/extensions/litestar/cli.py +0 -0
  139. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/extensions/litestar/config.py +0 -0
  140. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/extensions/litestar/handlers.py +0 -0
  141. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/extensions/litestar/plugin.py +0 -0
  142. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/extensions/litestar/providers.py +0 -0
  143. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/migrations/__init__.py +0 -0
  144. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/migrations/base.py +0 -0
  145. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/migrations/commands.py +0 -0
  146. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/migrations/loaders.py +0 -0
  147. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/migrations/runner.py +0 -0
  148. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/migrations/tracker.py +0 -0
  149. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/migrations/utils.py +0 -0
  150. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/py.typed +0 -0
  151. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/storage/backends/base.py +0 -0
  152. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/typing.py +0 -0
  153. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/__init__.py +0 -0
  154. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/correlation.py +0 -0
  155. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/data_transformation.py +0 -0
  156. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/deprecation.py +0 -0
  157. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/fixtures.py +0 -0
  158. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/logging.py +0 -0
  159. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/module_loader.py +0 -0
  160. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/serializers.py +0 -0
  161. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/singleton.py +0 -0
  162. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/text.py +0 -0
  163. {sqlspec-0.21.1 → sqlspec-0.23.0}/sqlspec/utils/type_guards.py +0 -0
  164. {sqlspec-0.21.1/sqlspec/storage/backends → sqlspec-0.23.0/tests}/__init__.py +0 -0
  165. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/conftest.py +0 -0
  166. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/__init__.py +0 -0
  167. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/asset_maintenance.sql +0 -0
  168. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/ddls-mysql-collection.sql +0 -0
  169. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/ddls-postgres-collection.sql +0 -0
  170. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/example_usage.py +0 -0
  171. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/init.sql +0 -0
  172. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/collection-config.sql +0 -0
  173. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/collection-data_types.sql +0 -0
  174. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/collection-database_details.sql +0 -0
  175. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/collection-engines.sql +0 -0
  176. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/collection-hostname.sql +0 -0
  177. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/collection-plugins.sql +0 -0
  178. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/collection-process_list.sql +0 -0
  179. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/collection-resource-groups.sql +0 -0
  180. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/collection-schema_objects.sql +0 -0
  181. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/collection-table_details.sql +0 -0
  182. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/collection-users.sql +0 -0
  183. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/mysql/init.sql +0 -0
  184. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/oracle.ddl.sql +0 -0
  185. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-applications.sql +0 -0
  186. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-aws_extension_dependency.sql +0 -0
  187. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-aws_oracle_exists.sql +0 -0
  188. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-bg_writer_stats.sql +0 -0
  189. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-calculated_metrics.sql +0 -0
  190. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-data_types.sql +0 -0
  191. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-database_details.sql +0 -0
  192. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-extensions.sql +0 -0
  193. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-index_details.sql +0 -0
  194. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-pglogical-details.sql +0 -0
  195. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-privileges.sql +0 -0
  196. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-replication_slots.sql +0 -0
  197. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-replication_stats.sql +0 -0
  198. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-schema_details.sql +0 -0
  199. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-schema_objects.sql +0 -0
  200. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-settings.sql +0 -0
  201. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-source_details.sql +0 -0
  202. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/collection-table_details.sql +0 -0
  203. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/extended-collection-all-databases.sql +0 -0
  204. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/postgres/init.sql +0 -0
  205. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/readiness-check.sql +0 -0
  206. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/fixtures/sql_utils.py +0 -0
  207. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/__init__.py +0 -0
  208. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/conftest.py +0 -0
  209. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/__init__.py +0 -0
  210. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_adbc/__init__.py +0 -0
  211. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_adbc/conftest.py +0 -0
  212. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_adbc/test_adbc_arrow_features.py +0 -0
  213. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_adbc/test_adbc_backends.py +0 -0
  214. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_adbc/test_adbc_connection.py +0 -0
  215. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_adbc/test_adbc_driver.py +0 -0
  216. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_adbc/test_adbc_edge_cases.py +0 -0
  217. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_adbc/test_adbc_results.py +0 -0
  218. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_adbc/test_migrations.py +0 -0
  219. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_adbc/test_parameter_styles.py +0 -0
  220. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_aiosqlite/__init__.py +0 -0
  221. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_aiosqlite/conftest.py +0 -0
  222. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_aiosqlite/test_connection.py +0 -0
  223. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_aiosqlite/test_driver.py +0 -0
  224. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_aiosqlite/test_migrations.py +0 -0
  225. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_aiosqlite/test_parameter_styles.py +0 -0
  226. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_aiosqlite/test_pooling.py +0 -0
  227. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncmy/__init__.py +0 -0
  228. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncmy/conftest.py +0 -0
  229. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncmy/test_asyncmy_features.py +0 -0
  230. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncmy/test_config.py +0 -0
  231. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncmy/test_driver.py +0 -0
  232. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncmy/test_migrations.py +0 -0
  233. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncmy/test_parameter_styles.py +0 -0
  234. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncpg/__init__.py +0 -0
  235. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncpg/conftest.py +0 -0
  236. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncpg/test_connection.py +0 -0
  237. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncpg/test_driver.py +0 -0
  238. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncpg/test_execute_many.py +0 -0
  239. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncpg/test_migrations.py +0 -0
  240. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_asyncpg/test_parameter_styles.py +0 -0
  241. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_bigquery/__init__.py +0 -0
  242. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_bigquery/conftest.py +0 -0
  243. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_bigquery/test_bigquery_features.py +0 -0
  244. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_bigquery/test_config.py +0 -0
  245. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_bigquery/test_connection.py +0 -0
  246. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_bigquery/test_driver.py +0 -0
  247. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_duckdb/__init__.py +0 -0
  248. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_duckdb/test_connection.py +0 -0
  249. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_duckdb/test_driver.py +0 -0
  250. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_duckdb/test_execute_many.py +0 -0
  251. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_duckdb/test_migrations.py +0 -0
  252. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_duckdb/test_mixed_parameter_styles.py +0 -0
  253. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_duckdb/test_parameter_styles.py +0 -0
  254. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_duckdb/test_pooling.py +0 -0
  255. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_duckdb/utils.py +0 -0
  256. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_oracledb/__init__.py +0 -0
  257. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_oracledb/conftest.py +0 -0
  258. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_oracledb/test_connection.py +0 -0
  259. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_oracledb/test_driver_async.py +0 -0
  260. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_oracledb/test_driver_sync.py +0 -0
  261. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_oracledb/test_execute_many.py +0 -0
  262. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_oracledb/test_migrations.py +0 -0
  263. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_oracledb/test_oracle_features.py +0 -0
  264. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_oracledb/test_parameter_styles.py +0 -0
  265. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psqlpy/__init__.py +0 -0
  266. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psqlpy/conftest.py +0 -0
  267. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psqlpy/test_connection.py +0 -0
  268. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psqlpy/test_driver.py +0 -0
  269. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psqlpy/test_migrations.py +0 -0
  270. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psqlpy/test_parameter_styles.py +0 -0
  271. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psqlpy/test_psqlpy_features.py +0 -0
  272. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psycopg/__init__.py +0 -0
  273. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psycopg/conftest.py +0 -0
  274. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psycopg/test_async_copy.py +0 -0
  275. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psycopg/test_connection.py +0 -0
  276. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psycopg/test_driver.py +0 -0
  277. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psycopg/test_execute_many.py +0 -0
  278. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psycopg/test_migrations.py +0 -0
  279. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_psycopg/test_parameter_styles.py +0 -0
  280. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_sqlite/__init__.py +0 -0
  281. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_sqlite/conftest.py +0 -0
  282. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_sqlite/test_driver.py +0 -0
  283. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_sqlite/test_migrations.py +0 -0
  284. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_sqlite/test_parameter_styles.py +0 -0
  285. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_sqlite/test_pooling.py +0 -0
  286. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_adapters/test_sqlite/test_query_mixin.py +0 -0
  287. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_loader/__init__.py +0 -0
  288. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/integration/test_migrations/__init__.py +0 -0
  289. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/conftest.py +0 -0
  290. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_adapters/__init__.py +0 -0
  291. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_adapters/conftest.py +0 -0
  292. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_adapters/test_adapter_implementations.py +0 -0
  293. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_adapters/test_async_adapters.py +0 -0
  294. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_adapters/test_sync_adapters.py +0 -0
  295. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_base/__init__.py +0 -0
  296. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_base/test_sql_integration.py +0 -0
  297. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_base/test_sqlspec_class.py +0 -0
  298. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_builder/__init__.py +0 -0
  299. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_builder/test_insert_builder.py +0 -0
  300. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_builder/test_parameter_naming.py +0 -0
  301. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_builder_parameter_naming.py +0 -0
  302. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_core/test_cache.py +0 -0
  303. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_core/test_compiler.py +0 -0
  304. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_core/test_filters.py +0 -0
  305. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_core/test_hashing.py +0 -0
  306. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_core/test_parameters.py +0 -0
  307. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_core/test_result.py +0 -0
  308. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_core/test_statement.py +0 -0
  309. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_extensions/__init__.py +0 -0
  310. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_extensions/test_litestar/__init__.py +0 -0
  311. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_extensions/test_litestar/test_config.py +0 -0
  312. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_loader/__init__.py +0 -0
  313. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_loader/test_cache_integration.py +0 -0
  314. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_loader/test_fixtures_directory_loading.py +0 -0
  315. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_migrations/__init__.py +0 -0
  316. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_migrations/test_migration.py +0 -0
  317. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_migrations/test_migration_commands.py +0 -0
  318. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_migrations/test_migration_execution.py +0 -0
  319. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_migrations/test_migration_runner.py +0 -0
  320. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_parsing_utils.py +0 -0
  321. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/__init__.py +0 -0
  322. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/test_correlation.py +0 -0
  323. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/test_data_transformation.py +0 -0
  324. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/test_deprecation.py +0 -0
  325. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/test_fixtures.py +0 -0
  326. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/test_logging.py +0 -0
  327. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/test_module_loader.py +0 -0
  328. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/test_serializers.py +0 -0
  329. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/test_singleton.py +0 -0
  330. {sqlspec-0.21.1 → sqlspec-0.23.0}/tests/unit/test_utils/test_text.py +0 -0
  331. {sqlspec-0.21.1/tests → sqlspec-0.23.0/tools}/__init__.py +0 -0
  332. {sqlspec-0.21.1 → sqlspec-0.23.0}/tools/build_docs.py +0 -0
  333. {sqlspec-0.21.1 → sqlspec-0.23.0}/tools/local-infra.sh +0 -0
  334. {sqlspec-0.21.1 → sqlspec-0.23.0}/tools/pypi_readme.py +0 -0
  335. {sqlspec-0.21.1 → sqlspec-0.23.0}/tools/sphinx_ext/__init__.py +0 -0
  336. {sqlspec-0.21.1 → sqlspec-0.23.0}/tools/sphinx_ext/changelog.py +0 -0
  337. {sqlspec-0.21.1 → sqlspec-0.23.0}/tools/sphinx_ext/missing_references.py +0 -0
@@ -17,7 +17,7 @@ repos:
17
17
  - id: mixed-line-ending
18
18
  - id: trailing-whitespace
19
19
  - repo: https://github.com/charliermarsh/ruff-pre-commit
20
- rev: "v0.12.10"
20
+ rev: "v0.12.11"
21
21
  hooks:
22
22
  - id: ruff
23
23
  args: ["--fix"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlspec
3
- Version: 0.21.1
3
+ Version: 0.23.0
4
4
  Summary: SQL Experiments in Python
5
5
  Project-URL: Discord, https://discord.gg/litestar
6
6
  Project-URL: Issue, https://github.com/litestar-org/sqlspec/issues/
@@ -13,7 +13,7 @@ maintainers = [{ name = "Litestar Developers", email = "hello@litestar.dev" }]
13
13
  name = "sqlspec"
14
14
  readme = "README.md"
15
15
  requires-python = ">=3.9, <4.0"
16
- version = "0.21.1"
16
+ version = "0.23.0"
17
17
 
18
18
  [project.urls]
19
19
  Discord = "https://discord.gg/litestar"
@@ -83,6 +83,7 @@ doc = [
83
83
  ]
84
84
  extras = [
85
85
  "adbc_driver_manager",
86
+ "fsspec[s3]",
86
87
  "pgvector",
87
88
  "pyarrow",
88
89
  "polars",
@@ -341,6 +342,7 @@ module = [
341
342
  "sqlglot.*",
342
343
  "pgvector",
343
344
  "pgvector.*",
345
+ "minio",
344
346
  ]
345
347
 
346
348
  [[tool.mypy.overrides]]
@@ -628,6 +628,42 @@ class SQLFactory:
628
628
  """Create a CROSS JOIN builder."""
629
629
  return JoinBuilder("cross join")
630
630
 
631
+ @property
632
+ def lateral_join_(self) -> "JoinBuilder":
633
+ """Create a LATERAL JOIN builder.
634
+
635
+ Returns:
636
+ JoinBuilder configured for LATERAL JOIN
637
+
638
+ Example:
639
+ ```python
640
+ query = (
641
+ sql.select("u.name", "arr.value")
642
+ .from_("users u")
643
+ .join(sql.lateral_join_("UNNEST(u.tags)").on("true"))
644
+ )
645
+ ```
646
+ """
647
+ return JoinBuilder("lateral join", lateral=True)
648
+
649
+ @property
650
+ def left_lateral_join_(self) -> "JoinBuilder":
651
+ """Create a LEFT LATERAL JOIN builder.
652
+
653
+ Returns:
654
+ JoinBuilder configured for LEFT LATERAL JOIN
655
+ """
656
+ return JoinBuilder("left join", lateral=True)
657
+
658
+ @property
659
+ def cross_lateral_join_(self) -> "JoinBuilder":
660
+ """Create a CROSS LATERAL JOIN builder.
661
+
662
+ Returns:
663
+ JoinBuilder configured for CROSS LATERAL JOIN
664
+ """
665
+ return JoinBuilder("cross join", lateral=True)
666
+
631
667
  def __getattr__(self, name: str) -> "Column":
632
668
  """Dynamically create column references.
633
669
 
@@ -64,7 +64,7 @@ class SQLSpec:
64
64
  config.close_pool()
65
65
  cleaned_count += 1
66
66
  except Exception as e:
67
- logger.warning("Failed to clean up sync pool for config %s: %s", config_type.__name__, e)
67
+ logger.debug("Failed to clean up sync pool for config %s: %s", config_type.__name__, e)
68
68
 
69
69
  if cleaned_count > 0:
70
70
  logger.debug("Sync pool cleanup completed. Cleaned %d pools.", cleaned_count)
@@ -87,14 +87,14 @@ class SQLSpec:
87
87
  else:
88
88
  sync_configs.append((config_type, config))
89
89
  except Exception as e:
90
- logger.warning("Failed to prepare cleanup for config %s: %s", config_type.__name__, e)
90
+ logger.debug("Failed to prepare cleanup for config %s: %s", config_type.__name__, e)
91
91
 
92
92
  if cleanup_tasks:
93
93
  try:
94
94
  await asyncio.gather(*cleanup_tasks, return_exceptions=True)
95
95
  logger.debug("Async pool cleanup completed. Cleaned %d pools.", len(cleanup_tasks))
96
96
  except Exception as e:
97
- logger.warning("Failed to complete async pool cleanup: %s", e)
97
+ logger.debug("Failed to complete async pool cleanup: %s", e)
98
98
 
99
99
  for _config_type, config in sync_configs:
100
100
  config.close_pool()
@@ -129,7 +129,7 @@ class SQLSpec:
129
129
  """
130
130
  config_type = type(config)
131
131
  if config_type in self._configs:
132
- logger.warning("Configuration for %s already exists. Overwriting.", config_type.__name__)
132
+ logger.debug("Configuration for %s already exists. Overwriting.", config_type.__name__)
133
133
  self._configs[config_type] = config
134
134
  return config_type
135
135
 
@@ -0,0 +1,388 @@
1
+ """JOIN operation mixins.
2
+
3
+ Provides mixins for JOIN operations in SELECT statements.
4
+ """
5
+
6
+ from typing import TYPE_CHECKING, Any, Optional, Union, cast
7
+
8
+ from mypy_extensions import trait
9
+ from sqlglot import exp
10
+ from typing_extensions import Self
11
+
12
+ from sqlspec.builder._parsing_utils import parse_table_expression
13
+ from sqlspec.exceptions import SQLBuilderError
14
+ from sqlspec.utils.type_guards import has_query_builder_parameters
15
+
16
+ if TYPE_CHECKING:
17
+ from sqlspec.core.statement import SQL
18
+ from sqlspec.protocols import SQLBuilderProtocol
19
+
20
+ __all__ = ("JoinBuilder", "JoinClauseMixin")
21
+
22
+
23
+ @trait
24
+ class JoinClauseMixin:
25
+ """Mixin providing JOIN clause methods for SELECT builders."""
26
+
27
+ __slots__ = ()
28
+
29
+ # Type annotation for PyRight - this will be provided by the base class
30
+ _expression: Optional[exp.Expression]
31
+
32
+ def join(
33
+ self,
34
+ table: Union[str, exp.Expression, Any],
35
+ on: Optional[Union[str, exp.Expression, "SQL"]] = None,
36
+ alias: Optional[str] = None,
37
+ join_type: str = "INNER",
38
+ lateral: bool = False,
39
+ ) -> Self:
40
+ builder = cast("SQLBuilderProtocol", self)
41
+ self._validate_join_context(builder)
42
+
43
+ # Handle Join expressions directly (from JoinBuilder.on() calls)
44
+ if isinstance(table, exp.Join):
45
+ if builder._expression is not None and isinstance(builder._expression, exp.Select):
46
+ builder._expression = builder._expression.join(table, copy=False)
47
+ return cast("Self", builder)
48
+
49
+ table_expr = self._parse_table_expression(table, alias, builder)
50
+ on_expr = self._parse_on_condition(on, builder)
51
+ join_expr = self._create_join_expression(table_expr, on_expr, join_type)
52
+
53
+ if lateral:
54
+ self._apply_lateral_modifier(join_expr)
55
+
56
+ if builder._expression is not None and isinstance(builder._expression, exp.Select):
57
+ builder._expression = builder._expression.join(join_expr, copy=False)
58
+ return cast("Self", builder)
59
+
60
+ def _validate_join_context(self, builder: "SQLBuilderProtocol") -> None:
61
+ """Validate that the join can be applied to the current expression."""
62
+ if builder._expression is None:
63
+ builder._expression = exp.Select()
64
+ if not isinstance(builder._expression, exp.Select):
65
+ msg = "JOIN clause is only supported for SELECT statements."
66
+ raise SQLBuilderError(msg)
67
+
68
+ def _parse_table_expression(
69
+ self, table: Union[str, exp.Expression, Any], alias: Optional[str], builder: "SQLBuilderProtocol"
70
+ ) -> exp.Expression:
71
+ """Parse table parameter into a SQLGlot expression."""
72
+ if isinstance(table, str):
73
+ return parse_table_expression(table, alias)
74
+ if has_query_builder_parameters(table):
75
+ return self._handle_query_builder_table(table, alias, builder)
76
+ if isinstance(table, exp.Expression):
77
+ return table
78
+ return cast("exp.Expression", table)
79
+
80
+ def _handle_query_builder_table(
81
+ self, table: Any, alias: Optional[str], builder: "SQLBuilderProtocol"
82
+ ) -> exp.Expression:
83
+ """Handle table parameters that are query builders."""
84
+ if hasattr(table, "_expression") and getattr(table, "_expression", None) is not None:
85
+ table_expr_value = getattr(table, "_expression", None)
86
+ if table_expr_value is not None:
87
+ subquery_exp = exp.paren(table_expr_value)
88
+ else:
89
+ subquery_exp = exp.paren(exp.Anonymous(this=""))
90
+ return exp.alias_(subquery_exp, alias) if alias else subquery_exp
91
+ subquery = table.build()
92
+ sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
93
+ subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(builder, "dialect", None)))
94
+ return exp.alias_(subquery_exp, alias) if alias else subquery_exp
95
+
96
+ def _parse_on_condition(
97
+ self, on: Optional[Union[str, exp.Expression, "SQL"]], builder: "SQLBuilderProtocol"
98
+ ) -> Optional[exp.Expression]:
99
+ """Parse ON condition into a SQLGlot expression."""
100
+ if on is None:
101
+ return None
102
+
103
+ if isinstance(on, str):
104
+ return exp.condition(on)
105
+ if hasattr(on, "expression") and hasattr(on, "sql"):
106
+ return self._handle_sql_object_condition(on, builder)
107
+ if isinstance(on, exp.Expression):
108
+ return on
109
+ # Last resort - convert to string and parse
110
+ return exp.condition(str(on))
111
+
112
+ def _handle_sql_object_condition(self, on: Any, builder: "SQLBuilderProtocol") -> exp.Expression:
113
+ """Handle SQL object conditions with parameter binding."""
114
+ expression = getattr(on, "expression", None)
115
+ if expression is not None and isinstance(expression, exp.Expression):
116
+ # Merge parameters from SQL object into builder
117
+ if hasattr(on, "parameters") and hasattr(builder, "add_parameter"):
118
+ sql_parameters = getattr(on, "parameters", {})
119
+ for param_name, param_value in sql_parameters.items():
120
+ builder.add_parameter(param_value, name=param_name)
121
+ return cast("exp.Expression", expression)
122
+ # If expression is None, fall back to parsing the raw SQL
123
+ sql_text = getattr(on, "sql", "")
124
+ # Merge parameters even when parsing raw SQL
125
+ if hasattr(on, "parameters") and hasattr(builder, "add_parameter"):
126
+ sql_parameters = getattr(on, "parameters", {})
127
+ for param_name, param_value in sql_parameters.items():
128
+ builder.add_parameter(param_value, name=param_name)
129
+ parsed_expr = exp.maybe_parse(sql_text)
130
+ return parsed_expr if parsed_expr is not None else exp.condition(str(sql_text))
131
+
132
+ def _create_join_expression(
133
+ self, table_expr: exp.Expression, on_expr: Optional[exp.Expression], join_type: str
134
+ ) -> exp.Join:
135
+ """Create the appropriate JOIN expression based on join type."""
136
+ join_type_upper = join_type.upper()
137
+ if join_type_upper == "INNER":
138
+ return exp.Join(this=table_expr, on=on_expr)
139
+ if join_type_upper == "LEFT":
140
+ return exp.Join(this=table_expr, on=on_expr, side="LEFT")
141
+ if join_type_upper == "RIGHT":
142
+ return exp.Join(this=table_expr, on=on_expr, side="RIGHT")
143
+ if join_type_upper == "FULL":
144
+ return exp.Join(this=table_expr, on=on_expr, side="FULL", kind="OUTER")
145
+ if join_type_upper == "CROSS":
146
+ return exp.Join(this=table_expr, kind="CROSS")
147
+ msg = f"Unsupported join type: {join_type}"
148
+ raise SQLBuilderError(msg)
149
+
150
+ def _apply_lateral_modifier(self, join_expr: exp.Join) -> None:
151
+ """Apply LATERAL modifier to the join expression."""
152
+ current_kind = join_expr.args.get("kind")
153
+ current_side = join_expr.args.get("side")
154
+
155
+ if current_kind == "CROSS":
156
+ join_expr.set("kind", "CROSS LATERAL")
157
+ elif current_kind == "OUTER" and current_side == "FULL":
158
+ join_expr.set("side", "FULL") # Keep side
159
+ join_expr.set("kind", "OUTER LATERAL")
160
+ elif current_side:
161
+ join_expr.set("kind", f"{current_side} LATERAL")
162
+ join_expr.set("side", None) # Clear side to avoid duplication
163
+ else:
164
+ join_expr.set("kind", "LATERAL")
165
+
166
+ def inner_join(
167
+ self, table: Union[str, exp.Expression, Any], on: Union[str, exp.Expression, "SQL"], alias: Optional[str] = None
168
+ ) -> Self:
169
+ return self.join(table, on, alias, "INNER")
170
+
171
+ def left_join(
172
+ self, table: Union[str, exp.Expression, Any], on: Union[str, exp.Expression, "SQL"], alias: Optional[str] = None
173
+ ) -> Self:
174
+ return self.join(table, on, alias, "LEFT")
175
+
176
+ def right_join(
177
+ self, table: Union[str, exp.Expression, Any], on: Union[str, exp.Expression, "SQL"], alias: Optional[str] = None
178
+ ) -> Self:
179
+ return self.join(table, on, alias, "RIGHT")
180
+
181
+ def full_join(
182
+ self, table: Union[str, exp.Expression, Any], on: Union[str, exp.Expression, "SQL"], alias: Optional[str] = None
183
+ ) -> Self:
184
+ return self.join(table, on, alias, "FULL")
185
+
186
+ def cross_join(self, table: Union[str, exp.Expression, Any], alias: Optional[str] = None) -> Self:
187
+ builder = cast("SQLBuilderProtocol", self)
188
+ if builder._expression is None:
189
+ builder._expression = exp.Select()
190
+ if not isinstance(builder._expression, exp.Select):
191
+ msg = "Cannot add cross join to a non-SELECT expression."
192
+ raise SQLBuilderError(msg)
193
+ table_expr: exp.Expression
194
+ if isinstance(table, str):
195
+ table_expr = parse_table_expression(table, alias)
196
+ elif has_query_builder_parameters(table):
197
+ if hasattr(table, "_expression") and getattr(table, "_expression", None) is not None:
198
+ table_expr_value = getattr(table, "_expression", None)
199
+ if table_expr_value is not None:
200
+ subquery_exp = exp.paren(table_expr_value)
201
+ else:
202
+ subquery_exp = exp.paren(exp.Anonymous(this=""))
203
+ table_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
204
+ else:
205
+ subquery = table.build()
206
+ sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
207
+ subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(builder, "dialect", None)))
208
+ table_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
209
+ else:
210
+ table_expr = table
211
+ join_expr = exp.Join(this=table_expr, kind="CROSS")
212
+ builder._expression = builder._expression.join(join_expr, copy=False)
213
+ return cast("Self", builder)
214
+
215
+ def lateral_join(
216
+ self,
217
+ table: Union[str, exp.Expression, Any],
218
+ on: Optional[Union[str, exp.Expression, "SQL"]] = None,
219
+ alias: Optional[str] = None,
220
+ ) -> Self:
221
+ """Create a LATERAL JOIN.
222
+
223
+ Args:
224
+ table: Table, subquery, or table function to join
225
+ on: Optional join condition (for LATERAL JOINs with ON clause)
226
+ alias: Optional alias for the joined table/subquery
227
+
228
+ Returns:
229
+ Self for method chaining
230
+
231
+ Example:
232
+ ```python
233
+ query = (
234
+ sql.select("u.name", "arr.value")
235
+ .from_("users u")
236
+ .lateral_join("UNNEST(u.tags)", alias="arr")
237
+ )
238
+ ```
239
+ """
240
+ return self.join(table, on=on, alias=alias, join_type="INNER", lateral=True)
241
+
242
+ def left_lateral_join(
243
+ self,
244
+ table: Union[str, exp.Expression, Any],
245
+ on: Optional[Union[str, exp.Expression, "SQL"]] = None,
246
+ alias: Optional[str] = None,
247
+ ) -> Self:
248
+ """Create a LEFT LATERAL JOIN.
249
+
250
+ Args:
251
+ table: Table, subquery, or table function to join
252
+ on: Optional join condition
253
+ alias: Optional alias for the joined table/subquery
254
+
255
+ Returns:
256
+ Self for method chaining
257
+ """
258
+ return self.join(table, on=on, alias=alias, join_type="LEFT", lateral=True)
259
+
260
+ def cross_lateral_join(self, table: Union[str, exp.Expression, Any], alias: Optional[str] = None) -> Self:
261
+ """Create a CROSS LATERAL JOIN (no ON condition).
262
+
263
+ Args:
264
+ table: Table, subquery, or table function to join
265
+ alias: Optional alias for the joined table/subquery
266
+
267
+ Returns:
268
+ Self for method chaining
269
+ """
270
+ return self.join(table, on=None, alias=alias, join_type="CROSS", lateral=True)
271
+
272
+
273
+ @trait
274
+ class JoinBuilder:
275
+ """Builder for JOIN operations with fluent syntax.
276
+
277
+ Example:
278
+ ```python
279
+ from sqlspec import sql
280
+
281
+ # sql.left_join_("posts").on("users.id = posts.user_id")
282
+ join_clause = sql.left_join_("posts").on(
283
+ "users.id = posts.user_id"
284
+ )
285
+
286
+ # Or with query builder
287
+ query = (
288
+ sql.select("users.name", "posts.title")
289
+ .from_("users")
290
+ .join(
291
+ sql.left_join_("posts").on(
292
+ "users.id = posts.user_id"
293
+ )
294
+ )
295
+ )
296
+ ```
297
+ """
298
+
299
+ def __init__(self, join_type: str, lateral: bool = False) -> None:
300
+ """Initialize the join builder.
301
+
302
+ Args:
303
+ join_type: Type of join (inner, left, right, full, cross, lateral)
304
+ lateral: Whether this is a LATERAL join
305
+ """
306
+ self._join_type = join_type.upper()
307
+ self._lateral = lateral
308
+ self._table: Optional[Union[str, exp.Expression]] = None
309
+ self._condition: Optional[exp.Expression] = None
310
+ self._alias: Optional[str] = None
311
+
312
+ def __call__(self, table: Union[str, exp.Expression], alias: Optional[str] = None) -> Self:
313
+ """Set the table to join.
314
+
315
+ Args:
316
+ table: Table name or expression to join
317
+ alias: Optional alias for the table
318
+
319
+ Returns:
320
+ Self for method chaining
321
+ """
322
+ self._table = table
323
+ self._alias = alias
324
+ return self
325
+
326
+ def on(self, condition: Union[str, exp.Expression]) -> exp.Expression:
327
+ """Set the join condition and build the JOIN expression.
328
+
329
+ Args:
330
+ condition: JOIN condition (e.g., "users.id = posts.user_id")
331
+
332
+ Returns:
333
+ Complete JOIN expression
334
+ """
335
+ if not self._table:
336
+ msg = "Table must be set before calling .on()"
337
+ raise SQLBuilderError(msg)
338
+
339
+ # Parse the condition
340
+ condition_expr: exp.Expression
341
+ if isinstance(condition, str):
342
+ parsed: Optional[exp.Expression] = exp.maybe_parse(condition)
343
+ condition_expr = parsed or exp.condition(condition)
344
+ else:
345
+ condition_expr = condition
346
+
347
+ # Build table expression
348
+ table_expr: exp.Expression
349
+ if isinstance(self._table, str):
350
+ table_expr = exp.to_table(self._table)
351
+ if self._alias:
352
+ table_expr = exp.alias_(table_expr, self._alias)
353
+ else:
354
+ table_expr = self._table
355
+ if self._alias:
356
+ table_expr = exp.alias_(table_expr, self._alias)
357
+
358
+ # Create the appropriate join type using same pattern as existing JoinClauseMixin
359
+ if self._join_type in {"INNER JOIN", "INNER", "LATERAL JOIN"}:
360
+ join_expr = exp.Join(this=table_expr, on=condition_expr)
361
+ elif self._join_type in {"LEFT JOIN", "LEFT"}:
362
+ join_expr = exp.Join(this=table_expr, on=condition_expr, side="LEFT")
363
+ elif self._join_type in {"RIGHT JOIN", "RIGHT"}:
364
+ join_expr = exp.Join(this=table_expr, on=condition_expr, side="RIGHT")
365
+ elif self._join_type in {"FULL JOIN", "FULL"}:
366
+ join_expr = exp.Join(this=table_expr, on=condition_expr, side="FULL", kind="OUTER")
367
+ elif self._join_type in {"CROSS JOIN", "CROSS"}:
368
+ # CROSS JOIN doesn't use ON condition
369
+ join_expr = exp.Join(this=table_expr, kind="CROSS")
370
+ else:
371
+ join_expr = exp.Join(this=table_expr, on=condition_expr)
372
+
373
+ if self._lateral or self._join_type == "LATERAL JOIN":
374
+ current_kind = join_expr.args.get("kind")
375
+ current_side = join_expr.args.get("side")
376
+
377
+ if current_kind == "CROSS":
378
+ join_expr.set("kind", "CROSS LATERAL")
379
+ elif current_kind == "OUTER" and current_side == "FULL":
380
+ join_expr.set("side", "FULL") # Keep side
381
+ join_expr.set("kind", "OUTER LATERAL")
382
+ elif current_side:
383
+ join_expr.set("kind", f"{current_side} LATERAL")
384
+ join_expr.set("side", None) # Clear side to avoid duplication
385
+ else:
386
+ join_expr.set("kind", "LATERAL")
387
+
388
+ return join_expr