mq-bridge-py-basic 0.3.3__tar.gz → 0.3.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/.github/workflows/benchmark.yml +22 -0
  2. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/CONFIGURATION.md +16 -0
  3. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/Cargo.lock +84 -4
  4. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/Cargo.toml +22 -3
  5. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/PKG-INFO +19 -4
  6. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/README.md +48 -20
  7. mq_bridge_py_basic-0.3.4/benches/ETL_BENCHMARKS.md +107 -0
  8. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/mq-bridge.schema.json +362 -2
  9. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/mq_bridge/__init__.pyi +6 -1
  10. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/mq_bridge/config.py +6 -0
  11. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/mq_bridge/config.pyi +50 -1
  12. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/README.md +18 -3
  13. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/src/lib.rs +60 -0
  14. mq_bridge_py_basic-0.3.4/src/checkpoint.rs +520 -0
  15. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/command_handler.rs +80 -90
  16. mq_bridge_py_basic-0.3.4/src/endpoints/clickhouse.rs +706 -0
  17. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/file.rs +353 -17
  18. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/kafka.rs +80 -44
  19. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/mod.rs +171 -9
  20. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/mongodb.rs +972 -3
  21. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/mqtt.rs +61 -0
  22. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/nats.rs +15 -0
  23. mq_bridge_py_basic-0.3.4/src/endpoints/poll.rs +64 -0
  24. mq_bridge_py_basic-0.3.4/src/endpoints/postgres/mod.rs +422 -0
  25. mq_bridge_py_basic-0.3.4/src/endpoints/postgres/pgoutput/decoder.rs +772 -0
  26. mq_bridge_py_basic-0.3.4/src/endpoints/postgres/pgoutput/messages.rs +246 -0
  27. mq_bridge_py_basic-0.3.4/src/endpoints/postgres/pgoutput/mod.rs +106 -0
  28. mq_bridge_py_basic-0.3.4/src/endpoints/postgres/pgoutput/registry.rs +123 -0
  29. mq_bridge_py_basic-0.3.4/src/endpoints/postgres/pgoutput/values.rs +516 -0
  30. mq_bridge_py_basic-0.3.4/src/endpoints/postgres/replication.rs +258 -0
  31. mq_bridge_py_basic-0.3.4/src/endpoints/postgres/state.rs +67 -0
  32. mq_bridge_py_basic-0.3.4/src/endpoints/sqlx.rs +2576 -0
  33. mq_bridge_py_basic-0.3.4/src/lib.rs +77 -0
  34. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/models.rs +273 -1
  35. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/publisher.rs +11 -0
  36. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/route.rs +288 -0
  37. mq_bridge_py_basic-0.3.4/tests/integration/clickhouse.rs +178 -0
  38. mq_bridge_py_basic-0.3.4/tests/integration/docker-compose/clickhouse.yml +21 -0
  39. mq_bridge_py_basic-0.3.4/tests/integration/docker-compose/postgres_cdc.yml +29 -0
  40. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/mod.rs +4 -0
  41. mq_bridge_py_basic-0.3.4/tests/integration/postgres.rs +376 -0
  42. mq_bridge_py_basic-0.3.4/tests/integration/postgres_cdc.rs +209 -0
  43. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration_test.rs +53 -0
  44. mq_bridge_py_basic-0.3.3/src/endpoints/sqlx.rs +0 -993
  45. mq_bridge_py_basic-0.3.3/src/lib.rs +0 -40
  46. mq_bridge_py_basic-0.3.3/tests/integration/postgres.rs +0 -202
  47. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  48. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  49. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  50. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/.github/workflows/ci.yml +0 -0
  51. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/.github/workflows/ibm-mq.yml +0 -0
  52. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/.github/workflows/node.yml +0 -0
  53. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/.github/workflows/publish-node.yml +0 -0
  54. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/.github/workflows/publish-python.yml +0 -0
  55. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/.github/workflows/python.yml +0 -0
  56. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/.github/workflows/release.yml +0 -0
  57. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/.github/workflows/test-matrix.yml +0 -0
  58. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/.gitignore +0 -0
  59. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/ARCHITECTURE.md +0 -0
  60. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/CONTRIBUTING.md +0 -0
  61. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/LICENSE +0 -0
  62. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/LICENSE.python +0 -0
  63. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/benches/performance_bench.rs +0 -0
  64. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/benches/router_bench.rs +0 -0
  65. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/bindings-common/Cargo.toml +0 -0
  66. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/bindings-common/src/lib.rs +0 -0
  67. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/bindings-common/src/logging.rs +0 -0
  68. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/build.rs +0 -0
  69. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/claude.md +0 -0
  70. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/examples/ipc_worker_queue.yaml +0 -0
  71. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/mq_bridge/__init__.py +0 -0
  72. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/mq_bridge/py.typed +0 -0
  73. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/package-lock.json +0 -0
  74. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/pyproject.toml +0 -0
  75. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/Cargo.toml +0 -0
  76. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/LICENSE.python +0 -0
  77. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/analysis/bench_http_native.py +0 -0
  78. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/example.py +0 -0
  79. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/examples/bench_memory.py +0 -0
  80. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/examples/bench_memory.yaml +0 -0
  81. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/examples/consume_pull.py +0 -0
  82. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/examples/dlt_source.py +0 -0
  83. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/examples/json_route.py +0 -0
  84. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/examples/memory.yaml +0 -0
  85. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/examples/raw_route.py +0 -0
  86. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/pyproject-basic.toml +0 -0
  87. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/pyproject-reduced.toml +0 -0
  88. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/scripts/gen_config_types.py +0 -0
  89. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/tests/test_config_types.py +0 -0
  90. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/tests/test_logging.py +0 -0
  91. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/tests/test_packaging.py +0 -0
  92. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/tests/test_performance_smoke.py +0 -0
  93. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/tests/test_public_api.py +0 -0
  94. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/tests/test_routes.py +0 -0
  95. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/python/mq-bridge-py/uv.lock +0 -0
  96. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/analysis/HTTP_STREAM_ENDPOINT_PLAN.md +0 -0
  97. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/analysis/benches/metadata_bench.rs +0 -0
  98. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/analysis/debug_ibm_mq_tls.rs +0 -0
  99. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/analysis/http/analyze_xctrace_time_profile.py +0 -0
  100. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/analysis/http/http_flamegraph.py +0 -0
  101. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/analysis/http/mq_bridge_http_profile.rs +0 -0
  102. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/analysis/http/run_http_ladder.sh +0 -0
  103. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/ci/format_bench_output.sh +0 -0
  104. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/httparena/README.md +0 -0
  105. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/httparena/frameworks/mq-bridge-py/Dockerfile +0 -0
  106. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/httparena/frameworks/mq-bridge-py/meta.json +0 -0
  107. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/httparena/frameworks/mq-bridge-py/server.py +0 -0
  108. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/techempower/Python/mq-bridge-py/README.md +0 -0
  109. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/techempower/Python/mq-bridge-py/benchmark_config.json +0 -0
  110. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/techempower/Python/mq-bridge-py/mq-bridge-py.dockerfile +0 -0
  111. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/techempower/Python/mq-bridge-py/server.py +0 -0
  112. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/techempower/README.md +0 -0
  113. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/techempower/postgres.yml +0 -0
  114. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/techempower/seed.sql +0 -0
  115. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/techempower/verify.sh +0 -0
  116. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/the-benchmarker/README.md +0 -0
  117. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/the-benchmarker/python/mq-bridge-py/config.yaml +0 -0
  118. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/the-benchmarker/python/mq-bridge-py/pyproject.toml +0 -0
  119. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/scripts/the-benchmarker/python/mq-bridge-py/server.py +0 -0
  120. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/canonical_message.rs +0 -0
  121. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/connection_registry.rs +0 -0
  122. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/amqp.rs +0 -0
  123. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/aws.rs +0 -0
  124. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/fanout.rs +0 -0
  125. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/grpc.proto +0 -0
  126. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/grpc.rs +0 -0
  127. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/http.rs +0 -0
  128. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/http_stream.rs +0 -0
  129. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/ibm_mq.rs +0 -0
  130. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/memory/endpoint.rs +0 -0
  131. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/memory/ipc_unix.rs +0 -0
  132. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/memory/ipc_windows.rs +0 -0
  133. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/memory/memory_transport.rs +0 -0
  134. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/memory/mod.rs +0 -0
  135. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/memory/transport.rs +0 -0
  136. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/null.rs +0 -0
  137. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/reader.rs +0 -0
  138. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/redis_streams.rs +0 -0
  139. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/response.rs +0 -0
  140. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/sled.rs +0 -0
  141. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/static_endpoint.rs +0 -0
  142. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/stream_buffer.rs +0 -0
  143. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/switch.rs +0 -0
  144. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/websocket.rs +0 -0
  145. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/endpoints/zeromq.rs +0 -0
  146. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/errors.rs +0 -0
  147. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/event_handler.rs +0 -0
  148. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/event_store.rs +0 -0
  149. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/extensions.rs +0 -0
  150. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/middleware/buffer.rs +0 -0
  151. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/middleware/cookie_jar.rs +0 -0
  152. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/middleware/deduplication.rs +0 -0
  153. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/middleware/delay.rs +0 -0
  154. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/middleware/dlq.rs +0 -0
  155. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/middleware/limiter.rs +0 -0
  156. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/middleware/metrics.rs +0 -0
  157. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/middleware/mod.rs +0 -0
  158. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/middleware/random_panic.rs +0 -0
  159. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/middleware/retry.rs +0 -0
  160. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/middleware/weak_join.rs +0 -0
  161. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/outcomes.rs +0 -0
  162. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/response.rs +0 -0
  163. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/test_utils.rs +0 -0
  164. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/traits.rs +0 -0
  165. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/src/type_handler.rs +0 -0
  166. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/README.md +0 -0
  167. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/armature_integration.rs +0 -0
  168. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/amqp.rs +0 -0
  169. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/aws.rs +0 -0
  170. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/config.toml +0 -0
  171. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/amqp.yml +0 -0
  172. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/aws.yml +0 -0
  173. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/ibm-mq-certs/client.crl +0 -0
  174. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/ibm-mq-certs/client.kdb +0 -0
  175. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/ibm-mq-certs/client.p12 +0 -0
  176. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/ibm-mq-certs/client.rdb +0 -0
  177. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/ibm-mq-certs/client.sth +0 -0
  178. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/ibm_mq.yml +0 -0
  179. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/ibm_mq_tls.yml +0 -0
  180. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/kafka-tls.yml +0 -0
  181. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/kafka.yml +0 -0
  182. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/mariadb.yml +0 -0
  183. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/mongodb-replica.yml +0 -0
  184. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/mongodb-tls.yml +0 -0
  185. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/mongodb.yml +0 -0
  186. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/mosquitto.conf +0 -0
  187. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/mosquitto_performance.conf +0 -0
  188. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/mosquitto_persistence.conf +0 -0
  189. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/mq_init.mqsc +0 -0
  190. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/mq_init_tls.mqsc +0 -0
  191. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/mqtt.yml +0 -0
  192. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/mqtt_performance.yml +0 -0
  193. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/mysql.yml +0 -0
  194. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/nats.yml +0 -0
  195. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/postgres.yml +0 -0
  196. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/docker-compose/redis.yml +0 -0
  197. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/file.rs +0 -0
  198. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/grpc.rs +0 -0
  199. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/grpc_tls.rs +0 -0
  200. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/http.rs +0 -0
  201. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/http_tls.rs +0 -0
  202. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/ibm_mq.rs +0 -0
  203. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/ibm_mq_tls.rs +0 -0
  204. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/ipc.rs +0 -0
  205. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/kafka.rs +0 -0
  206. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/kafka_tls.rs +0 -0
  207. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/logic_test.rs +0 -0
  208. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/mariadb.rs +0 -0
  209. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/memory.rs +0 -0
  210. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/mongodb.rs +0 -0
  211. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/mongodb_raw.rs +0 -0
  212. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/mongodb_tls.rs +0 -0
  213. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/mqtt.rs +0 -0
  214. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/mysql.rs +0 -0
  215. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/nats.rs +0 -0
  216. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/performance_static.rs +0 -0
  217. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/redis_streams.rs +0 -0
  218. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/route.rs +0 -0
  219. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/scripts/gen_certs.sh +0 -0
  220. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/sqlite.rs +0 -0
  221. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/tls_helpers.rs +0 -0
  222. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/websocket.rs +0 -0
  223. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/integration/zeromq.rs +0 -0
  224. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/memory_leak_test.rs +0 -0
  225. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/ref_test.rs +0 -0
  226. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/sqlite_test.rs +0 -0
  227. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/tls_example.rs +0 -0
  228. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/unit_tests.md +0 -0
  229. {mq_bridge_py_basic-0.3.3 → mq_bridge_py_basic-0.3.4}/tests/websocket_test.rs +0 -0
