vgi-python 0.29.4__tar.gz → 0.29.5__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 (441) hide show
  1. {vgi_python-0.29.4 → vgi_python-0.29.5}/PKG-INFO +1 -1
  2. {vgi_python-0.29.4 → vgi_python-0.29.5}/pyproject.toml +1 -1
  3. vgi_python-0.29.5/tests/table_in_out/test_blended_exchange.py +240 -0
  4. {vgi_python-0.29.4 → vgi_python-0.29.5}/uv.lock +1 -1
  5. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/client/client.py +107 -14
  6. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/protocol.py +75 -0
  7. {vgi_python-0.29.4 → vgi_python-0.29.5}/.gitattributes +0 -0
  8. {vgi_python-0.29.4 → vgi_python-0.29.5}/.github/dependabot.yml +0 -0
  9. {vgi_python-0.29.4 → vgi_python-0.29.5}/.github/styles/config/vocabularies/VGI/accept.txt +0 -0
  10. {vgi_python-0.29.4 → vgi_python-0.29.5}/.github/workflows/ci.yml +0 -0
  11. {vgi_python-0.29.4 → vgi_python-0.29.5}/.github/workflows/docs.yml +0 -0
  12. {vgi_python-0.29.4 → vgi_python-0.29.5}/.github/workflows/integration.yml +0 -0
  13. {vgi_python-0.29.4 → vgi_python-0.29.5}/.github/workflows/release.yml +0 -0
  14. {vgi_python-0.29.4 → vgi_python-0.29.5}/.gitignore +0 -0
  15. {vgi_python-0.29.4 → vgi_python-0.29.5}/.python-version +0 -0
  16. {vgi_python-0.29.4 → vgi_python-0.29.5}/.vale.ini +0 -0
  17. {vgi_python-0.29.4 → vgi_python-0.29.5}/CLAUDE.md +0 -0
  18. {vgi_python-0.29.4 → vgi_python-0.29.5}/DOCS_ACCEPTANCE_CRITERIA.md +0 -0
  19. {vgi_python-0.29.4 → vgi_python-0.29.5}/DOCS_REVIEW_RUBRIC.md +0 -0
  20. {vgi_python-0.29.4 → vgi_python-0.29.5}/DOCS_USABILITY_TEST.md +0 -0
  21. {vgi_python-0.29.4 → vgi_python-0.29.5}/LICENSE +0 -0
  22. {vgi_python-0.29.4 → vgi_python-0.29.5}/README.md +0 -0
  23. {vgi_python-0.29.4 → vgi_python-0.29.5}/RELEASE-RUNBOOK.md +0 -0
  24. {vgi_python-0.29.4 → vgi_python-0.29.5}/SECURITY.md +0 -0
  25. {vgi_python-0.29.4 → vgi_python-0.29.5}/ci/README.md +0 -0
  26. {vgi_python-0.29.4 → vgi_python-0.29.5}/ci/preprocess-require.awk +0 -0
  27. {vgi_python-0.29.4 → vgi_python-0.29.5}/ci/run-integration.sh +0 -0
  28. {vgi_python-0.29.4 → vgi_python-0.29.5}/dist-vgi/.gitignore +0 -0
  29. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/aggregate-functions.md +0 -0
  30. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/api/arguments.md +0 -0
  31. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/api/auth.md +0 -0
  32. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/api/catalogs.md +0 -0
  33. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/api/client.md +0 -0
  34. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/api/exceptions.md +0 -0
  35. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/api/filters.md +0 -0
  36. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/api/functions.md +0 -0
  37. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/api/http.md +0 -0
  38. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/api/index.md +0 -0
  39. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/api/metadata.md +0 -0
  40. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/api/observability.md +0 -0
  41. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/api/storage.md +0 -0
  42. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/api/transactor.md +0 -0
  43. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/api/worker.md +0 -0
  44. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/argument-serialization.md +0 -0
  45. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/assets/apple-touch-icon.png +0 -0
  46. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/assets/favicon-16x16.png +0 -0
  47. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/assets/favicon-32x32.png +0 -0
  48. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/assets/favicon.ico +0 -0
  49. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/assets/kinds/aggregate.svg +0 -0
  50. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/assets/kinds/buffering.svg +0 -0
  51. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/assets/kinds/scalar.svg +0 -0
  52. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/assets/kinds/table-in-out.svg +0 -0
  53. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/assets/kinds/table.svg +0 -0
  54. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/assets/logo.png +0 -0
  55. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/assets/social-card.png +0 -0
  56. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/authentication.md +0 -0
  57. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/catalog-interface.md +0 -0
  58. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/cli.md +0 -0
  59. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/column-statistics.md +0 -0
  60. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/concepts/index.md +0 -0
  61. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/contributing-docs.md +0 -0
  62. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/filter-pushdown.md +0 -0
  63. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/generator-api.md +0 -0
  64. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/global-functions.md +0 -0
  65. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/how-to/catalogs.md +0 -0
  66. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/how-to/function-patterns.md +0 -0
  67. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/how-to/http-auth.md +0 -0
  68. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/how-to/index.md +0 -0
  69. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/how-to/pushdown-and-statistics.md +0 -0
  70. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/how-to/state-storage.md +0 -0
  71. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/index.md +0 -0
  72. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/lifecycle.md +0 -0
  73. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/metadata.md +0 -0
  74. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/overrides/main.html +0 -0
  75. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/robots.txt +0 -0
  76. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/shared-storage.md +0 -0
  77. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/stylesheets/extra.css +0 -0
  78. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/tutorial/index.md +0 -0
  79. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/tutorial/scalar.md +0 -0
  80. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/tutorial/table.md +0 -0
  81. {vgi_python-0.29.4 → vgi_python-0.29.5}/docs/vgi-logo.png +0 -0
  82. {vgi_python-0.29.4 → vgi_python-0.29.5}/examples/batch_index_worker.py +0 -0
  83. {vgi_python-0.29.4 → vgi_python-0.29.5}/examples/cache_worker.py +0 -0
  84. {vgi_python-0.29.4 → vgi_python-0.29.5}/examples/calc_scalar_worker.py +0 -0
  85. {vgi_python-0.29.4 → vgi_python-0.29.5}/examples/calc_worker.py +0 -0
  86. {vgi_python-0.29.4 → vgi_python-0.29.5}/examples/copy_format_worker.py +0 -0
  87. {vgi_python-0.29.4 → vgi_python-0.29.5}/examples/filter_worker.py +0 -0
  88. {vgi_python-0.29.4 → vgi_python-0.29.5}/examples/greeting_scalar_worker.py +0 -0
  89. {vgi_python-0.29.4 → vgi_python-0.29.5}/examples/row_count_worker.py +0 -0
  90. {vgi_python-0.29.4 → vgi_python-0.29.5}/examples/series_streaming_worker.py +0 -0
  91. {vgi_python-0.29.4 → vgi_python-0.29.5}/examples/sum_worker.py +0 -0
  92. {vgi_python-0.29.4 → vgi_python-0.29.5}/mkdocs.yml +0 -0
  93. {vgi_python-0.29.4 → vgi_python-0.29.5}/packages/vgi-fixtures/LICENSE +0 -0
  94. {vgi_python-0.29.4 → vgi_python-0.29.5}/packages/vgi-fixtures/README.md +0 -0
  95. {vgi_python-0.29.4 → vgi_python-0.29.5}/packages/vgi-fixtures/docker/Dockerfile +0 -0
  96. {vgi_python-0.29.4 → vgi_python-0.29.5}/packages/vgi-fixtures/docker/docker-entrypoint.sh +0 -0
  97. {vgi_python-0.29.4 → vgi_python-0.29.5}/packages/vgi-fixtures/pyproject.toml +0 -0
  98. {vgi_python-0.29.4 → vgi_python-0.29.5}/scripts/measure_startup.py +0 -0
  99. {vgi_python-0.29.4 → vgi_python-0.29.5}/scripts/run_all_tests.sh +0 -0
  100. {vgi_python-0.29.4 → vgi_python-0.29.5}/test-data/generate.sh +0 -0
  101. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/__init__.py +0 -0
  102. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/_http_fixtures.py +0 -0
  103. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/catalog/__init__.py +0 -0
  104. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/catalog/test_attach_option.py +0 -0
  105. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/catalog/test_catalog_interface.py +0 -0
  106. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/catalog/test_client_catalog.py +0 -0
  107. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/catalog/test_column_statistics.py +0 -0
  108. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/catalog/test_declarative.py +0 -0
  109. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/catalog/test_example_worker_catalog.py +0 -0
  110. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/catalog/test_integration.py +0 -0
  111. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/catalog/test_required_filters.py +0 -0
  112. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/catalog/test_scan_branches.py +0 -0
  113. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/catalog/test_serialization.py +0 -0
  114. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/catalog/test_setting.py +0 -0
  115. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/catalog/test_storage.py +0 -0
  116. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/catalog/test_time_travel.py +0 -0
  117. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/catalog/test_writable_table.py +0 -0
  118. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/client/__init__.py +0 -0
  119. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/client/test_aggregate_client.py +0 -0
  120. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/client/test_batch_metadata_callback.py +0 -0
  121. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/client/test_broken_pipe.py +0 -0
  122. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/client/test_cli.py +0 -0
  123. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/client/test_cli_catalog_functions.py +0 -0
  124. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/client/test_copy_client.py +0 -0
  125. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/client/test_scan_branches_client.py +0 -0
  126. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/client/test_split_client.py +0 -0
  127. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/client/test_stop_force.py +0 -0
  128. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/client/test_table_column_statistics.py +0 -0
  129. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/client/test_time_travel_client.py +0 -0
  130. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/client/test_worker_debug.py +0 -0
  131. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/__init__.py +0 -0
  132. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/_stub.py +0 -0
  133. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/conftest.py +0 -0
  134. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_accumulate.py +0 -0
  135. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_aggregate.py +0 -0
  136. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_attach.py +0 -0
  137. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_bearer_auth.py +0 -0
  138. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_const_param_enforcement.py +0 -0
  139. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_container.py +0 -0
  140. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_copy_from.py +0 -0
  141. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_copy_to.py +0 -0
  142. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_directory_parity.py +0 -0
  143. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_function_inventory.py +0 -0
  144. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_github.py +0 -0
  145. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_http_client.py +0 -0
  146. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_http_external_location.py +0 -0
  147. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_http_upload_url.py +0 -0
  148. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_macro.py +0 -0
  149. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_overload.py +0 -0
  150. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_protocol_inventory.py +0 -0
  151. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_protocol_version.py +0 -0
  152. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_resumable_scan.py +0 -0
  153. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_scalar.py +0 -0
  154. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_scalar_attach_opaque_data.py +0 -0
  155. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_secret.py +0 -0
  156. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_settings.py +0 -0
  157. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_splits.py +0 -0
  158. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_table.py +0 -0
  159. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_table_in_out.py +0 -0
  160. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_view.py +0 -0
  161. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conformance/test_writable.py +0 -0
  162. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/conftest.py +0 -0
  163. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/data/split_tokens/anchor_len_overrun.bin +0 -0
  164. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/data/split_tokens/bad_fingerprint.bin +0 -0
  165. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/data/split_tokens/bad_flags_unsealed_but_key_present.bin +0 -0
  166. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/data/split_tokens/bad_version.bin +0 -0
  167. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/data/split_tokens/generate.py +0 -0
  168. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/data/split_tokens/manifest.json +0 -0
  169. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/data/split_tokens/reserved_flag_bit.bin +0 -0
  170. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/data/split_tokens/stale_anchor.bin +0 -0
  171. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/data/split_tokens/truncated.bin +0 -0
  172. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/data/split_tokens/valid_sealed.bin +0 -0
  173. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/data/split_tokens/valid_unsealed.bin +0 -0
  174. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/scalar/__init__.py +0 -0
  175. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/scalar/test_bernoulli_function.py +0 -0
  176. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/scalar/test_binary_packet_function.py +0 -0
  177. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/scalar/test_client.py +0 -0
  178. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/scalar/test_conditional_message_function.py +0 -0
  179. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/scalar/test_hash_seed_function.py +0 -0
  180. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/scalar/test_multiply_by_setting_function.py +0 -0
  181. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/scalar/test_multiply_function.py +0 -0
  182. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/scalar/test_random_bytes_function.py +0 -0
  183. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/scalar/test_return_secret_value_function.py +0 -0
  184. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table/__init__.py +0 -0
  185. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table/generator/__init__.py +0 -0
  186. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table/generator/test_constant_columns_function.py +0 -0
  187. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table/generator/test_double_sequence_function.py +0 -0
  188. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table/generator/test_exception_function.py +0 -0
  189. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table/generator/test_filter_echo_function.py +0 -0
  190. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table/generator/test_logging_function.py +0 -0
  191. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table/generator/test_nested_sequence_function.py +0 -0
  192. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table/generator/test_partitioned_function.py +0 -0
  193. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table/generator/test_projected_data_function.py +0 -0
  194. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table/generator/test_sequence_function.py +0 -0
  195. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table/generator/test_settings_function.py +0 -0
  196. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table/generator/test_struct_settings_function.py +0 -0
  197. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table/generator/test_ten_thousand_function.py +0 -0
  198. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table/test_wide_columns.py +0 -0
  199. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table_in_out/__init__.py +0 -0
  200. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table_in_out/generator/__init__.py +0 -0
  201. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table_in_out/generator/test_buffer_input_function.py +0 -0
  202. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table_in_out/generator/test_echo_function.py +0 -0
  203. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table_in_out/generator/test_exception_functions.py +0 -0
  204. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table_in_out/generator/test_filter_by_setting_function.py +0 -0
  205. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table_in_out/generator/test_repeat_inputs_function.py +0 -0
  206. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table_in_out/generator/test_substream_partial_sum_function.py +0 -0
  207. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table_in_out/generator/test_sum_all_columns_function.py +0 -0
  208. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table_in_out/test_blended_metadata.py +0 -0
  209. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/table_in_out/test_client.py +0 -0
  210. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_access_log_audit.py +0 -0
  211. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_aggregate_function.py +0 -0
  212. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_argument_spec.py +0 -0
  213. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_auth.py +0 -0
  214. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_bind_exceptions.py +0 -0
  215. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_bind_request_at_clause.py +0 -0
  216. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_bound_storage_conformance.py +0 -0
  217. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_cache_control.py +0 -0
  218. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_call_state_attach.py +0 -0
  219. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_catalog_auth_binding.py +0 -0
  220. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_copy_from_function.py +0 -0
  221. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_copy_to_function.py +0 -0
  222. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_docstrings.py +0 -0
  223. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_documentation_examples.py +0 -0
  224. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_example_function_arg_types.py +0 -0
  225. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_examples_workers.py +0 -0
  226. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_exception_handling.py +0 -0
  227. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_exceptions.py +0 -0
  228. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_filter_pushdown.py +0 -0
  229. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_filter_pushdown_extension.py +0 -0
  230. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_fixture_catalog_parity.py +0 -0
  231. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_function_storage.py +0 -0
  232. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_function_storage_azure_sql.py +0 -0
  233. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_function_storage_cf_do.py +0 -0
  234. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_function_storage_cf_do_integration.py +0 -0
  235. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_function_storage_conformance.py +0 -0
  236. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_generated_cpp_constants.py +0 -0
  237. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_generated_cpp_protocol_version.py +0 -0
  238. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_generated_cpp_request_builders.py +0 -0
  239. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_generated_cpp_schemas.py +0 -0
  240. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_generated_cpp_secret.py +0 -0
  241. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_generated_go_schemas.py +0 -0
  242. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_generated_java_schemas.py +0 -0
  243. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_generated_protocol_version.py +0 -0
  244. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_generated_rust_request_builders.py +0 -0
  245. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_generated_rust_schemas.py +0 -0
  246. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_generated_schemas_cross_lang.py +0 -0
  247. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_generated_ts_client.py +0 -0
  248. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_generated_ts_schemas.py +0 -0
  249. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_http_demo_storage.py +0 -0
  250. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_http_s3_offload_input.py +0 -0
  251. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_http_s3_offload_output.py +0 -0
  252. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_launch_transport.py +0 -0
  253. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_metadata.py +0 -0
  254. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_mypy_consolidated.py +0 -0
  255. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_nest_tensor.py +0 -0
  256. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_otel.py +0 -0
  257. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_projection_enforcement.py +0 -0
  258. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_projection_repro.py +0 -0
  259. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_protocol_classes.py +0 -0
  260. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_resolved_secrets.py +0 -0
  261. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_schema_scoped_functions.py +0 -0
  262. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_schema_utils.py +0 -0
  263. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_serve.py +0 -0
  264. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_setting_secret_annotations.py +0 -0
  265. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_signing_key.py +0 -0
  266. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_split_token.py +0 -0
  267. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_table_buffering_function.py +0 -0
  268. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_table_function_dynamic_to_string.py +0 -0
  269. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_tcp_transport.py +0 -0
  270. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_type_bounds.py +0 -0
  271. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_union_argument.py +0 -0
  272. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_wire_record_schema_parity.py +0 -0
  273. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_worker.py +0 -0
  274. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_worker_cli.py +0 -0
  275. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/test_worker_page.py +0 -0
  276. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/transactor/__init__.py +0 -0
  277. {vgi_python-0.29.4 → vgi_python-0.29.5}/tests/transactor/test_transactor.py +0 -0
  278. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/__init__.py +0 -0
  279. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_duckdb.py +0 -0
  280. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_storage_profile.py +0 -0
  281. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/__init__.py +0 -0
  282. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/accumulate/__init__.py +0 -0
  283. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/accumulate/worker.py +0 -0
  284. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/aggregate/__init__.py +0 -0
  285. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/aggregate/_common.py +0 -0
  286. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/aggregate/basic.py +0 -0
  287. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/aggregate/dynamic.py +0 -0
  288. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/aggregate/generic.py +0 -0
  289. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/aggregate/listagg.py +0 -0
  290. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/aggregate/percentile.py +0 -0
  291. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/aggregate/same_name.py +0 -0
  292. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/aggregate/secret_typed.py +0 -0
  293. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/aggregate/streaming.py +0 -0
  294. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/aggregate/varargs.py +0 -0
  295. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/aggregate/window.py +0 -0
  296. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/attach_options.py +0 -0
  297. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/bad_enum.py +0 -0
  298. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/bad_protocol.py +0 -0
  299. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/cancellable.py +0 -0
  300. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/catalog.py +0 -0
  301. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/companion.py +0 -0
  302. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/copy_from.py +0 -0
  303. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/copy_to.py +0 -0
  304. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/global_functions.py +0 -0
  305. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/http_server.py +0 -0
  306. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/narrow_bind/__init__.py +0 -0
  307. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/narrow_bind/worker.py +0 -0
  308. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/nest_tensor.py +0 -0
  309. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/orchard_catalog.py +0 -0
  310. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/projection_repro/__init__.py +0 -0
  311. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/projection_repro/worker.py +0 -0
  312. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/scalar/__init__.py +0 -0
  313. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/scalar/_common.py +0 -0
  314. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/scalar/arithmetic.py +0 -0
  315. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/scalar/bench_ladder.py +0 -0
  316. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/scalar/binary.py +0 -0
  317. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/scalar/formatting.py +0 -0
  318. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/scalar/geo.py +0 -0
  319. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/scalar/null_handling.py +0 -0
  320. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/scalar/random_demo.py +0 -0
  321. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/scalar/same_name.py +0 -0
  322. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/scalar/settings_secrets.py +0 -0
  323. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/scalar/type_info.py +0 -0
  324. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/schema_reconcile/__init__.py +0 -0
  325. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/schema_reconcile/worker.py +0 -0
  326. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/simple_writable.py +0 -0
  327. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/__init__.py +0 -0
  328. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/_common.py +0 -0
  329. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/batch_index.py +0 -0
  330. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/batch_index_broken.py +0 -0
  331. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/cache.py +0 -0
  332. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/catalog_scans.py +0 -0
  333. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/filters.py +0 -0
  334. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/late_materialization.py +0 -0
  335. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/make_series.py +0 -0
  336. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/misc.py +0 -0
  337. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/order_modes.py +0 -0
  338. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/pairs.py +0 -0
  339. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/partition_columns.py +0 -0
  340. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/partition_columns_broken.py +0 -0
  341. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/profiling_example.py +0 -0
  342. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/required_filters.py +0 -0
  343. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/same_name_cached.py +0 -0
  344. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/sequence.py +0 -0
  345. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/settings.py +0 -0
  346. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/splits.py +0 -0
  347. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/transaction_storage.py +0 -0
  348. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/tt_pushdown.py +0 -0
  349. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/typed_probe.py +0 -0
  350. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/versioned.py +0 -0
  351. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table/wide.py +0 -0
  352. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table_in_out.py +0 -0
  353. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/table_in_out_same_name.py +0 -0
  354. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/twin_catalogs.py +0 -0
  355. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/versioned.py +0 -0
  356. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/versioned_tables.py +0 -0
  357. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/worker.py +0 -0
  358. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/writable/__init__.py +0 -0
  359. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/writable/generic.py +0 -0
  360. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/writable/table.py +0 -0
  361. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/_test_fixtures/writable/worker.py +0 -0
  362. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/aggregate_function.py +0 -0
  363. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/argument_spec.py +0 -0
  364. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/arguments.py +0 -0
  365. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/attach_header.py +0 -0
  366. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/auth.py +0 -0
  367. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/cache_control.py +0 -0
  368. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/catalog/__init__.py +0 -0
  369. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/catalog/_descriptor_spec.py +0 -0
  370. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/catalog/attach_option.py +0 -0
  371. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/catalog/catalog_interface.py +0 -0
  372. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/catalog/descriptors.py +0 -0
  373. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/catalog/duckdb_statistics.py +0 -0
  374. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/catalog/secret_type.py +0 -0
  375. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/catalog/setting.py +0 -0
  376. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/catalog/storage.py +0 -0
  377. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/client/__init__.py +0 -0
  378. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/client/aggregate.py +0 -0
  379. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/client/catalog_mixin.py +0 -0
  380. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/client/cli.py +0 -0
  381. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/client/cli_catalog.py +0 -0
  382. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/client/cli_schema.py +0 -0
  383. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/client/cli_table.py +0 -0
  384. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/client/cli_transaction.py +0 -0
  385. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/client/cli_utils.py +0 -0
  386. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/client/cli_view.py +0 -0
  387. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/client/errors.py +0 -0
  388. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/codegen/__init__.py +0 -0
  389. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/codegen/_common.py +0 -0
  390. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/codegen/cpp_constants.py +0 -0
  391. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/codegen/cpp_protocol_version.py +0 -0
  392. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/codegen/cpp_request_builders.py +0 -0
  393. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/codegen/cpp_schemas.py +0 -0
  394. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/codegen/cpp_secret_protocol_version.py +0 -0
  395. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/codegen/cpp_secret_request_builders.py +0 -0
  396. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/codegen/cpp_secret_schemas.py +0 -0
  397. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/codegen/go_schemas.py +0 -0
  398. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/codegen/java_schemas.py +0 -0
  399. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/codegen/protocol_version.py +0 -0
  400. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/codegen/rust_request_builders.py +0 -0
  401. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/codegen/rust_schemas.py +0 -0
  402. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/codegen/ts_client.py +0 -0
  403. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/codegen/ts_schemas.py +0 -0
  404. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/copy_from_function.py +0 -0
  405. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/copy_to_function.py +0 -0
  406. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/exceptions.py +0 -0
  407. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/function.py +0 -0
  408. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/function_storage.py +0 -0
  409. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/function_storage_azure_sql.py +0 -0
  410. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/function_storage_cf_do.py +0 -0
  411. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/http/__init__.py +0 -0
  412. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/http/demo_storage.py +0 -0
  413. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/http/landing.html +0 -0
  414. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/http/landing_page.py +0 -0
  415. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/http/vgi-client.js +0 -0
  416. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/http/worker_page.py +0 -0
  417. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/invocation.py +0 -0
  418. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/logging_config.py +0 -0
  419. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/meta_worker.py +0 -0
  420. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/metadata.py +0 -0
  421. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/otel.py +0 -0
  422. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/profiling.py +0 -0
  423. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/protocol_version.txt +0 -0
  424. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/py.typed +0 -0
  425. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/scalar_function.py +0 -0
  426. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/schema_utils.py +0 -0
  427. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/secret_protocol.py +0 -0
  428. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/secret_service.py +0 -0
  429. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/serve.py +0 -0
  430. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/split_token.py +0 -0
  431. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/table_buffering_function.py +0 -0
  432. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/table_filter_pushdown.py +0 -0
  433. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/table_function.py +0 -0
  434. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/table_in_out_function.py +0 -0
  435. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/transactor/__init__.py +0 -0
  436. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/transactor/_duckdb_compat.py +0 -0
  437. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/transactor/client.py +0 -0
  438. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/transactor/protocol.py +0 -0
  439. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/transactor/server.py +0 -0
  440. {vgi_python-0.29.4 → vgi_python-0.29.5}/vgi/worker.py +0 -0
  441. {vgi_python-0.29.4 → vgi_python-0.29.5}/wrangler.jsonc +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: vgi-python
3
- Version: 0.29.4
3
+ Version: 0.29.5
4
4
  Summary: Vector Gateway Interface - Connect DuckDB to external programs via Apache Arrow
5
5
  Project-URL: Homepage, https://query.farm
6
6
  Project-URL: Repository, https://github.com/Query-farm/vgi-python
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "vgi-python"
3
- version = "0.29.4"
3
+ version = "0.29.5"
4
4
  description = "Vector Gateway Interface - Connect DuckDB to external programs via Apache Arrow"
5
5
  readme = "README.md"
6
6
  keywords = [
@@ -0,0 +1,240 @@
1
+ # Copyright 2025, 2026 Query Farm LLC - https://query.farm
2
+
3
+ """RPC-level exchange coverage for blended (`RowTransformFunction`) table functions.
4
+
5
+ `test_blended_metadata.py` covers `resolve_metadata()` in isolation — no worker
6
+ subprocess. This file is the first exchange-level coverage for the blended
7
+ fixtures registered in `vgi/_test_fixtures/table_in_out.py`: it drives them
8
+ through `Client.table_in_out_function(...)`'s new `parent_row_callback`, which
9
+ decodes `vgi_rpc.parent_row#b64` provenance for a 1->N / 1->0 emit (see
10
+ `vgi.protocol._decode_parent_rows`).
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import Any
16
+
17
+ import pyarrow as pa
18
+ import pytest
19
+
20
+ from tests.conftest import make_schema
21
+ from vgi.arguments import Arguments
22
+ from vgi.client import Client
23
+ from vgi.client.client import ClientError
24
+
25
+
26
+ class TestBlendedExplodeProvenance:
27
+ """`blended_explode`: 1->N fan-out with per-output-row parent_row provenance."""
28
+
29
+ def test_parent_row_pairing_is_correct(self, fixture_worker: str) -> None:
30
+ """Each output row's parent index must point at the input row that produced it.
31
+
32
+ Asserting only the final row *set* would pass even for a naive
33
+ "assume identity" implementation whenever fan-out counts differ
34
+ across rows -- so this reconstructs the exact expected
35
+ (parent_row, emitted_value) pairs in emission order and compares.
36
+ """
37
+ input_schema = make_schema([pa.field("n", pa.int64())])
38
+ counts = [0, 1, 3, 2]
39
+ batch = pa.RecordBatch.from_pydict({"n": counts}, schema=input_schema)
40
+
41
+ parent_rows_seen: list[int] = []
42
+
43
+ with Client(fixture_worker) as client:
44
+ results = list(
45
+ client.table_in_out_function(
46
+ function_name="blended_explode",
47
+ schema_name="main",
48
+ input=iter([batch]),
49
+ parent_row_callback=parent_rows_seen.extend,
50
+ )
51
+ )
52
+
53
+ assert len(results) == 1
54
+ out = results[0]
55
+ i_values = out.column("i").to_pylist()
56
+
57
+ assert len(i_values) == sum(counts)
58
+ assert len(parent_rows_seen) == len(i_values)
59
+
60
+ expected_pairs = [(row_idx, k) for row_idx, n in enumerate(counts) for k in range(n)]
61
+ actual_pairs = list(zip(parent_rows_seen, i_values, strict=True))
62
+ assert actual_pairs == expected_pairs
63
+
64
+ def test_all_rows_filtered_to_empty_result(self, fixture_worker: str) -> None:
65
+ """n=0 for every input row -> a correctly-schemed, zero-row output."""
66
+ input_schema = make_schema([pa.field("n", pa.int64())])
67
+ batch = pa.RecordBatch.from_pydict({"n": [0, 0, 0]}, schema=input_schema)
68
+
69
+ parent_rows_seen: list[list[int]] = []
70
+
71
+ with Client(fixture_worker) as client:
72
+ results = list(
73
+ client.table_in_out_function(
74
+ function_name="blended_explode",
75
+ schema_name="main",
76
+ input=iter([batch]),
77
+ parent_row_callback=parent_rows_seen.append,
78
+ )
79
+ )
80
+
81
+ assert len(results) == 1
82
+ out = results[0]
83
+ assert out.num_rows == 0
84
+ assert out.schema.names == ["i"]
85
+ assert parent_rows_seen == [[]]
86
+
87
+ def test_identity_map_when_row_counts_match(self, fixture_worker: str) -> None:
88
+ """n=1 for every row -> a 1:1 emit; the identity map is a valid decode.
89
+
90
+ `blended_explode` never encodes `vgi_rpc.parent_row` for a 1:1 emit
91
+ (the C++ reference assumes identity for that case too), so this
92
+ exercises `_decode_parent_rows`'s absent-metadata branch, not just
93
+ the explicit-array branch the other tests exercise.
94
+ """
95
+ input_schema = make_schema([pa.field("n", pa.int64())])
96
+ batch = pa.RecordBatch.from_pydict({"n": [1, 1, 1]}, schema=input_schema)
97
+
98
+ parent_rows_seen: list[int] = []
99
+
100
+ with Client(fixture_worker) as client:
101
+ results = list(
102
+ client.table_in_out_function(
103
+ function_name="blended_explode",
104
+ schema_name="main",
105
+ input=iter([batch]),
106
+ parent_row_callback=parent_rows_seen.extend,
107
+ )
108
+ )
109
+
110
+ assert len(results) == 1
111
+ assert results[0].column("i").to_pylist() == [0, 0, 0]
112
+ assert parent_rows_seen == [0, 1, 2]
113
+
114
+
115
+ class TestHostileProvenance:
116
+ """`hostile_provenance`: adversarial worker payloads must raise, never corrupt."""
117
+
118
+ @pytest.mark.parametrize("mode", ["range", "length", "base64"])
119
+ def test_malformed_provenance_raises_client_error(
120
+ self,
121
+ client_transport: Any,
122
+ mode: str,
123
+ ) -> None:
124
+ """Each hostile mode must raise ClientError on both subprocess and HTTP.
125
+
126
+ Symmetry matters here: an asymmetry between transports would mean one
127
+ parse path validates and the other doesn't -- a security bug, not
128
+ just an inconsistency.
129
+ """
130
+ input_schema = make_schema([pa.field("x", pa.int64())])
131
+ batch = pa.RecordBatch.from_pydict({"x": [1, 2, 3]}, schema=input_schema)
132
+
133
+ with client_transport() as client, pytest.raises(ClientError):
134
+ list(
135
+ client.table_in_out_function(
136
+ function_name="hostile_provenance",
137
+ schema_name="main",
138
+ input=iter([batch]),
139
+ arguments=Arguments(named={"mode": pa.scalar(mode)}),
140
+ parent_row_callback=lambda _rows: None,
141
+ )
142
+ )
143
+
144
+ def test_no_callback_no_decode_no_raise(self, fixture_worker: str) -> None:
145
+ """Without `parent_row_callback`, malformed provenance is never even decoded.
146
+
147
+ `decode_parent_rows` is derived from `parent_row_callback is not None`
148
+ -- this is the backward-compat guarantee that an ordinary (non-blended)
149
+ table-in-out caller, which never passes the callback, is completely
150
+ unaffected by this feature existing.
151
+ """
152
+ input_schema = make_schema([pa.field("x", pa.int64())])
153
+ batch = pa.RecordBatch.from_pydict({"x": [1, 2, 3]}, schema=input_schema)
154
+
155
+ with Client(fixture_worker) as client:
156
+ results = list(
157
+ client.table_in_out_function(
158
+ function_name="hostile_provenance",
159
+ schema_name="main",
160
+ input=iter([batch]),
161
+ arguments=Arguments(named={"mode": pa.scalar("range")}),
162
+ )
163
+ )
164
+
165
+ assert len(results) == 1
166
+ assert results[0].column("hv").to_pylist() == [1, 2, 3]
167
+
168
+
169
+ class TestBlendedArityAndVarargs:
170
+ """Broader coverage once the core provenance mechanism is proven."""
171
+
172
+ def test_geo_encode_two_arg_overload(self, fixture_worker: str) -> None:
173
+ """`geo_encode(lat, lon)` -- the 2-arg overload, identity 1:1 map."""
174
+ input_schema = make_schema([pa.field("latitude", pa.float64()), pa.field("longitude", pa.float64())])
175
+ batch = pa.RecordBatch.from_pydict(
176
+ {"latitude": [52.0, 48.5], "longitude": [13.0, 2.3]},
177
+ schema=input_schema,
178
+ )
179
+
180
+ parent_rows_seen: list[int] = []
181
+
182
+ with Client(fixture_worker) as client:
183
+ results = list(
184
+ client.table_in_out_function(
185
+ function_name="geo_encode",
186
+ schema_name="main",
187
+ input=iter([batch]),
188
+ arguments=Arguments(named={"precision": pa.scalar(1)}),
189
+ parent_row_callback=parent_rows_seen.extend,
190
+ )
191
+ )
192
+
193
+ assert len(results) == 1
194
+ assert results[0].column("geohash").to_pylist() == ["52.0:13.0", "48.5:2.3"]
195
+ assert parent_rows_seen == [0, 1]
196
+
197
+ def test_geo_encode_three_arg_overload(self, fixture_worker: str) -> None:
198
+ """`geo_encode(lat, lon, alt)` -- same Meta.name, resolved by input arity."""
199
+ input_schema = make_schema(
200
+ [
201
+ pa.field("latitude", pa.float64()),
202
+ pa.field("longitude", pa.float64()),
203
+ pa.field("altitude", pa.float64()),
204
+ ]
205
+ )
206
+ batch = pa.RecordBatch.from_pydict(
207
+ {"latitude": [52.0], "longitude": [13.0], "altitude": [100.0]},
208
+ schema=input_schema,
209
+ )
210
+
211
+ with Client(fixture_worker) as client:
212
+ results = list(
213
+ client.table_in_out_function(
214
+ function_name="geo_encode",
215
+ schema_name="main",
216
+ input=iter([batch]),
217
+ arguments=Arguments(named={"precision": pa.scalar(0)}),
218
+ )
219
+ )
220
+
221
+ assert len(results) == 1
222
+ assert results[0].column("geohash").to_pylist() == ["52.0:13.0:100.0"]
223
+
224
+ def test_row_sum_varargs(self, fixture_worker: str) -> None:
225
+ """`row_sum(a, b, c)` -- varargs positional input, positionally-named columns."""
226
+ input_schema = make_schema([pa.field("col0", pa.float64()), pa.field("col1", pa.float64())])
227
+ batch = pa.RecordBatch.from_pydict({"col0": [1.0, -2.0], "col1": [2.0, -3.0]}, schema=input_schema)
228
+
229
+ with Client(fixture_worker) as client:
230
+ results = list(
231
+ client.table_in_out_function(
232
+ function_name="row_sum",
233
+ schema_name="main",
234
+ input=iter([batch]),
235
+ arguments=Arguments(named={"absolute": pa.scalar(True)}),
236
+ )
237
+ )
238
+
239
+ assert len(results) == 1
240
+ assert results[0].column("row_sum").to_pylist() == [3.0, 5.0]
@@ -2569,7 +2569,7 @@ requires-dist = [
2569
2569
 
2570
2570
  [[package]]
2571
2571
  name = "vgi-python"
2572
- version = "0.29.4"
2572
+ version = "0.29.5"
2573
2573
  source = { editable = "." }
2574
2574
  dependencies = [
2575
2575
  { name = "click" },
@@ -118,6 +118,7 @@ from vgi.protocol import (
118
118
  TableBufferingProcessRequest,
119
119
  TableFunctionPlanRequest,
120
120
  VgiProtocol,
121
+ _decode_parent_rows,
121
122
  )
122
123
  from vgi.table_function import TableInOutFunctionInitPhase
123
124
 
@@ -296,6 +297,33 @@ class Client(CatalogClientMixin, AggregateClientMixin):
296
297
  return batches[0]
297
298
  return combined[0]
298
299
 
300
+ @staticmethod
301
+ def _combine_parent_rows(parent_rows_batches: list[list[int]]) -> list[int]:
302
+ """Concatenate per-output-batch parent-row lists in emission order.
303
+
304
+ Mirrors `_combine_batches` for blended row-transform provenance: when
305
+ a table-in-out call produces multiple output batches for one input
306
+ batch (a worker replying `HAVE_MORE_OUTPUT` more than once), each
307
+ sub-batch's decoded parent-row list already indexes into that same
308
+ shipped input batch (see `_decode_parent_rows`'s contract), so
309
+ concatenating the lists in the order the sub-batches were emitted is
310
+ sound — no index renumbering is needed, because `_combine_batches`
311
+ also concatenates the sub-batches themselves in that same order.
312
+
313
+ Args:
314
+ parent_rows_batches: One decoded parent-row list per output
315
+ sub-batch, in emission order.
316
+
317
+ Returns:
318
+ The concatenated parent-row list, aligned row-for-row with what
319
+ `_combine_batches` produces from the corresponding output batches.
320
+
321
+ """
322
+ combined: list[int] = []
323
+ for batch_rows in parent_rows_batches:
324
+ combined.extend(batch_rows)
325
+ return combined
326
+
299
327
  @staticmethod
300
328
  def _on_worker_log(msg: Message) -> None:
301
329
  """Forward log messages from vgi_rpc to the worker logger."""
@@ -1618,7 +1646,9 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1618
1646
  worker: WorkerConnection,
1619
1647
  input_batch: pa.RecordBatch,
1620
1648
  batch_index: int,
1621
- ) -> list[pa.RecordBatch]:
1649
+ *,
1650
+ decode_parent_rows: bool = False,
1651
+ ) -> tuple[list[pa.RecordBatch], list[list[int]] | None]:
1622
1652
  """Send a batch to a worker and collect all output batches.
1623
1653
 
1624
1654
  Sends the input batch via stream.exchange(), then checks the vgi.status
@@ -1629,19 +1659,32 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1629
1659
  worker: The worker connection to use. Must have stream initialized.
1630
1660
  input_batch: The input `RecordBatch` to send to the worker.
1631
1661
  batch_index: Index of this batch in the input sequence (for logging).
1662
+ decode_parent_rows: When True, additionally decode each output
1663
+ batch's `vgi_rpc.parent_row#b64` metadata (see
1664
+ `vgi.protocol._decode_parent_rows`) — used by blended
1665
+ row-transform table functions, where a worker's output row
1666
+ count need not match its input row count. False for every
1667
+ other table-in-out / scalar caller, which has no provenance
1668
+ concept and must not have the row-count-mismatch check inside
1669
+ `_decode_parent_rows` start firing for it.
1632
1670
 
1633
1671
  Returns:
1634
- List of output `RecordBatch`es produced by processing this input batch.
1672
+ A tuple of (output batches, per-batch decoded parent-row lists).
1673
+ The second element is `None` when `decode_parent_rows` is False —
1674
+ keeps the common path from building a list it never uses.
1635
1675
 
1636
1676
  Raises:
1637
- [`ClientError`][]: If worker.stream is None, or if the worker returns
1638
- an unexpected status, or if the RPC call fails.
1677
+ [`ClientError`][]: If worker.stream is None, if the worker returns
1678
+ an unexpected status, if the RPC call fails, or (when
1679
+ `decode_parent_rows` is True) if `vgi_rpc.parent_row#b64` is
1680
+ malformed or absent-with-mismatched-row-counts.
1639
1681
 
1640
1682
  """
1641
1683
  if worker.stream is None:
1642
1684
  raise ClientError(f"Worker {worker.worker_index} stream not initialized")
1643
1685
 
1644
1686
  output_batches: list[pa.RecordBatch] = []
1687
+ parent_rows_batches: list[list[int]] | None = [] if decode_parent_rows else None
1645
1688
 
1646
1689
  while True:
1647
1690
  _logger.debug(
@@ -1665,6 +1708,18 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1665
1708
 
1666
1709
  output_batches.append(output.batch)
1667
1710
 
1711
+ if parent_rows_batches is not None:
1712
+ try:
1713
+ parent_rows_batches.append(
1714
+ _decode_parent_rows(
1715
+ output.custom_metadata,
1716
+ output_rows=output.batch.num_rows,
1717
+ input_rows=input_batch.num_rows,
1718
+ )
1719
+ )
1720
+ except RuntimeError as e:
1721
+ raise ClientError(str(e)) from e
1722
+
1668
1723
  # Check vgi.status for table-in-out status
1669
1724
  status = None
1670
1725
  if output.custom_metadata:
@@ -1678,22 +1733,25 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1678
1733
  else:
1679
1734
  raise ClientError(f"Unexpected status from worker {worker.worker_index}: {status!r}")
1680
1735
 
1681
- return output_batches
1736
+ return output_batches, parent_rows_batches
1682
1737
 
1683
1738
  def _worker_thread_loop(
1684
1739
  self,
1685
1740
  worker: WorkerConnection,
1686
1741
  input_queue: Queue[tuple[int, pa.RecordBatch] | None],
1687
- output_queue: Queue[tuple[int, list[pa.RecordBatch]] | BaseException],
1742
+ output_queue: Queue[tuple[int, list[pa.RecordBatch], list[list[int]] | None] | BaseException],
1743
+ *,
1744
+ decode_parent_rows: bool = False,
1688
1745
  ) -> None:
1689
1746
  """Thread function that processes batches for a single worker.
1690
1747
 
1691
1748
  Runs in a dedicated thread, pulling (batch_index, batch) tuples from
1692
1749
  the input queue, processing them via _process_batch_on_worker, and
1693
- pushing (batch_index, output_batches) tuples to the output queue.
1750
+ pushing (batch_index, output_batches, parent_rows_batches) tuples to
1751
+ the output queue.
1694
1752
 
1695
1753
  When None is received from input_queue, signals thread completion by
1696
- pushing (-1, []) to output_queue and exits.
1754
+ pushing (-1, [], None) to output_queue and exits.
1697
1755
 
1698
1756
  If an exception occurs during processing, it is caught, logged, and
1699
1757
  pushed to output_queue as the exception object itself.
@@ -1703,6 +1761,8 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1703
1761
  input_queue: Thread-safe queue providing (batch_index, `RecordBatch`)
1704
1762
  tuples for processing. A None value signals end of input.
1705
1763
  output_queue: Thread-safe queue for results.
1764
+ decode_parent_rows: Forwarded to `_process_batch_on_worker` — see
1765
+ its docstring.
1706
1766
 
1707
1767
  """
1708
1768
  try:
@@ -1710,12 +1770,14 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1710
1770
  item = input_queue.get()
1711
1771
  if item is None:
1712
1772
  # End of input - signal thread completion
1713
- output_queue.put((-1, []))
1773
+ output_queue.put((-1, [], None))
1714
1774
  break
1715
1775
 
1716
1776
  batch_index, input_batch = item
1717
- outputs = self._process_batch_on_worker(worker, input_batch, batch_index)
1718
- output_queue.put((batch_index, outputs))
1777
+ outputs, parent_rows_batches = self._process_batch_on_worker(
1778
+ worker, input_batch, batch_index, decode_parent_rows=decode_parent_rows
1779
+ )
1780
+ output_queue.put((batch_index, outputs, parent_rows_batches))
1719
1781
  except Exception as e:
1720
1782
  _logger.exception("worker_thread_error worker_index=%s", worker.worker_index)
1721
1783
  output_queue.put(e)
@@ -1726,6 +1788,8 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1726
1788
  all_workers: list[WorkerConnection],
1727
1789
  first_batch: pa.RecordBatch,
1728
1790
  remaining_input: Iterator[pa.RecordBatch],
1791
+ decode_parent_rows: bool = False,
1792
+ parent_row_callback: Callable[[list[int]], None] | None = None,
1729
1793
  ) -> Generator[pa.RecordBatch]:
1730
1794
  """Distribute input batches round-robin across workers and collect output.
1731
1795
 
@@ -1738,6 +1802,15 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1738
1802
  first_batch: The first input batch, already consumed from the
1739
1803
  iterator by the calling method.
1740
1804
  remaining_input: Iterator for remaining input batches.
1805
+ decode_parent_rows: See `_process_batch_on_worker`. Forwarded to
1806
+ every worker thread; False (the default) preserves today's
1807
+ behavior exactly for every existing caller.
1808
+ parent_row_callback: Optional callback invoked once per yielded
1809
+ output batch, immediately before the yield, with that batch's
1810
+ combined parent-row list (see `_combine_parent_rows`) — the
1811
+ same "callback then yield" contract `table_function`'s
1812
+ `batch_metadata_callback` uses. Only meaningful when
1813
+ `decode_parent_rows` is True; ignored otherwise.
1741
1814
 
1742
1815
  Yields:
1743
1816
  Output `RecordBatch`es from processing. When multiple batches are
@@ -1754,7 +1827,7 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1754
1827
 
1755
1828
  # Create queues for each worker
1756
1829
  input_queues: list[Queue[tuple[int, pa.RecordBatch] | None]] = [Queue() for _ in range(num_workers)]
1757
- output_queue: Queue[tuple[int, list[pa.RecordBatch]] | BaseException] = Queue()
1830
+ output_queue: Queue[tuple[int, list[pa.RecordBatch], list[list[int]] | None] | BaseException] = Queue()
1758
1831
 
1759
1832
  # Start worker threads
1760
1833
  threads: list[threading.Thread] = []
@@ -1762,6 +1835,7 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1762
1835
  thread = threading.Thread(
1763
1836
  target=self._worker_thread_loop,
1764
1837
  args=(worker, input_queues[i], output_queue),
1838
+ kwargs={"decode_parent_rows": decode_parent_rows},
1765
1839
  daemon=True,
1766
1840
  )
1767
1841
  thread.start()
@@ -1804,12 +1878,14 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1804
1878
  raise ClientError.from_rpc_error(result) from result
1805
1879
  raise ClientError(f"Worker thread failed: {result}") from result
1806
1880
 
1807
- batch_idx, output_batches = result
1881
+ batch_idx, output_batches, parent_rows_batches = result
1808
1882
  outputs_received += 1
1809
1883
 
1810
1884
  # Combine output batches if needed
1811
1885
  combined = self._combine_batches(output_batches)
1812
1886
  if combined is not None:
1887
+ if decode_parent_rows and parent_row_callback is not None and parent_rows_batches is not None:
1888
+ parent_row_callback(self._combine_parent_rows(parent_rows_batches))
1813
1889
  yield combined
1814
1890
 
1815
1891
  _logger.debug(
@@ -1838,6 +1914,7 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1838
1914
  pushdown_filters: bytes | None = None,
1839
1915
  settings: dict[str, Any] | None = None,
1840
1916
  transaction_opaque_data: bytes | None = None,
1917
+ parent_row_callback: Callable[[list[int]], None] | None = None,
1841
1918
  ) -> Generator[pa.RecordBatch]:
1842
1919
  """Invoke a table-in-out function on the worker and stream results.
1843
1920
 
@@ -1865,6 +1942,20 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1865
1942
  settings: Optional dictionary of settings/pragmas to
1866
1943
  pass to the function.
1867
1944
  transaction_opaque_data: Optional unique identifier for the DuckDB transaction.
1945
+ parent_row_callback: Optional callback invoked once per yielded
1946
+ output batch (before finalize), immediately before the yield,
1947
+ with that batch's decoded `vgi_rpc.parent_row` provenance —
1948
+ `parent_rows[i]` is the 0-based index into the input batch
1949
+ that produced output row `i`. Passing this switches on
1950
+ provenance decoding: a batch with no `vgi_rpc.parent_row`
1951
+ metadata is only accepted when its row count matches the
1952
+ input batch's (raising `ClientError` otherwise), since a
1953
+ worker changing row count without provenance is a worker bug
1954
+ for a function that opted into this contract. Intended for
1955
+ blended row-transform functions (`RowTransformFunction`,
1956
+ `FunctionInfo.input_from_args`); leave unset for ordinary
1957
+ table-in-out functions, which have no provenance concept and
1958
+ may legitimately change row count.
1868
1959
 
1869
1960
  Yields:
1870
1961
  Output `RecordBatch`es from the function. In single-worker mode, output
@@ -1915,6 +2006,8 @@ class Client(CatalogClientMixin, AggregateClientMixin):
1915
2006
  all_workers=all_workers,
1916
2007
  first_batch=first_batch,
1917
2008
  remaining_input=input,
2009
+ decode_parent_rows=parent_row_callback is not None,
2010
+ parent_row_callback=parent_row_callback,
1918
2011
  )
1919
2012
 
1920
2013
  # Close all input streams
@@ -2299,7 +2392,7 @@ class Client(CatalogClientMixin, AggregateClientMixin):
2299
2392
  cursor=cursor,
2300
2393
  )
2301
2394
  try:
2302
- response: PlanResponse = self._primary.proxy.table_function_plan(request=plan_request) # type: ignore[assignment]
2395
+ response: PlanResponse = self._primary.proxy.table_function_plan(request=plan_request)
2303
2396
  except RpcError as e:
2304
2397
  raise ClientError.from_rpc_error(e) from e
2305
2398
 
@@ -24,6 +24,7 @@ StreamState Implementations
24
24
  from __future__ import annotations
25
25
 
26
26
  import base64
27
+ import binascii
27
28
  import contextlib
28
29
  import dataclasses
29
30
  import logging
@@ -1516,6 +1517,80 @@ def _merge_parent_rows(
1516
1517
  return merged
1517
1518
 
1518
1519
 
1520
+ def _decode_parent_rows(
1521
+ metadata: pa.KeyValueMetadata | None,
1522
+ *,
1523
+ output_rows: int,
1524
+ input_rows: int,
1525
+ ) -> list[int]:
1526
+ """Decode ``vgi_rpc.parent_row#b64`` from an exchange output batch's metadata.
1527
+
1528
+ The client-side counterpart to :func:`_merge_parent_rows` above — ported
1529
+ from the VGI DuckDB extension's own reference decoder
1530
+ (``vgi_lateral_batch_operator.cpp``'s ``DecodeParentRow``) byte-for-byte,
1531
+ since a wrong or malformed ``parent_row`` array is not a missed
1532
+ optimization the way most other worker-declared metadata is: the caller
1533
+ has no independent way to recompute which output row came from which
1534
+ input row, so an unvalidated value here would produce silent data
1535
+ corruption (or an out-of-bounds read) rather than a merely suboptimal
1536
+ result. Every check below is load-bearing, not defensive polish.
1537
+
1538
+ Args:
1539
+ metadata: The output batch's ``custom_metadata``, or ``None``.
1540
+ output_rows: Row count of the output batch this metadata came with.
1541
+ input_rows: Row count of the input batch shipped for this call.
1542
+
1543
+ Returns:
1544
+ ``parent_rows[i]`` = the 0-based index into the input batch that
1545
+ produced output row ``i``. Always a concrete list — absent metadata
1546
+ synthesizes the identity map (only valid when ``output_rows ==
1547
+ input_rows``, else it's a raise, not a ``None``; see below).
1548
+
1549
+ Raises:
1550
+ RuntimeError: If the metadata is malformed, or if it's absent but
1551
+ the row counts don't match (a worker emitting a different row
1552
+ count without provenance is a worker bug, not something to
1553
+ silently paper over).
1554
+
1555
+ """
1556
+ if output_rows == 0:
1557
+ # Unambiguous regardless of input_rows or whether metadata is present:
1558
+ # a 0-row emit is a legitimate 1->0 filter (every input row dropped),
1559
+ # and _merge_parent_rows never encodes vgi_rpc.parent_row for an empty
1560
+ # emit in the first place (see its own `batch.num_rows == 0` branch) --
1561
+ # so this must be checked BEFORE the absent-metadata row-count-mismatch
1562
+ # check below, or a fully-filtered batch would wrongly look like a
1563
+ # worker bug.
1564
+ return []
1565
+
1566
+ raw_b64 = metadata.get(b"vgi_rpc.parent_row#b64") if metadata else None
1567
+ if raw_b64 is None:
1568
+ if output_rows != input_rows:
1569
+ raise RuntimeError(
1570
+ f"worker returned {output_rows} output row(s) for {input_rows} input row(s) "
1571
+ "without vgi_rpc.parent_row provenance; a fan-out (1->N) or filtering (1->0) "
1572
+ "blended row-transform emit must carry per-output-row parent indices"
1573
+ )
1574
+ return list(range(output_rows))
1575
+
1576
+ try:
1577
+ raw = base64.b64decode(raw_b64, validate=True)
1578
+ except (binascii.Error, ValueError) as e:
1579
+ raise RuntimeError(f"vgi_rpc.parent_row#b64 is not valid base64: {e}") from e
1580
+
1581
+ expected_len = output_rows * 4
1582
+ if len(raw) != expected_len:
1583
+ raise RuntimeError(
1584
+ f"vgi_rpc.parent_row is {len(raw)} byte(s) for {output_rows} output row(s) (expected {expected_len})"
1585
+ )
1586
+
1587
+ values = struct.unpack(f"<{output_rows}i", raw)
1588
+ for i, v in enumerate(values):
1589
+ if v < 0 or v >= input_rows:
1590
+ raise RuntimeError(f"parent_row[{i}] = {v} is out of range [0, {input_rows})")
1591
+ return list(values)
1592
+
1593
+
1519
1594
  def _merge_cache_control(
1520
1595
  *,
1521
1596
  cache_control: CacheControl | None,
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