@@ -179,6 +179,11 @@ jobs:
179
179
  name: Collect Benchmark Results
180
180
  needs: [benchmark-streams, benchmark-database, benchmark-dockerless]
181
181
  runs-on: ubuntu-latest
182
+ # Serialize gh-pages pushes so overlapping runs don't race the Pages build
183
+ # (concurrent pushes make GitHub cancel the superseded deployment -> "failed").
184
+ concurrency:
185
+ group: gh-pages-publish
186
+ cancel-in-progress: false
182
187
  permissions:
183
188
  contents: write
184
189
  deployments: write
@@ -248,7 +253,24 @@ jobs:
248
253
  tool: 'cargo'
249
254
  output-file-path: output.txt
250
255
  github-token: ${{ secrets.GITHUB_TOKEN }}
256
+ # Push on both main and dev so dev commits appear in the benchmark graph.
257
+ # This mints a gh-pages deployment per run; the next step prunes old ones.
251
258
  auto-push: true
252
259
  alert-threshold: '40000%'
253
260
  comment-on-alert: true
254
261
  fail-on-alert: false
262
+
263
+ # Keep only the most recent gh-pages deployments so the environment's
264
+ # deployment list doesn't grow unbounded (one is minted per push above).
265
+ - name: Prune old gh-pages deployments
266
+ env:
267
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
268
+ KEEP: 20
269
+ run: |
270
+ gh api "repos/${{ github.repository }}/deployments?environment=github-pages&per_page=100" --paginate -q '.[].id' \
271
+ | tail -n +$((KEEP + 1)) \
272
+ | while read -r id; do
273
+ gh api -X POST "repos/${{ github.repository }}/deployments/$id/statuses" -f state=inactive >/dev/null 2>&1 \
274
+ || echo "::warning::failed to mark deployment $id inactive; attempting delete anyway"
275
+ gh api -X DELETE "repos/${{ github.repository }}/deployments/$id" >/dev/null 2>&1 && echo "pruned $id"
276
+ done
@@ -139,6 +139,22 @@ For example, to set the Kafka topic for the `kafka_to_nats` route:
139
139
  ```sh
140
140
  export MQB__KAFKA_TO_NATS__INPUT__KAFKA__TOPIC="my-other-topic"
141
141
  ```
142
+ #### Postgres CDC example
143
+
144
+ ```yaml
145
+ orders_cdc:
146
+ input:
147
+ postgres_cdc:
148
+ url: "postgres://user:pass@localhost:5432/app"
149
+ publication: "orders_pub" # CREATE PUBLICATION orders_pub FOR TABLE orders;
150
+ slot_name: "mqb_orders" # created if missing (permanent slot, resumable)
151
+ output:
152
+ nats:
153
+ subject: "orders.changes"
154
+ url: "nats://localhost:4222"
155
+ ```
156
+
157
+ Each change arrives as a `CanonicalMessage` whose payload is the flat row and whose `postgres.operation` metadata marks the operation — the same convention as MongoDB CDC, so typed handlers work identically across both. The replication transport uses the published [`pgwire-replication`](https://crates.io/crates/pgwire-replication) crate.
142
158
 
143
159
  ### NATS JetStream Notes
144
160
 
@@ -218,6 +218,18 @@ dependencies = [
218
218
  "tokio",
219
219
  ]
220
220
 
221
+ [[package]]
222
+ name = "async-compression"
223
+ version = "0.4.42"
224
+ source = "registry+https://github.com/rust-lang/crates.io-index"
225
+ checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac"
226
+ dependencies = [
227
+ "compression-codecs",
228
+ "compression-core",
229
+ "pin-project-lite",
230
+ "tokio",
231
+ ]
232
+
221
233
  [[package]]
222
234
  name = "async-executor"
223
235
  version = "1.14.0"
@@ -1097,6 +1109,23 @@ dependencies = [
1097
1109
  "tokio-util",
1098
1110
  ]
1099
1111
 
1112
+ [[package]]
1113
+ name = "compression-codecs"
1114
+ version = "0.4.38"
1115
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1116
+ checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf"
1117
+ dependencies = [
1118
+ "compression-core",
1119
+ "flate2",
1120
+ "memchr",
1121
+ ]
1122
+
1123
+ [[package]]
1124
+ name = "compression-core"
1125
+ version = "0.4.32"
1126
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1127
+ checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789"
1128
+
1100
1129
  [[package]]
1101
1130
  name = "concurrent-queue"
1102
1131
  version = "2.5.0"
@@ -2952,6 +2981,12 @@ dependencies = [
2952
2981
  "digest 0.11.3",
2953
2982
  ]
2954
2983
 
2984
+ [[package]]
2985
+ name = "md5"
2986
+ version = "0.7.0"
2987
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2988
+ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
2989
+
2955
2990
  [[package]]
2956
2991
  name = "memchr"
2957
2992
  version = "2.8.2"
@@ -3119,7 +3154,7 @@ dependencies = [
3119
3154
 
3120
3155
  [[package]]
3121
3156
  name = "mq-bridge"
3122
- version = "0.3.3"
3157
+ version = "0.3.4"
3123
3158
  dependencies = [
3124
3159
  "anyhow",
3125
3160
  "arc-swap",
@@ -3130,6 +3165,7 @@ dependencies = [
3130
3165
  "aws-sdk-sns",
3131
3166
  "aws-sdk-sqs",
3132
3167
  "base64",
3168
+ "byteorder",
3133
3169
  "bytes",
3134
3170
  "chrono",
3135
3171
  "config",
@@ -3138,6 +3174,7 @@ dependencies = [
3138
3174
  "flate2",
3139
3175
  "futures",
3140
3176
  "h2 0.4.14",
3177
+ "hex",
3141
3178
  "http-body 1.0.1",
3142
3179
  "http-body-util",
3143
3180
  "hyper 1.10.1",
@@ -3151,6 +3188,8 @@ dependencies = [
3151
3188
  "mq-bridge",
3152
3189
  "mqi",
3153
3190
  "once_cell",
3191
+ "percent-encoding",
3192
+ "pgwire-replication",
3154
3193
  "prost",
3155
3194
  "protoc-bin-vendored",
3156
3195
  "rand 0.10.1",
@@ -3188,7 +3227,7 @@ dependencies = [
3188
3227
 
3189
3228
  [[package]]
3190
3229
  name = "mq-bridge-bindings-common"
3191
- version = "0.3.3"
3230
+ version = "0.3.4"
3192
3231
  dependencies = [
3193
3232
  "anyhow",
3194
3233
  "fast-uuid-v7",
@@ -3202,7 +3241,7 @@ dependencies = [
3202
3241
 
3203
3242
  [[package]]
3204
3243
  name = "mq-bridge-node"
3205
- version = "0.3.3"
3244
+ version = "0.3.4"
3206
3245
  dependencies = [
3207
3246
  "anyhow",
3208
3247
  "async-trait",
@@ -3223,7 +3262,7 @@ dependencies = [
3223
3262
 
3224
3263
  [[package]]
3225
3264
  name = "mq-bridge-py"
3226
- version = "0.3.3"
3265
+ version = "0.3.4"
3227
3266
  dependencies = [
3228
3267
  "anyhow",
3229
3268
  "async-trait",
@@ -3689,6 +3728,28 @@ dependencies = [
3689
3728
  "indexmap",
3690
3729
  ]
3691
3730
 
3731
+ [[package]]
3732
+ name = "pgwire-replication"
3733
+ version = "0.3.2"
3734
+ source = "registry+https://github.com/rust-lang/crates.io-index"
3735
+ checksum = "7eec6334b2466a0163bd91c29e06a25b391c155231934aee80381b99560e240e"
3736
+ dependencies = [
3737
+ "anyhow",
3738
+ "base64",
3739
+ "bytes",
3740
+ "hmac 0.12.1",
3741
+ "md5",
3742
+ "rand 0.9.4",
3743
+ "rustls 0.23.40",
3744
+ "sha2 0.10.9",
3745
+ "thiserror 1.0.69",
3746
+ "tokio",
3747
+ "tokio-rustls 0.26.4",
3748
+ "tokio-util",
3749
+ "tracing",
3750
+ "webpki-roots 0.26.11",
3751
+ ]
3752
+
3692
3753
  [[package]]
3693
3754
  name = "phf"
3694
3755
  version = "0.13.1"
@@ -4496,6 +4557,8 @@ dependencies = [
4496
4557
  "rustls 0.23.40",
4497
4558
  "rustls-pki-types",
4498
4559
  "rustls-platform-verifier",
4560
+ "serde",
4561
+ "serde_urlencoded",
4499
4562
  "sync_wrapper",
4500
4563
  "tokio",
4501
4564
  "tokio-rustls 0.26.4",
@@ -5027,6 +5090,18 @@ dependencies = [
5027
5090
  "serde_core",
5028
5091
  ]
5029
5092
 
5093
+ [[package]]
5094
+ name = "serde_urlencoded"
5095
+ version = "0.7.1"
5096
+ source = "registry+https://github.com/rust-lang/crates.io-index"
5097
+ checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
5098
+ dependencies = [
5099
+ "form_urlencoded",
5100
+ "itoa",
5101
+ "ryu",
5102
+ "serde",
5103
+ ]
5104
+
5030
5105
  [[package]]
5031
5106
  name = "serde_with"
5032
5107
  version = "3.21.0"
@@ -5948,12 +6023,17 @@ version = "0.6.11"
5948
6023
  source = "registry+https://github.com/rust-lang/crates.io-index"
5949
6024
  checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
5950
6025
  dependencies = [
6026
+ "async-compression",
5951
6027
  "bitflags 2.13.0",
5952
6028
  "bytes",
6029
+ "futures-core",
5953
6030
  "futures-util",
5954
6031
  "http 1.4.2",
5955
6032
  "http-body 1.0.1",
6033
+ "http-body-util",
5956
6034
  "pin-project-lite",
6035
+ "tokio",
6036
+ "tokio-util",
5957
6037
  "tower",
5958
6038
  "tower-layer",
5959
6039
  "tower-service",
@@ -16,7 +16,7 @@ members = ["bindings-common", "python/mq-bridge-py"]
16
16
  resolver = "2"
17
17
 
18
18
  [workspace.package]
19
- version = "0.3.3"
19
+ version = "0.3.4"
20
20
 
21
21
  [workspace.dependencies]
22
22
  anyhow = "1.0"
@@ -134,6 +134,11 @@ aws-sdk-sns = { version = "1.93", optional = true, default-features = false, fea
134
134
  # SQLx
135
135
  sqlx = { version = "0.9", features = ["runtime-tokio", "tls-rustls", "postgres", "mysql", "sqlite", "any"], optional = true }
136
136
 
137
+ # ClickHouse — talks the ClickHouse HTTP interface directly (JSONEachRow in/out).
138
+ # reqwest is used rather than the typed `clickhouse` crate because that crate is
139
+ # RowBinary/`Row`-derive only and cannot round-trip arbitrary dynamic JSON payloads.
140
+ reqwest = { version = "0.13", default-features = false, features = ["rustls", "http2", "charset", "query", "gzip"], optional = true }
141
+
137
142
  # ZeroMQ
138
143
  zeromq = { version = "0.6", optional = true }
139
144
 
@@ -149,6 +154,13 @@ prost = { version = "0.14", optional = true, default-features = false, features
149
154
  tokio-stream = { version = "0.1", optional = true, features = ["net"] }
150
155
 
151
156
  url = { version = "2.5", optional = true }
157
+ percent-encoding = { version = "2.3", optional = true }
158
+
159
+ # Postgres CDC (logical replication / pgoutput). pgwire-replication is a
160
+ # narrowly-scoped wire-protocol client (COPY-both, LSN/standby, TLS/SCRAM) that
161
+ # deliberately avoids tokio-postgres; byteorder backs the pgoutput byte decoder.
162
+ pgwire-replication = { version = "0.3", optional = true, default-features = false, features = ["tls-rustls", "scram", "md5"] }
163
+ byteorder = { version = "1.5", optional = true }
152
164
 
153
165
  # Generate JSON schema
154
166
  schemars = { version = "1.2", optional = true }
@@ -168,7 +180,7 @@ middleware = ["metrics", "dedup"]
168
180
  # runtime the client is loaded lazily only when an ibm-mq endpoint is opened
169
181
  # (clear error if the client isn't installed). Use `ibm-mq-static` instead for
170
182
  # static linking.
171
- full = ["middleware", "mongodb", "kafka", "amqp", "nats", "mqtt", "http", "aws", "zeromq", "redis-streams", "sled", "grpc", "sqlx", "websocket", "rustls-aws-lc", "ibm-mq"]
183
+ full = ["middleware", "mongodb", "kafka", "amqp", "nats", "mqtt", "http", "aws", "zeromq", "redis-streams", "sled", "grpc", "sqlx", "clickhouse", "websocket", "postgres-cdc", "rustls-aws-lc", "ibm-mq"]
172
184
 
173
185
  # Functionality features
174
186
  dedup = ["sled"]
@@ -194,7 +206,7 @@ schema = ["dep:schemars"]
194
206
  kafka = ["rdkafka"]
195
207
  amqp = ["lapin", "rustls", "rustls-pemfile", "url"]
196
208
  nats = ["async-nats", "rustls", "rustls-pemfile"]
197
- mongodb = ["dep:mongodb"]
209
+ mongodb = ["dep:mongodb", "url"]
198
210
  mqtt = ["rumqttc", "rustls", "tokio-rustls", "rustls-pemfile", "url"]
199
211
  http = ["dep:arc-swap", "hyper", "hyper-util", "hyper-rustls", "http-body-util", "dep:http-body", "rustls", "tokio-rustls", "rustls-pemfile", "webpki-roots", "h2", "dep:flate2", "dep:base64"]
200
212
  websocket = ["dep:tokio-websockets"]
@@ -216,6 +228,11 @@ redis-streams = ["dep:redis"]
216
228
  # Note: The grpc feature requires protobuf-src which can significantly increase build times.
217
229
  grpc = ["tonic", "prost", "tokio-stream", "tonic-prost", "tonic-prost-build", "protoc-bin-vendored", "rustls"]
218
230
  sqlx = ["dep:sqlx", "url"]
231
+ clickhouse = ["dep:reqwest", "dep:flate2", "url"]
232
+ # Postgres logical-replication CDC (pgoutput). Reuses the sqlx dependency for the
233
+ # slot-lifecycle control plane; the streaming wire protocol is pgwire-replication.
234
+ # Needs Rust 1.88 (pgwire-replication MSRV), like the amqp feature gate.
235
+ postgres-cdc = ["dep:pgwire-replication", "dep:byteorder", "sqlx", "dep:base64", "url", "dep:percent-encoding"]
219
236
 
220
237
  [dev-dependencies]
221
238
  config = { version = "0.15" }
@@ -223,6 +240,8 @@ serde_yaml_ng = "0.10"
223
240
  tempfile = "3.10"
224
241
  chrono = "0.4"
225
242
  metrics-util = "0.20"
243
+ # pgoutput decoder unit tests decode hex-encoded wire fixtures.
244
+ hex = "0.4"
226
245
  criterion = { version = "0.8", features = ["async", "async_tokio"] }
227
246
  # Used by the router_bench microbench to model the proposed lock-free route table.
228
247
  arc-swap = "1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mq-bridge-py-basic
3
- Version: 0.3.3
3
+ Version: 0.3.4
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: License :: OSI Approved :: MIT License
6
6
  Classifier: Programming Language :: Python :: 3
@@ -113,9 +113,24 @@ schema = config_schema() # the JSON Schema as a dict
113
113
  ```
114
114
 
115
115
  `config_schema()` is handy for editor validation of YAML configs too — dump it
116
- to a file and point your `# yaml-language-server: $schema=` line at it. The types
117
- are regenerated with `uv run python scripts/gen_config_types.py` (a test fails if
118
- they drift from the schema).
116
+ to a file and point your `# yaml-language-server: $schema=` line at it.
117
+
118
+ ### Regenerating the config types
119
+
120
+ `mq_bridge/config.pyi` and `mq_bridge/config.py` are **generated — do not edit by
121
+ hand**. Regenerate them whenever the Rust config models change (e.g. adding a
122
+ field or a new endpoint). Because the generator reads the schema from the
123
+ compiled extension, you must rebuild first:
124
+
125
+ ```bash
126
+ # from python/mq-bridge-py/
127
+ uv run maturin develop # rebuild the extension
128
+ uv run --no-sync python scripts/gen_config_types.py # regenerate config.pyi / config.py
129
+ ```
130
+
131
+ Then commit the updated `config.pyi`/`config.py`. `tests/test_config_types.py`
132
+ asserts the checked-in output matches the schema, so CI (the "Python package
133
+ smoke test" job) fails if you skip this after a models change.
119
134
 
120
135
  ## Running a route
121
136
 
@@ -19,6 +19,22 @@
19
19
 
20
20
  It is not only a forwarder. A route can transform, filter, fan out, retry, rate-limit, deduplicate, or turn a request into a response before the message reaches the next system. The core is built on Tokio and keeps the transport details at the edge, so application code can mostly work with `CanonicalMessage`s and handlers.
21
21
 
22
+ ## Move data from A to B — inside your own service
23
+
24
+ If you need to move data reliably between systems and you write code (Rust, Python, or Node), `mq-bridge` is a strong default. It is a **library you embed**, not a daemon or control plane you operate.
25
+
26
+ **Prefer not to write code?** [`mq-bridge-app`](https://github.com/marcomq/mq-bridge-app) runs the exact same engine as a **standalone, zero-code ETL service** configured entirely by **YAML or environment variables** — move data from A to B without writing a line. It ships a **Postman-style UI** to build, send, and inspect messages against a route, and can **import Postman collections and AsyncAPI documents** to scaffold routes and endpoints for you.
27
+
28
+ * **16+ transports, one API**: Kafka, NATS, AMQP (RabbitMQ), MQTT, MongoDB, **Postgres CDC** (logical replication), PostgreSQL / MySQL / SQLite (SQLx), ClickHouse, HTTP, WebSocket, gRPC, ZeroMQ, Redis Streams, AWS SQS/SNS, IBM MQ, files, and in-memory channels — all behind the same `receive_batch` / `send_batch` shape.
29
+ * **Change Data Capture**: stream row-level changes from **Postgres** (logical replication / `pgoutput`) and **MongoDB** (change streams) as flat rows with an operation marker.
30
+ * **Restart-safe delivery**: batch-aware ack/nack with commit sequencing for cumulative-ack brokers; the integration suite shows **no data loss during in-flight broker restarts**, including a Postgres CDC restart-safety test.
31
+ * **Reliability middleware, not a framework**: retries, dead-letter queues, deduplication, rate limiting, and cookie/session persistence wrap any endpoint.
32
+ * **Polyglot on one engine**: the same Rust core ships as native **Python** and **Node.js** bindings; routing, batching, and broker I/O stay in Rust.
33
+ * **TLS everywhere, one config shape**: a single `TlsConfig` block (CA bundle, client cert/key for mTLS, insecure-skip) is reused across transports.
34
+ * **Self-hosted, no daemon**: generate config in the optional UI, paste it into your code, run it in-process. No hosted control plane, no separate scheduler.
35
+
36
+ > **Throughput.** In our own benchmarks, the same engine — driven the zero-code way through [`mq-bridge-app`](https://github.com/marcomq/mq-bridge-app) — sustained ~303,000 rows/s copying 1,000,000 rows from Postgres to JSONL on commodity hardware, keeping it well in the range of dedicated data-movement tools. On a CSV→JSONL file conversion (1,000,000 mixed-type rows, ~116 MiB), it sustained **833,333 rows/s**, about **~43x faster than Meltano** (`tap-csv` → `target-jsonl`, ~19,500 rows/s). Full setup, methodology, and the exact parameters are in [`benches/ETL_BENCHMARKS.md`](benches/ETL_BENCHMARKS.md).
37
+
22
38
 
23
39
  ## Language Bindings
24
40
 
@@ -56,7 +72,8 @@ For implementation details and quick start examples for each usage type, see the
56
72
 
57
73
  ## Features
58
74
 
59
- * **Supported Backends**: Kafka, NATS, AMQP (RabbitMQ), MQTT, MongoDB, SQL Databases (PostgreSQL, MySQL, SQLite via sqlx), HTTP, WebSocket, ZeroMQ, Redis Streams, Files, AWS (SQS/SNS), IBM MQ, and in-memory channels.
75
+ * **Supported Backends**: Kafka, NATS, AMQP (RabbitMQ), MQTT, MongoDB, SQL Databases (PostgreSQL, MySQL, SQLite via sqlx), **Postgres CDC** (logical replication), ClickHouse, gRPC, HTTP, WebSocket, ZeroMQ, Redis Streams, Files, AWS (SQS/SNS), IBM MQ, and in-memory channels.
76
+ * **Change Data Capture**: PostgreSQL logical replication (`postgres_cdc`) and MongoDB change streams, surfaced as flat rows with an `*.operation` marker (`insert`/`update`/`delete`/`truncate`).
60
77
  > **Note**: IBM MQ is included in the `full` feature set via the `ibm-mq` feature, which loads the IBM MQ client library at runtime via dlopen — **no IBM SDK is needed to build**. The IBM MQ redistributable client only has to be present at runtime, and only if you actually use an IBM MQ endpoint (it is loaded lazily on first connect). If the client is missing, the affected route fails fast with a non-retryable error instead of reconnecting forever. The loader finds the client via the platform default name, `MQ_INSTALLATION_PATH` (e.g. `/opt/mqm`), or an explicit `MQB_IBM_MQ_LIB` path. To link the client statically at build time instead, use the `ibm-mq-static` feature (requires the IBM MQ SDK). See the [mqi crate](https://crates.io/crates/mqi/) for details.
61
78
  >
62
79
  > **TLS**: IBM MQ has its own TLS config shape (`IbmTlsConfig`), since the native client doesn't consume PEM files. The field names mirror the generic `TlsConfig` for config parity, but carry MQ-native semantics: `tls.cert_file` (alias `key_repository`) is a CMS key repository path (e.g. `/path/to/tls` for `tls.kdb`), not a PEM file. The repository can either be passwordless (backed by a `.sth` stash file next to the `.kdb`) or password-protected via `tls.cert_password` (alias `key_repository_password`; requires an IBM MQ client/server at 9.3.0.0 or later, which is the capability level `ibm-mq`/`ibm-mq-static` build against; 9.2 is EOL).
@@ -83,31 +100,32 @@ The error handling follows the same idea. Batch publishing can report partial su
83
100
 
84
101
  What it does not try to be: a domain framework, an actor runtime, or a full stream processor. You can build CQRS-ish flows with it, but the library cares more about transport, routing, and delivery behavior than about prescribing your domain model.
85
102
 
86
- ## Status
103
+ ### How it compares
87
104
 
88
- This library was created in 2025 and is still fairly new.
105
+ `mq-bridge` overlaps with config-driven ETL / pipeline runners, but comes in two shapes: a **library you embed in your own service**, or [`mq-bridge-app`](https://github.com/marcomq/mq-bridge-app) — the **same engine as a zero-code, config-driven ETL service**. You get the developer-first path and the no-code path from one codebase.
89
106
 
90
- It may still be possible that there are issues with
91
- - old or very new versions of broker servers
92
- - specific settings of the brokers
93
- - subscribe/event and response patterns if those are not available natively
94
- - NATS without JetStream
95
- - TLS setups, which are usually non-trivial and have just been tested automatically, but not in detail.
107
+ * **Choose the `mq-bridge` library** when you write the code that moves the data — in Rust, Python, or Node — and want batching, retries/DLQ/dedup, request-reply, CDC, and TLS behind one small API you run in-process. No separate scheduler, no daemon to operate.
108
+ * **Choose `mq-bridge-app` for zero-code ETL** when you'd rather move data A→B purely by config: define routes and endpoints in YAML/env, then use its **Postman-style UI** to send test messages and watch them flow — and **import Postman collections or AsyncAPI specs** through the UI to generate the config. Same reliability engine, no build step.
109
+ * **Choose a dedicated stream processor** when you need windowed joins or aggregations over time.
96
110
 
97
- Automated integration and performance tests cover all supported endpoints, including queue and subscriber modes, request-reply (where supported), and protocol-specific behaviors. See the backend feature table below for details on configuration and protocol support.
111
+ Either way it stays transport-first on purpose: it moves and routes data reliably rather than prescribing a domain model or a platform.
98
112
 
99
- The following table tracks which endpoints are already used actively in other projects for events. Send me a message or create an issue if you use another endpoint in production:
113
+ ## Status
100
114
 
101
- | Endpoint | Manual Test |
102
- |-----------|:-----------:|
103
- | Kafka | ✅ |
104
- | MongoDB | ✅ |
105
- | HTTP | ✅ |
106
- | IBM MQ | ✅ |
107
- | Retry Middleware | ✅ |
108
- | DLQ Middleware | ✅ |
115
+ `mq-bridge` is young (created in 2025) but its reliability behavior is exercised by an automated integration + performance suite across **every** supported endpoint:
116
+
117
+ * Integration and performance tests cover all endpoints in both queue and subscriber modes, request-reply where supported, and protocol-specific behavior.
118
+ * All endpoints have automated integration tests that showed **no data loss during in-flight broker restarts**; MQTT publish-confirmation was hardened until a chaos test drove in-flight loss to **zero**.
119
+ * Postgres CDC ships with a **restart-safety integration test**: an un-acked, in-flight batch is redelivered after a database restart with no loss and no gap into already-acknowledged changes.
120
+ * Throughput is tracked continuously on a public [benchmark dashboard](https://marcomq.github.io/mq-bridge/dev/bench/); ETL/CDC-shaped, like-for-like scenarios and their fixed methodology are documented in [`benches/ETL_BENCHMARKS.md`](benches/ETL_BENCHMARKS.md).
121
+
122
+ Known rough edges (be deliberate here):
123
+
124
+ - old or very new broker-server versions, or unusual broker settings
125
+ - subscribe/event and response patterns where the backend has no native equivalent (emulated)
126
+ - NATS without JetStream
127
+ - **TLS**: one unified `TlsConfig` shape is reused across transports and exercised by automated tests; per-backend certificate matrices (self-signed / CA-signed / mTLS / expired-cert rejection) are being expanded — treat non-trivial TLS setups as needing your own verification for now.
109
128
 
110
- All endpoints have automated integration tests and did not show data loss during simple in-flight broker restarts.
111
129
 
112
130
  ## Test Notes
113
131
 
@@ -157,6 +175,7 @@ The table below summarizes the capabilities and configuration for each backend:
157
175
  | **MongoDB** | Set `change_stream: true` | Emulated (Metadata) | **Yes** (Unlock) |
158
176
  | **MQTT** | Set `clean_session: true` | Emulated (Property) | Eventual (Skip Ack) |
159
177
  | **NATS** | Set `subscriber_mode: true` | **Native** (Inbox) | **Yes** (JetStream Nak) |
178
+ | **Postgres CDC** | N/A (streams committed changes) | No | **Yes** (confirmed LSN not advanced) |
160
179
  | **Redis Streams** | Set `subscriber_mode: true` | No | Eventual (PEL, un-acked) |
161
180
  | **Sled** | Set `delete_after_read: false` | No | **Yes** (Tx Rollback) |
162
181
  | **SQLx** | Not supported | No | Eventual (Skip Delete) |
@@ -169,6 +188,15 @@ The table below summarizes the capabilities and configuration for each backend:
169
188
  * **Emulated**: Publishes a new message to a reply destination (specified by the `reply_to` metadata field) carrying a `correlation_id` metadata field.
170
189
  * **Nack Support**: If "Yes", the backend supports explicit negative acknowledgement triggering redelivery. "Eventual" means redelivery depends on timeout or connection drop. "Simulated" is handled in-memory by the bridge.
171
190
 
191
+ ### Database Sources: Change Capture vs Polling
192
+ Databases have no native pub/sub, so `mq-bridge` reads them as a source in one of two ways:
193
+
194
+ * **Change Data Capture (CDC)** — tails the database's own change log, capturing inserts **and** updates/deletes and resuming from a durable log position.
195
+ * On **PostgreSQL**, the `postgres_cdc` endpoint streams a logical-replication slot (`pgoutput`). It emits flat JSON rows tagged with `postgres.operation` metadata (`insert`/`update`/`delete`/`truncate`). Acking a batch confirms the LSN back to the server (`standby_status_update`); a nack (or an interrupted run) does **not** advance the confirmed LSN, so replication resumes from the last acknowledged position on reconnect — at-least-once, verified by a restart-safety integration test. Enable with the `postgres-cdc` feature; requires `wal_level = logical` and a publication. See below.
196
+ * On **MongoDB**, set `consume: capture_new` to watch an existing collection for changes from now on, or `consume: capture_all` to read the existing documents first and then keep capturing changes (no gap, at-least-once). It emits `insert`/`update`/`replace`/`delete` events tagged with `mongodb.operation` metadata and checkpoints progress under `cursor_id`. These use the change stream (full post-image via `updateLookup`) and need a replica set; on a standalone server `capture_all` falls back to an insert-only read.
197
+ * **Cursor polling** — pages an existing table by a monotonic `cursor_column` (`WHERE col > $last ORDER BY col ASC`), persisting the last read value under `cursor_id`. Captures **appends only** — updates and deletes are not observed. Available on **SQLx** (PostgreSQL / MySQL / MariaDB / SQLite) and **ClickHouse**; while idle the poll interval backs off exponentially between `polling_interval_ms` and `max_polling_interval_ms`. **SQLite** and **ClickHouse** are polling-only — they have no server-side change log.
198
+
199
+
172
200
  ### Response Endpoint
173
201
  The `response` output endpoint sends a reply back to the original requester. This is useful for synchronous request-reply flows, for example HTTP-to-NATS-to-HTTP. Use `response: {}` as the output endpoint configuration.
174
202
 
@@ -0,0 +1,107 @@
1
+ # ETL / CDC-comparable benchmark methodology
2
+
3
+ This suite exists so that mq-bridge can be evaluated **like-for-like** against
4
+ config-driven ETL/CDC tools for moving Postgres data — not against
5
+ microbenchmarks of unrelated primitives. Credibility is the asset here: every
6
+ number below is reproducible from a pinned workload and a pinned backend
7
+ version, and the methodology is stated up front rather than cherry-picked.
8
+
9
+ The existing criterion harness (`benches/performance_bench.rs`, published to the
10
+ [benchmark dashboard](https://marcomq.github.io/mq-bridge/dev/bench/)) already
11
+ measures per-backend batched throughput. This document defines the additional
12
+ **ETL/CDC-shaped scenarios** and the fixed parameters they run under.
13
+
14
+ ## Fixed parameters (keep constant across runs and across tools)
15
+
16
+ | Parameter | Value |
17
+ | ---------------- | ------------------------------------------------- |
18
+ | Message payload | 256 B and 4 KiB JSON rows (report both) |
19
+ | Message count | 100_000 per run |
20
+ | Batch sizes | 1 (unbatched) and 128 (batched) |
21
+ | Concurrency | 1 and 4 route workers |
22
+ | Postgres | `postgres:16-alpine`, `wal_level=logical` |
23
+ | Warm-up | criterion default + 5_000-message pre-roll |
24
+ | Machine | record CPU model, cores, RAM in the published run |
25
+
26
+ Report the **backend version and the exact parameters next to every number**.
27
+ Never publish a single favourable figure without its methodology row.
28
+
29
+ ## Scenarios
30
+
31
+ 1. **Bulk-insert throughput** — `memory → sqlx(postgres)` sink, measured in
32
+ rows/second at batch sizes 1 and 128. Answers "how fast can mq-bridge load a
33
+ table". Uses `docker-compose/postgres_cdc.yml`.
34
+
35
+ 2. **CDC event-to-sink latency** — insert a row into a captured table and
36
+ measure the time until the corresponding change event is delivered to a
37
+ `memory` sink through the `postgres_cdc` endpoint (p50 / p95 / p99). Answers
38
+ "how quickly does a change propagate". Uses `docker-compose/postgres_cdc.yml`.
39
+
40
+ 3. **Batched vs. unbatched throughput** — the same source→sink route at
41
+ `batch_size: 1` vs `batch_size: 128`, per backend, to quantify the batching
42
+ lever the README documents. Runs on `memory` (no external service) and on
43
+ each broker that has a compose file.
44
+
45
+ ## Running
46
+
47
+ ```bash
48
+ # Batched-vs-unbatched (no external services)
49
+ cargo bench --features test-utils --bench performance_bench
50
+
51
+ # CDC latency + bulk-insert (needs Docker Postgres with logical replication)
52
+ docker compose -f tests/integration/docker-compose/postgres_cdc.yml up -d --wait
53
+ cargo bench --features "test-utils postgres-cdc" --bench performance_bench -- cdc
54
+ docker compose -f tests/integration/docker-compose/postgres_cdc.yml down -v
55
+ ```
56
+
57
+ ## Results
58
+
59
+ The scenarios are run through
60
+ [`mq-bridge-app`](https://github.com/marcomq/mq-bridge-app) (the zero-code, config-driven
61
+ path a real user takes) so the numbers are comparable to how other ETL/CDC tools publish
62
+ theirs. The run brief lives in [`mq-bridge-app-benchmark-prompt.md`](mq-bridge-app-benchmark-prompt.md).
63
+
64
+ > ⚠️ **Preliminary — measured on battery power.** The number below was measured on a
65
+ > laptop running on battery, where macOS throttles the CPU. It is **not yet authoritative**
66
+ > and is pending a re-run on AC (mains) power before publication. Treat it as a floor, not a
67
+ > confirmed figure.
68
+
69
+ **Environment:** Apple M1, 8 cores, 8 GB RAM, macOS (battery power) · via `mq-bridge-app` 0.2.6 · postgres:16
70
+
71
+ ### Validated run — bulk copy Postgres → JSONL
72
+
73
+ | Scenario | Payload | Batch | Concurrency | Source → Sink | Result |
74
+ | --- | --- | --- | --- | --- | --- |
75
+ | Bulk-copy throughput | 7-col mixed-type rows, 1,000,000 rows | 1024 | 4 | postgres (keyset cursor) → file (JSONL) | **303,398 rows/s** (3.296 s) |
76
+
77
+ Command: `mq-bridge-app copy --from postgres://…?table=…&cursor_column=id --to file://…?format=raw --drain --batch-size 1024 --concurrency 4`, wall-clocked. Requires an index on the cursor column (`CREATE INDEX ON <table>(id)`) — without it the keyset-pagination reader (`WHERE id > $cursor ORDER BY id LIMIT batch`) does a full scan per batch (near-quadratic).
78
+
79
+ ### Validated run — Postgres → JSONL vs. Meltano
80
+
81
+ Same source table, same Postgres instance, same machine, both one-shot full-table syncs to a local JSONL file — mq-bridge-app's `copy` CLI vs. Meltano's `tap-postgres` → `target-jsonl` (median of 5 timed runs each side).
82
+
83
+ | Scenario | Payload | Batch | Concurrency | Source → Sink | Result |
84
+ | --- | --- | --- | --- | --- | --- |
85
+ | mq-bridge-app `copy` | 256 B, 100,000 rows | 1024 | 1 | postgres (keyset cursor) → file (JSONL) | **197,628 rows/s** |
86
+ | Meltano (`tap-postgres` → `target-jsonl`) | 256 B, 100,000 rows | default Singer config | — | postgres → JSONL | 16,330 rows/s |
87
+
88
+ **~12.1x faster** than Meltano in this scenario. Full setup (including the Meltano project config) is in [mq-bridge-app's `benches/etl/README.md`](https://github.com/marcomq/mq-bridge-app/blob/dev/benches/etl/README.md#5--postgres--jsonl-vs-meltano-tap-postgres--target-jsonl).
89
+
90
+ ### Validated run — CSV → JSONL vs. Meltano
91
+
92
+ Same seeded dataset both sides, same machine, one-shot full-file CSV → local JSONL (one JSON object per input row) — mq-bridge-app's `copy` CLI vs. Meltano's `tap-csv` → `target-jsonl`.
93
+
94
+ | Scenario | Payload | Batch | Concurrency | Source → Sink | Result |
95
+ | --- | --- | --- | --- | --- | --- |
96
+ | mq-bridge-app `copy` | 7-col mixed-type, 1,000,000 rows (~116 MiB) | 1024 | 1 | file (CSV) → file (JSONL) | **833,333 rows/s** |
97
+ | Meltano (`tap-csv` → `target-jsonl`) | 7-col mixed-type, 1,000,000 rows | default Singer config | — | file (CSV) → JSONL | ~19,500 rows/s |
98
+
99
+ **~43x faster** than Meltano in this scenario. Full setup is in [mq-bridge-app's `benches/etl/README.md`](https://github.com/marcomq/mq-bridge-app/blob/dev/benches/etl/README.md#6--csv--jsonl-vs-meltano-faucet-streams-headline-benchmark).
100
+
101
+ ## Status
102
+
103
+ Scenario 3 (batched vs. unbatched) is covered by the existing harness. Scenarios
104
+ 1 and 2 (bulk-insert, CDC latency) share the Docker Postgres environment with the
105
+ CDC integration tests (`tests/integration/postgres_cdc.rs`); their criterion
106
+ wiring is added and validated together with that Docker run so the published
107
+ numbers are real, not estimated.