pg_eventstore 2.0.0 → 3.0.0

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 (303) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +10 -1
  3. data/CHANGELOG.md +186 -20
  4. data/README.md +25 -15
  5. data/db/migrations/10_setup_pg_cron.rb +1 -23
  6. data/db/migrations/15_create_streams_global_index.sql +17 -0
  7. data/db/migrations/16_populate_streams_global_index.rb +150 -0
  8. data/db/migrations/17_create_events_global_index.sql +62 -0
  9. data/db/migrations/18_populate_events_global_index.rb +124 -0
  10. data/db/migrations/19_populate_events_global_index_p2.rb +25 -0
  11. data/db/migrations/20_migrate_subscriptions_current_position.sql +19 -0
  12. data/db/migrations/21_create_partitions_stats.sql +2 -0
  13. data/db/migrations/22_drop_streams_sql_view.sql +2 -0
  14. data/db/migrations/23_adjust_partitions_indexes.sql +5 -0
  15. data/db/migrations/24_drop_legacy_events_indexes.sql +3 -0
  16. data/db/migrations/25_add_parent_partitions_info_to_partitions.sql +16 -0
  17. data/db/migrations/26_change_events_stream_revision_type.rb +35 -0
  18. data/db/migrations/27_drop_events_horizon.sql +3 -0
  19. data/db/migrations/28_create_maintenance_tasks.sql +8 -0
  20. data/db/migrations/29_create_event_markers.sql +35 -0
  21. data/db/migrations/30_improve_partitions_search_in_admin_ui.rb +24 -0
  22. data/db/migrations/31_drop_events_id_default_value.sql +1 -0
  23. data/db/migrations/32_create_parittions_triggers.sql +101 -0
  24. data/docs/AGENTS.fragment.md +696 -0
  25. data/docs/admin_ui.md +3 -1
  26. data/docs/appending_events.md +110 -49
  27. data/docs/configuration.md +69 -36
  28. data/docs/event_tracing.md +55 -0
  29. data/docs/events_and_streams.md +54 -30
  30. data/docs/how_it_works.md +57 -16
  31. data/docs/linking_events.md +2 -1
  32. data/docs/maintenance.md +14 -5
  33. data/docs/multiple_commands.md +17 -8
  34. data/docs/reading_events.md +208 -43
  35. data/docs/reading_streams.md +50 -0
  36. data/docs/replication.md +99 -0
  37. data/docs/subscriptions.md +165 -35
  38. data/docs/writing_middleware.md +25 -8
  39. data/ext/pg_eventstore_ext/extconf.rb +5 -0
  40. data/ext/pg_eventstore_ext/pg_eventstore_ext.c +74 -0
  41. data/lib/pg_eventstore/async_runner.rb +63 -0
  42. data/lib/pg_eventstore/basic_config.rb +15 -0
  43. data/lib/pg_eventstore/chunks/chunk.rb +31 -0
  44. data/lib/pg_eventstore/chunks/read_api_events_index_chunk.rb +83 -0
  45. data/lib/pg_eventstore/chunks/replica_events_index_chunk.rb +35 -0
  46. data/lib/pg_eventstore/chunks/repository.rb +73 -0
  47. data/lib/pg_eventstore/chunks/subscription_checkpoint_chunk.rb +48 -0
  48. data/lib/pg_eventstore/chunks/subscription_events_index_chunk.rb +99 -0
  49. data/lib/pg_eventstore/chunks.rb +14 -0
  50. data/lib/pg_eventstore/client.rb +163 -50
  51. data/lib/pg_eventstore/commands/append.rb +62 -43
  52. data/lib/pg_eventstore/commands/delete_event.rb +7 -8
  53. data/lib/pg_eventstore/commands/delete_stream.rb +3 -3
  54. data/lib/pg_eventstore/commands/event_modifiers/prepare_link_event.rb +9 -7
  55. data/lib/pg_eventstore/commands/event_modifiers/prepare_regular_event.rb +12 -7
  56. data/lib/pg_eventstore/commands/link_to.rb +5 -5
  57. data/lib/pg_eventstore/commands/read.rb +23 -5
  58. data/lib/pg_eventstore/commands/read_grouped.rb +46 -0
  59. data/lib/pg_eventstore/commands/read_streams.rb +18 -0
  60. data/lib/pg_eventstore/commands/read_streams_paginated.rb +56 -0
  61. data/lib/pg_eventstore/commands/regular_stream_read_paginated.rb +4 -4
  62. data/lib/pg_eventstore/commands/revision_check/current_revision.rb +51 -0
  63. data/lib/pg_eventstore/commands/revision_check/event_type_revisions_comparison.rb +46 -0
  64. data/lib/pg_eventstore/commands/revision_check/expected_revision.rb +135 -0
  65. data/lib/pg_eventstore/commands/revision_check/stream_revision_check.rb +98 -0
  66. data/lib/pg_eventstore/commands/revision_check/stream_revision_comparison.rb +36 -0
  67. data/lib/pg_eventstore/commands/stream_revision.rb +14 -0
  68. data/lib/pg_eventstore/commands/system_stream_read_paginated.rb +4 -4
  69. data/lib/pg_eventstore/commands.rb +4 -2
  70. data/lib/pg_eventstore/config.rb +30 -9
  71. data/lib/pg_eventstore/connection.rb +21 -2
  72. data/lib/pg_eventstore/errors.rb +136 -10
  73. data/lib/pg_eventstore/event.rb +34 -2
  74. data/lib/pg_eventstore/event_deserializer.rb +5 -4
  75. data/lib/pg_eventstore/event_global_index.rb +118 -0
  76. data/lib/pg_eventstore/event_marker.rb +16 -0
  77. data/lib/pg_eventstore/event_marker_index.rb +25 -0
  78. data/lib/pg_eventstore/event_serializer.rb +6 -0
  79. data/lib/pg_eventstore/extensions/acts_as_configurable.rb +88 -0
  80. data/lib/pg_eventstore/extensions/options_defaults.rb +25 -0
  81. data/lib/pg_eventstore/extensions/options_extension.rb +2 -2
  82. data/lib/pg_eventstore/feature_marker.rb +18 -0
  83. data/lib/pg_eventstore/maintenance.rb +11 -6
  84. data/lib/pg_eventstore/middleware/event_tracing.rb +53 -0
  85. data/lib/pg_eventstore/partition.rb +6 -0
  86. data/lib/pg_eventstore/pg_connection.rb +70 -14
  87. data/lib/pg_eventstore/queries/event_marker_queries.rb +74 -0
  88. data/lib/pg_eventstore/queries/event_queries.rb +7 -93
  89. data/lib/pg_eventstore/queries/events_global_index_queries.rb +131 -0
  90. data/lib/pg_eventstore/queries/index_filtering_queries.rb +179 -0
  91. data/lib/pg_eventstore/queries/links_resolver.rb +4 -4
  92. data/lib/pg_eventstore/queries/maintenance_queries.rb +159 -42
  93. data/lib/pg_eventstore/queries/partition_queries.rb +45 -103
  94. data/lib/pg_eventstore/queries/replica_queries.rb +110 -0
  95. data/lib/pg_eventstore/queries/streams_global_index_queries.rb +163 -0
  96. data/lib/pg_eventstore/queries/transaction_queries.rb +52 -10
  97. data/lib/pg_eventstore/queries.rb +29 -1
  98. data/lib/pg_eventstore/query_builders/basic_filtering.rb +38 -5
  99. data/lib/pg_eventstore/query_builders/event_markers_filtering.rb +47 -0
  100. data/lib/pg_eventstore/query_builders/event_markers_index_filtering.rb +275 -0
  101. data/lib/pg_eventstore/query_builders/event_subscription_positions_filtering.rb +37 -0
  102. data/lib/pg_eventstore/query_builders/events_filtering.rb +15 -164
  103. data/lib/pg_eventstore/query_builders/events_global_index_filtering.rb +227 -0
  104. data/lib/pg_eventstore/query_builders/filters/collection.rb +324 -0
  105. data/lib/pg_eventstore/query_builders/filters/event_type_filter.rb +37 -0
  106. data/lib/pg_eventstore/query_builders/filters/filter_row.rb +54 -0
  107. data/lib/pg_eventstore/query_builders/filters/marker_filter.rb +20 -0
  108. data/lib/pg_eventstore/query_builders/filters/marker_filter_row.rb +49 -0
  109. data/lib/pg_eventstore/query_builders/filters/stream_filter.rb +48 -0
  110. data/lib/pg_eventstore/query_builders/index_based_events_filtering.rb +260 -0
  111. data/lib/pg_eventstore/query_builders/partitions_filtering.rb +175 -51
  112. data/lib/pg_eventstore/query_builders/read_cursor/stream_cursor.rb +122 -0
  113. data/lib/pg_eventstore/query_builders/streams_global_index_filtering.rb +83 -0
  114. data/lib/pg_eventstore/query_builders/subscription_events_filtering.rb +79 -0
  115. data/lib/pg_eventstore/query_strategy/async.rb +64 -0
  116. data/lib/pg_eventstore/query_strategy/foreground.rb +25 -0
  117. data/lib/pg_eventstore/query_strategy.rb +19 -0
  118. data/lib/pg_eventstore/raw_event.rb +46 -0
  119. data/lib/pg_eventstore/rspec/test_helpers.rb +21 -8
  120. data/lib/pg_eventstore/sql_builder.rb +87 -8
  121. data/lib/pg_eventstore/stream.rb +13 -16
  122. data/lib/pg_eventstore/stream_global_index.rb +28 -0
  123. data/lib/pg_eventstore/subscriptions/callback_handlers/events_processor_handlers.rb +5 -17
  124. data/lib/pg_eventstore/subscriptions/callback_handlers/events_subscription_position_worker_handlers.rb +36 -0
  125. data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_feeder_handlers.rb +12 -0
  126. data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rb +21 -7
  127. data/lib/pg_eventstore/subscriptions/events_processor.rb +20 -18
  128. data/lib/pg_eventstore/subscriptions/events_processor_consumer/multiple.rb +63 -0
  129. data/lib/pg_eventstore/subscriptions/events_processor_consumer/replica.rb +68 -0
  130. data/lib/pg_eventstore/subscriptions/events_processor_consumer/single.rb +62 -0
  131. data/lib/pg_eventstore/subscriptions/events_processor_consumer.rb +44 -0
  132. data/lib/pg_eventstore/subscriptions/events_subscription_position_worker.rb +68 -0
  133. data/lib/pg_eventstore/subscriptions/queries/event_subscription_position_queries.rb +177 -0
  134. data/lib/pg_eventstore/subscriptions/queries/subscription_queries.rb +78 -54
  135. data/lib/pg_eventstore/subscriptions/replica_subscription_handler.rb +140 -0
  136. data/lib/pg_eventstore/subscriptions/replica_subscription_runner.rb +9 -0
  137. data/lib/pg_eventstore/subscriptions/subscription.rb +2 -1
  138. data/lib/pg_eventstore/subscriptions/subscription_feed_strategies/collection.rb +36 -0
  139. data/lib/pg_eventstore/subscriptions/subscription_feed_strategies/index_read_strategy.rb +82 -0
  140. data/lib/pg_eventstore/subscriptions/subscription_feed_strategies/replication_strategy.rb +75 -0
  141. data/lib/pg_eventstore/subscriptions/subscription_feed_strategy.rb +31 -0
  142. data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +24 -0
  143. data/lib/pg_eventstore/subscriptions/subscription_handler_performance.rb +4 -2
  144. data/lib/pg_eventstore/subscriptions/subscription_runner.rb +20 -9
  145. data/lib/pg_eventstore/subscriptions/subscription_runner_commands/reset_position.rb +1 -1
  146. data/lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb +9 -18
  147. data/lib/pg_eventstore/subscriptions/subscriptions_lifecycle.rb +0 -12
  148. data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +74 -14
  149. data/lib/pg_eventstore/{subscriptions/synchronized_array.rb → synchronized_array.rb} +15 -0
  150. data/lib/pg_eventstore/tasks/setup.rake +2 -2
  151. data/lib/pg_eventstore/utils.rb +31 -0
  152. data/lib/pg_eventstore/version.rb +1 -1
  153. data/lib/pg_eventstore/web/application.rb +100 -16
  154. data/lib/pg_eventstore/web/paginator/base_collection.rb +5 -1
  155. data/lib/pg_eventstore/web/paginator/event_types_collection.rb +14 -4
  156. data/lib/pg_eventstore/web/paginator/events_collection.rb +29 -33
  157. data/lib/pg_eventstore/web/paginator/helpers.rb +16 -9
  158. data/lib/pg_eventstore/web/paginator/markers_collection.rb +48 -0
  159. data/lib/pg_eventstore/web/paginator/stream_contexts_collection.rb +13 -2
  160. data/lib/pg_eventstore/web/paginator/stream_ids_collection.rb +21 -11
  161. data/lib/pg_eventstore/web/paginator/stream_names_collection.rb +18 -4
  162. data/lib/pg_eventstore/web/paginator/streams_collection.rb +95 -0
  163. data/lib/pg_eventstore/web/public/javascripts/pg_eventstore.js +49 -22
  164. data/lib/pg_eventstore/web/subscriptions/set_collection.rb +1 -1
  165. data/lib/pg_eventstore/web/subscriptions/subscriptions.rb +1 -1
  166. data/lib/pg_eventstore/web/subscriptions/with_state/set_collection.rb +1 -1
  167. data/lib/pg_eventstore/web/subscriptions/with_state/subscriptions.rb +1 -1
  168. data/lib/pg_eventstore/web/views/home/dashboard.erb +11 -9
  169. data/lib/pg_eventstore/web/views/home/partials/event_filter.erb +18 -5
  170. data/lib/pg_eventstore/web/views/home/partials/events.erb +38 -1
  171. data/lib/pg_eventstore/web/views/home/partials/markers_filter.erb +18 -0
  172. data/lib/pg_eventstore/web/views/layouts/application.erb +6 -0
  173. data/lib/pg_eventstore/web/views/streams/index.erb +110 -0
  174. data/lib/pg_eventstore/web/views/streams/partials/streams.erb +11 -0
  175. data/lib/pg_eventstore/web/views/subscriptions/index.erb +4 -1
  176. data/lib/pg_eventstore/web.rb +2 -0
  177. data/lib/pg_eventstore.rb +23 -60
  178. data/pg_eventstore.gemspec +6 -4
  179. data/sig/interfaces/_raw_events_handler.rbs +3 -0
  180. data/sig/interfaces/subscription_handler.rbs +1 -1
  181. data/sig/pg/result.rbs +9 -0
  182. data/sig/pg_eventstore/async_runner.rbs +13 -0
  183. data/sig/pg_eventstore/basic_config.rbs +9 -0
  184. data/sig/pg_eventstore/chunks/chunk.rbs +13 -0
  185. data/sig/pg_eventstore/chunks/read_api_events_index_chunk.rbs +30 -0
  186. data/sig/pg_eventstore/chunks/replica_events_index_chunk.rbs +11 -0
  187. data/sig/pg_eventstore/chunks/repository.rbs +22 -0
  188. data/sig/pg_eventstore/chunks/subscription_checkpoint_chunk.rbs +18 -0
  189. data/sig/pg_eventstore/chunks/subscription_events_index_chunk.rbs +37 -0
  190. data/sig/pg_eventstore/client.rbs +40 -42
  191. data/sig/pg_eventstore/commands/append.rbs +11 -25
  192. data/sig/pg_eventstore/commands/event_modifiers/prepare_link_event.rbs +9 -12
  193. data/sig/pg_eventstore/commands/event_modifiers/prepare_regular_event.rbs +5 -4
  194. data/sig/pg_eventstore/commands/link_to.rbs +6 -11
  195. data/sig/pg_eventstore/commands/read.rbs +2 -5
  196. data/sig/pg_eventstore/commands/read_grouped.rbs +7 -0
  197. data/sig/pg_eventstore/commands/read_streams.rbs +7 -0
  198. data/sig/pg_eventstore/commands/read_streams_paginated.rbs +16 -0
  199. data/sig/pg_eventstore/commands/regular_stream_read_paginated.rbs +9 -17
  200. data/sig/pg_eventstore/commands/revision_check/current_revision.rbs +25 -0
  201. data/sig/pg_eventstore/commands/revision_check/event_type_revisions_comparison.rbs +14 -0
  202. data/sig/pg_eventstore/commands/revision_check/expected_revision.rbs +67 -0
  203. data/sig/pg_eventstore/commands/revision_check/stream_revision_check.rbs +20 -0
  204. data/sig/pg_eventstore/commands/revision_check/stream_revision_comparison.rbs +9 -0
  205. data/sig/pg_eventstore/commands/stream_revision.rbs +7 -0
  206. data/sig/pg_eventstore/commands/system_stream_read_paginated.rbs +9 -14
  207. data/sig/pg_eventstore/config.rbs +15 -6
  208. data/sig/pg_eventstore/connection.rbs +11 -18
  209. data/sig/pg_eventstore/errors.rbs +64 -34
  210. data/sig/pg_eventstore/event.rbs +16 -2
  211. data/sig/pg_eventstore/event_deserializer.rbs +5 -10
  212. data/sig/pg_eventstore/event_global_index.rbs +60 -0
  213. data/sig/pg_eventstore/event_marker.rbs +9 -0
  214. data/sig/pg_eventstore/event_marker_index.rbs +12 -0
  215. data/sig/pg_eventstore/extensions/acts_as_configurable.rbs +29 -0
  216. data/sig/pg_eventstore/extensions/options_defaults.rbs +7 -0
  217. data/sig/pg_eventstore/feature_marker.rbs +10 -0
  218. data/sig/pg_eventstore/maintenance.rbs +12 -8
  219. data/sig/pg_eventstore/middleware/event_trace.rbs +14 -0
  220. data/sig/pg_eventstore/partition.rbs +2 -4
  221. data/sig/pg_eventstore/pg_connection.rbs +8 -0
  222. data/sig/pg_eventstore/queries/event_marker_queries.rbs +21 -0
  223. data/sig/pg_eventstore/queries/event_queries.rbs +4 -36
  224. data/sig/pg_eventstore/queries/events_global_index_queries.rbs +37 -0
  225. data/sig/pg_eventstore/queries/index_filtering_queries.rbs +50 -0
  226. data/sig/pg_eventstore/queries/links_resolver.rbs +5 -9
  227. data/sig/pg_eventstore/queries/maintenance_queries.rbs +12 -9
  228. data/sig/pg_eventstore/queries/partition_queries.rbs +32 -67
  229. data/sig/pg_eventstore/queries/replica_queries.rbs +31 -0
  230. data/sig/pg_eventstore/queries/streams_global_index_queries.rbs +39 -0
  231. data/sig/pg_eventstore/queries/transaction_queries.rbs +2 -0
  232. data/sig/pg_eventstore/queries.rbs +11 -21
  233. data/sig/pg_eventstore/query_builders/basic_filtering.rbs +10 -3
  234. data/sig/pg_eventstore/query_builders/event_markers_filtering.rbs +17 -0
  235. data/sig/pg_eventstore/query_builders/event_markers_index_filtering.rbs +69 -0
  236. data/sig/pg_eventstore/query_builders/event_subscription_positions_filtering.rbs +15 -0
  237. data/sig/pg_eventstore/query_builders/events_filtering_query.rbs +9 -51
  238. data/sig/pg_eventstore/query_builders/events_global_index_filtering.rbs +68 -0
  239. data/sig/pg_eventstore/query_builders/filters/collection.rbs +84 -0
  240. data/sig/pg_eventstore/query_builders/filters/event_type_filter.rbs +19 -0
  241. data/sig/pg_eventstore/query_builders/filters/filter_row.rbs +21 -0
  242. data/sig/pg_eventstore/query_builders/filters/marker_filter.rbs +13 -0
  243. data/sig/pg_eventstore/query_builders/filters/marker_filter_row.rbs +19 -0
  244. data/sig/pg_eventstore/query_builders/filters/stream_filter.rbs +24 -0
  245. data/sig/pg_eventstore/query_builders/index_based_events_filtering.rbs +60 -0
  246. data/sig/pg_eventstore/query_builders/partitions_filtering.rbs +29 -10
  247. data/sig/pg_eventstore/query_builders/read_cursor/stream_cursor.rbs +47 -0
  248. data/sig/pg_eventstore/query_builders/streams_global_index_filtering.rbs +27 -0
  249. data/sig/pg_eventstore/query_builders/subscription_events_filtering.rbs +26 -0
  250. data/sig/pg_eventstore/query_strategy/async.rbs +17 -0
  251. data/sig/pg_eventstore/query_strategy/foreground.rbs +11 -0
  252. data/sig/pg_eventstore/query_strategy.rbs +7 -0
  253. data/sig/pg_eventstore/raw_event.rbs +20 -0
  254. data/sig/pg_eventstore/sql_builder.rbs +32 -10
  255. data/sig/pg_eventstore/stream.rbs +10 -18
  256. data/sig/pg_eventstore/stream_global_index.rbs +14 -0
  257. data/sig/pg_eventstore/subscriptions/callback_handlers/events_processor_handlers.rbs +5 -4
  258. data/sig/pg_eventstore/subscriptions/callback_handlers/events_subscription_position_worker_handlers.rbs +9 -0
  259. data/sig/pg_eventstore/subscriptions/callback_handlers/subscription_feeder_handlers.rbs +17 -12
  260. data/sig/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rbs +17 -6
  261. data/sig/pg_eventstore/subscriptions/event_subscription_position_queries.rbs +30 -0
  262. data/sig/pg_eventstore/subscriptions/events_processor.rbs +12 -5
  263. data/sig/pg_eventstore/subscriptions/events_processor_consumer/multiple.rbs +17 -0
  264. data/sig/pg_eventstore/subscriptions/events_processor_consumer/replica.rbs +17 -0
  265. data/sig/pg_eventstore/subscriptions/events_processor_consumer/single.rbs +17 -0
  266. data/sig/pg_eventstore/subscriptions/events_processor_consumer.rbs +14 -0
  267. data/sig/pg_eventstore/subscriptions/events_subscription_position_worker.rbs +28 -0
  268. data/sig/pg_eventstore/subscriptions/queries/subscription_queries.rbs +7 -39
  269. data/sig/pg_eventstore/subscriptions/replica_subscription_handler.rbs +33 -0
  270. data/sig/pg_eventstore/subscriptions/replica_subscription_runner.rbs +4 -0
  271. data/sig/pg_eventstore/subscriptions/subscription_feed_strategy/collection.rbs +14 -0
  272. data/sig/pg_eventstore/subscriptions/subscription_feed_strategy/index_read_strategy.rbs +23 -0
  273. data/sig/pg_eventstore/subscriptions/subscription_feed_strategy/replication_strategy.rbs +23 -0
  274. data/sig/pg_eventstore/subscriptions/subscription_feed_strategy.rbs +11 -0
  275. data/sig/pg_eventstore/subscriptions/subscription_feeder.rbs +13 -10
  276. data/sig/pg_eventstore/subscriptions/subscription_handler_performance.rbs +1 -1
  277. data/sig/pg_eventstore/subscriptions/subscription_runner.rbs +13 -2
  278. data/sig/pg_eventstore/subscriptions/subscription_runners_feeder.rbs +1 -8
  279. data/sig/pg_eventstore/subscriptions/subscriptions_lifecycle.rbs +4 -6
  280. data/sig/pg_eventstore/subscriptions/subscriptions_manager.rbs +29 -43
  281. data/sig/pg_eventstore/synchronized_array.rbs +4 -0
  282. data/sig/pg_eventstore/utils.rbs +10 -11
  283. data/sig/pg_eventstore/web/application.rbs +13 -3
  284. data/sig/pg_eventstore/web/paginator/base_collection.rbs +2 -0
  285. data/sig/pg_eventstore/web/paginator/event_types_collection.rbs +4 -0
  286. data/sig/pg_eventstore/web/paginator/events_collection.rbs +11 -11
  287. data/sig/pg_eventstore/web/paginator/helpers.rbs +6 -15
  288. data/sig/pg_eventstore/web/paginator/markers_collection.rbs +13 -0
  289. data/sig/pg_eventstore/web/paginator/stream_contexts_collection.rbs +4 -0
  290. data/sig/pg_eventstore/web/paginator/stream_names_collection.rbs +4 -0
  291. data/sig/pg_eventstore/web/paginator/streams_collection.rbs +30 -0
  292. data/sig/pg_eventstore.rbs +8 -26
  293. metadata +175 -20
  294. data/Dockerfile +0 -3
  295. data/lib/pg_eventstore/commands/all_stream_read_grouped.rb +0 -69
  296. data/lib/pg_eventstore/commands/regular_stream_read_grouped.rb +0 -31
  297. data/lib/pg_eventstore/subscriptions/queries/subscription_service_queries.rb +0 -78
  298. data/lib/pg_eventstore/web/views/home/partials/system_stream_filter.erb +0 -15
  299. data/sig/pg_eventstore/commands/all_stream_read_grouped.rbs +0 -16
  300. data/sig/pg_eventstore/commands/regular_stream_read_grouped.rbs +0 -8
  301. data/sig/pg_eventstore/subscriptions/queries/subscription_service_queries.rbs +0 -19
  302. data/sig/pg_eventstore/subscriptions/synchronized_array.rbs +0 -4
  303. /data/sig/interfaces/{_raw_event_handler.rbs → raw_event_handler.rbs} +0 -0
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module Middleware
5
+ class EventTracing
6
+ include Middleware
7
+
8
+ # @return [String]
9
+ CAUSATION_ID_KEY = "#{Event::SYSTEM_SYMBOL}CaId".freeze
10
+ # @return [String]
11
+ CORRELATION_ID_KEY = "#{Event::SYSTEM_SYMBOL}CoId".freeze
12
+
13
+ class << self
14
+ # @param marker [String]
15
+ # @return [PgEventstore::FeatureMarker]
16
+ def causation_marker(marker)
17
+ FeatureMarker.new(marker:, purpose: :causation_id, description: 'Causation ID')
18
+ end
19
+
20
+ # @param marker [String]
21
+ # @return [PgEventstore::FeatureMarker]
22
+ def correlation_marker(marker)
23
+ FeatureMarker.new(marker:, purpose: :correlation_id, description: 'Correlation ID')
24
+ end
25
+ end
26
+
27
+ def serialize(event)
28
+ if event.caused_by
29
+ event.causation_id = event.caused_by.id.dup
30
+ event.metadata[CAUSATION_ID_KEY] = event.causation_id
31
+ event.feature_markers.push(self.class.causation_marker(event.causation_id))
32
+ end
33
+
34
+ event.correlation_id = event.caused_by&.correlation_id&.dup || SecureRandom.uuid_v7
35
+ event.metadata[CORRELATION_ID_KEY] = event.correlation_id
36
+ event.feature_markers.push(self.class.correlation_marker(event.correlation_id))
37
+ end
38
+
39
+ # rubocop:disable Style/GuardClause
40
+ def deserialize(event)
41
+ if event.metadata[CAUSATION_ID_KEY]
42
+ event.causation_id = event.metadata[CAUSATION_ID_KEY]
43
+ event.feature_markers.push(self.class.causation_marker(event.causation_id))
44
+ end
45
+ if event.metadata[CORRELATION_ID_KEY]
46
+ event.correlation_id = event.metadata[CORRELATION_ID_KEY]
47
+ event.feature_markers.push(self.class.correlation_marker(event.correlation_id))
48
+ end
49
+ end
50
+ # rubocop:enable Style/GuardClause
51
+ end
52
+ end
53
+ end
@@ -19,5 +19,11 @@ module PgEventstore
19
19
  # @!attribute table_name
20
20
  # @return [String]
21
21
  option(:table_name)
22
+ # @!attribute parent_context_partition_id
23
+ # @return [Integer, nil]
24
+ option(:parent_context_partition_id)
25
+ # @!attribute parent_stream_name_partition_id
26
+ # @return [Integer, nil]
27
+ option(:parent_stream_name_partition_id)
22
28
  end
23
29
  end
@@ -3,6 +3,17 @@
3
3
  module PgEventstore
4
4
  # @!visibility private
5
5
  class PgConnection < PG::Connection
6
+ PLACEHOLDER_RE = /
7
+ '(?:''|[^'])*' | # string literal
8
+ "(?:""|[^"])*" | # quoted identifier
9
+ --[^\n]* | # line comment
10
+ \/\*.*?\*\/ | # block comment
11
+ \$\$.*?\$\$ | # dollar-quoted string. E.g. $$ $1 $$
12
+ \$(?<__dq_tag>[A-Za-z_][A-Za-z_0-9]*)\$.*?\$\k<__dq_tag>\$ | # named dollar-quoted string. E.g. $foo$ $1 $foo$
13
+ (?<placeholder>\$(?:[1-9]\d*)) # placeholder we are interested in
14
+ /mx
15
+ private_constant :PLACEHOLDER_RE
16
+
6
17
  def exec(sql)
7
18
  log(sql, [])
8
19
  super
@@ -13,37 +24,82 @@ module PgEventstore
13
24
  super
14
25
  end
15
26
 
16
- private
27
+ def send_query_params(sql, params)
28
+ log(sql, params)
29
+ super
30
+ end
17
31
 
18
- def log(sql, params)
19
- return unless PgEventstore.logger&.debug?
32
+ def send_query(sql)
33
+ log(sql, [])
34
+ super
35
+ end
36
+
37
+ # This is a partial copy-paste of https://github.com/ged/ruby-pg/pull/726. Because maintainers think it is too risky
38
+ # to accept those changes because of possible sql injections in the long run - I copy-pasted it here and simplified
39
+ # for pg_eventstore use cases.
40
+ # Why is it safe for pg_eventstore? The main concern there is that if new string literal would be introduced in some
41
+ # future PostgreSQL version - the implementation would become vulnerable to sql injections in case that new string
42
+ # literal is used in queries along with #compile. Because pg_eventstore uses only two forms of string literals
43
+ # (those are $$ my string $$ and 'my string') - we are totally safe. Also, there is only one place so far where this
44
+ # method is used(create subscription table function) - all other places use exec/exec_params. To conclude: careful
45
+ # usage + very low risk of future exposure to sql injections makes the usage of this method acceptable for
46
+ # pg_eventstore.
47
+ #
48
+ # Compiles your prepared SQL statement and the given positional arguments into plain SQL string.
49
+ #
50
+ # The resulting SQL string can be used with +conn.exec+ like the prepared SQL statement and parameters with
51
+ # +conn.exec_params+. +conn.exec_params+ is usually preferred because it's faster and safer.
52
+ #
53
+ # Example:
54
+ # res = conn.compile('SELECT $1 AS a, $2 AS b, $3 AS c', [1, 2, nil])
55
+ # # => "SELECT '1' AS a, '2' AS b, NULL AS c"
56
+ # @param sql [String]
57
+ # @param params [Array]
58
+ # @return [String]
59
+ def compile(sql, params)
60
+ return sql if params.empty?
20
61
 
21
- unless params&.empty?
22
- sql = sql.gsub(/\$\d+/).each do |matched|
23
- value = params[matched[1..].to_i - 1]
24
- value = encode_value(value)
25
- normalize_value(value)
26
- end
62
+ sql.gsub(PLACEHOLDER_RE).each do |matched|
63
+ placeholder = Regexp.last_match[:placeholder]
64
+ # Do not replace non-positional args string and pass it as is
65
+ next matched unless placeholder
66
+
67
+ prepared_value(params[placeholder[1..].to_i - 1])
27
68
  end
28
- PgEventstore.logger.debug(sql)
29
69
  end
30
70
 
71
+ # Converts the given object into its PostgreSQL representation
72
+ def prepared_value(value)
73
+ value = encode_value(value)
74
+ normalize_value(value)
75
+ end
76
+
77
+ private
78
+
31
79
  def encode_value(value)
32
80
  encoder = type_map_for_queries[value.class]
33
81
  return type_map_for_queries.send(encoder, value).encode(value) if encoder.is_a?(Symbol)
82
+ # format == 1 stands for binary format
83
+ return value if encoder.nil? || encoder.format == 1
34
84
 
35
- type_map_for_queries[value.class]&.encode(value) || value
85
+ encoder.encode(value)
36
86
  end
37
87
 
38
88
  def normalize_value(value)
39
89
  case value
40
- when String
41
- "'#{value}'"
90
+ when TrueClass, FalseClass
91
+ value.to_s
42
92
  when NilClass
43
93
  'NULL'
44
94
  else
45
- value
95
+ "'#{escape(value.to_s)}'"
46
96
  end
47
97
  end
98
+
99
+ def log(sql, params)
100
+ return unless PgEventstore.logger&.debug?
101
+
102
+ PgEventstore.logger.debug(compile(sql, params))
103
+ end
48
104
  end
49
105
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ # @!visibility private
5
+ class EventMarkerQueries
6
+ # @!attribute connection
7
+ # @return [PgEventstore::Connection]
8
+ attr_reader :connection
9
+ private :connection
10
+
11
+ # @param connection [PgEventstore::Connection]
12
+ # @param query_strategy [PgEventstore::QueryStrategy]
13
+ def initialize(connection, query_strategy)
14
+ @connection = connection
15
+ @query_strategy = query_strategy
16
+ end
17
+
18
+ # @param names [Array<String>]
19
+ # @return [Array<PgEventstore::EventMarker>]
20
+ def find_or_create_by(names)
21
+ builder = QueryBuilders::EventMarkersFiltering.sql_builder_by_names(names)
22
+ existing = @query_strategy.exec_params(*builder.to_exec_params)
23
+ existing = deserialize(existing)
24
+ rest = names - existing.map(&:name)
25
+ return existing if rest.empty?
26
+
27
+ values = rest.map do |name|
28
+ "('#{PG::Connection.escape(name)}')"
29
+ end.join(',')
30
+ table_name = QueryBuilders::EventMarkersFiltering::PRIMARY_TABLE_NAME
31
+ rest = @query_strategy.exec(<<~SQL)
32
+ insert into #{table_name} ("name") values #{values} returning *
33
+ SQL
34
+ existing + deserialize(rest)
35
+ end
36
+
37
+ # @param stream_idx_id [Integer]
38
+ # @param write_api_events_index [Array<PgEventstore::EventGlobalIndex::WriteApiRepr>]
39
+ # @param revision_to_marker_ids_map [Hash<Integer, Set<Integer>>]
40
+ # @return [void]
41
+ def create_indexes(stream_idx_id, write_api_events_index, revision_to_marker_ids_map)
42
+ offset = write_api_events_index.first.stream_revision
43
+ values = revision_to_marker_ids_map.flat_map do |stream_revision, marker_ids|
44
+ event_index = write_api_events_index[stream_revision - offset]
45
+ marker_ids.map do |marker_id|
46
+ values = [
47
+ marker_id,
48
+ stream_idx_id,
49
+ event_index.global_position,
50
+ event_index.stream_revision,
51
+ event_index.event_type_partition_id,
52
+ ]
53
+ "(#{values.join(',')})"
54
+ end
55
+ end
56
+ values = values.join(', ')
57
+ table_name = QueryBuilders::EventMarkersIndexFiltering::PRIMARY_TABLE_NAME
58
+ @query_strategy.exec(<<~SQL)
59
+ insert into #{table_name}
60
+ ("marker_id", "streams_global_index_id", "global_position", "stream_revision", "event_type_partition_id")
61
+ values
62
+ #{values}
63
+ SQL
64
+ end
65
+
66
+ private
67
+
68
+ # @param pg_result [PG::Result]
69
+ # @return [Array<PgEventstore::EventMarker>]
70
+ def deserialize(pg_result)
71
+ pg_result.map { EventMarker.new(**_1.transform_keys(&:to_sym)) }
72
+ end
73
+ end
74
+ end
@@ -6,82 +6,16 @@ module PgEventstore
6
6
  # @!attribute connection
7
7
  # @return [PgEventstore::Connection]
8
8
  attr_reader :connection
9
- # @!attribute serializer
10
- # @return [PgEventstore::EventSerializer]
11
- attr_reader :serializer
12
- # @!attribute deserializer
13
- # @return [PgEventstore::EventDeserializer]
14
- attr_reader :deserializer
15
- private :connection, :serializer, :deserializer
9
+ private :connection
16
10
 
17
11
  # @param connection [PgEventstore::Connection]
18
- # @param serializer [PgEventstore::EventSerializer]
19
- # @param deserializer [PgEventstore::EventDeserializer]
20
- def initialize(connection, serializer, deserializer)
12
+ def initialize(connection)
21
13
  @connection = connection
22
- @serializer = serializer
23
- @deserializer = deserializer
24
- end
25
-
26
- # @param event [PgEventstore::Event]
27
- # @return [Boolean]
28
- def event_exists?(event)
29
- return false if event.id.nil? || event.stream.nil?
30
-
31
- sql_builder = SQLBuilder.new.select('1 as exists').from(Event::PRIMARY_TABLE_NAME).where('id = ?', event.id)
32
- sql_builder.limit(1)
33
- sql_builder.where(
34
- 'context = ? and stream_name = ? and type = ?', event.stream.context, event.stream.stream_name, event.type
35
- )
36
- connection.with do |conn|
37
- conn.exec_params(*sql_builder.to_exec_params)
38
- end.to_a.dig(0, 'exists') == 1
39
- end
40
-
41
- # Takes an array of potentially persisted events and loads their ids from db. Those ids can be later used to check
42
- # whether events are actually existing events.
43
- # @param events [Array<PgEventstore::Event>]
44
- # @return [Array<Integer>]
45
- def global_positions_from_db(events)
46
- sql_builder = SQLBuilder.new.from(Event::PRIMARY_TABLE_NAME).select('global_position')
47
- partition_attrs = events.map { |event| [event.stream&.context, event.stream&.stream_name, event.type] }.uniq
48
- partition_attrs.each do |context, stream_name, event_type|
49
- sql_builder.where_or('context = ? and stream_name = ? and type = ?', context, stream_name, event_type)
50
- end
51
- sql_builder.where('global_position = ANY(?::bigint[])', events.map(&:global_position))
52
- raw_events = PgEventstore.connection.with do |conn|
53
- conn.exec_params(*sql_builder.to_exec_params)
54
- end.to_a
55
- raw_events.map { |attrs| attrs['global_position'] }
56
- end
57
-
58
- # @param stream [PgEventstore::Stream]
59
- # @return [Integer, nil]
60
- def stream_revision(stream)
61
- sql_builder = SQLBuilder.new.from(Event::PRIMARY_TABLE_NAME).select('stream_revision')
62
- sql_builder.where('context = ? and stream_name = ? and stream_id = ?', *stream.to_a)
63
- sql_builder.order('stream_revision DESC').limit(1)
64
- connection.with do |conn|
65
- conn.exec_params(*sql_builder.to_exec_params)
66
- end.to_a.dig(0, 'stream_revision')
67
- end
68
-
69
- # @see PgEventstore::Client#read for more info
70
- # @param stream [PgEventstore::Stream]
71
- # @param options [Hash]
72
- # @return [Array<PgEventstore::Event>]
73
- def stream_events(stream, options)
74
- exec_params = QueryBuilders::EventsFiltering.events_filtering(stream, options).to_exec_params
75
- raw_events = connection.with do |conn|
76
- conn.exec_params(*exec_params)
77
- end.to_a
78
- raw_events = links_resolver.resolve(raw_events) if options[:resolve_link_tos]
79
- deserializer.deserialize_many(raw_events)
80
14
  end
81
15
 
82
16
  # @param stream [PgEventstore::Stream]
83
17
  # @param events [Array<PgEventstore::Event>]
84
- # @return [Array<PgEventstore::Event>]
18
+ # @return [Array<Hash>]
85
19
  def insert(stream, events)
86
20
  sql_rows_for_insert, values = prepared_statements(stream, events)
87
21
  columns = %w[id data metadata stream_revision link_global_position link_partition_id type context stream_name stream_id]
@@ -92,30 +26,11 @@ module PgEventstore
92
26
  RETURNING *
93
27
  SQL
94
28
 
95
- raw_events = connection.with do |conn|
29
+ result = connection.with do |conn|
96
30
  conn.exec_params(sql, values)
97
31
  end
98
- raw_events.map do |raw_event|
99
- deserializer.without_middlewares.deserialize(raw_event)
100
- end
101
- end
102
-
103
- # @param stream [PgEventstore::Stream]
104
- # @param options_by_event_type [Array<Hash>] a set of options per an event type
105
- # @param options [Hash]
106
- # @option options [Boolean] :resolve_link_tos
107
- # @return [Array<PgEventstore::Event>]
108
- def grouped_events(stream, options_by_event_type, **options)
109
- builders = options_by_event_type.map do |filter|
110
- QueryBuilders::EventsFiltering.events_filtering(stream, filter)
111
- end
112
- final_builder = SQLBuilder.union_builders(builders.map(&:to_sql_builder))
113
-
114
- raw_events = connection.with do |conn|
115
- conn.exec_params(*final_builder.to_exec_params)
116
- end.to_a
117
- raw_events = links_resolver.resolve(raw_events) if options[:resolve_link_tos]
118
- deserializer.deserialize_many(raw_events)
32
+ # "returning" statement has no guarantees about the order in which rows are returned. Thus, sort them explicitly
33
+ result.sort_by { _1['stream_revision'] }
119
34
  end
120
35
 
121
36
  private
@@ -127,7 +42,6 @@ module PgEventstore
127
42
  positional_counter = 1
128
43
  values = []
129
44
  sql_rows_for_insert = events.map do |event|
130
- event = serializer.serialize(event)
131
45
  attributes = event.options_hash.slice(
132
46
  :id, :data, :metadata, :stream_revision, :link_global_position, :link_partition_id, :type
133
47
  )
@@ -150,7 +64,7 @@ module PgEventstore
150
64
 
151
65
  # @return [PgEventstore::LinksResolver]
152
66
  def links_resolver
153
- LinksResolver.new(connection)
67
+ LinksResolver.new(connection, QueryStrategy::Foreground.new(connection))
154
68
  end
155
69
  end
156
70
  end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ # @!visibility private
5
+ class EventsGlobalIndexQueries
6
+ # @!attribute connection
7
+ # @return [PgEventstore::Connection]
8
+ attr_reader :connection
9
+ private :connection
10
+
11
+ # @param connection [PgEventstore::Connection]
12
+ # @param query_strategy [PgEventstore::QueryStrategy]
13
+ def initialize(connection, query_strategy)
14
+ @connection = connection
15
+ @query_strategy = query_strategy
16
+ end
17
+
18
+ # @param raw_events [Array<Hash>]
19
+ # @param affected_partitions [Array<PgEventstore::Partition>]
20
+ # @param stream_index_id [Integer]
21
+ # @return [Array<PgEventstore::EventGlobalIndex::WriteApiRepr>]
22
+ def index_events(raw_events, affected_partitions, stream_index_id)
23
+ partitions = affected_partitions.to_h { [_1.event_type, _1] }
24
+ values = raw_events.map do |event_attrs|
25
+ values = [
26
+ event_attrs['global_position'],
27
+ event_attrs['stream_revision'],
28
+ partitions[event_attrs['type']].parent_context_partition_id,
29
+ partitions[event_attrs['type']].parent_stream_name_partition_id,
30
+ partitions[event_attrs['type']].id,
31
+ stream_index_id,
32
+ ]
33
+ "(#{values.join(', ')})"
34
+ end
35
+ values = values.join(',')
36
+
37
+ result = @query_strategy.exec(<<~SQL)
38
+ INSERT INTO events_global_index
39
+ ("global_position", "stream_revision", "context_partition_id", "stream_name_partition_id",
40
+ "event_type_partition_id", "streams_global_index_id")
41
+ VALUES #{values}
42
+ RETURNING global_position, stream_revision, event_type_partition_id
43
+ SQL
44
+ # "returning" statement has no guarantees about the order in which rows are returned. Thus, sort them explicitly
45
+ deserialize(result, repr: EventGlobalIndex::ReprType::WRITE_API).sort_by(&:stream_revision)
46
+ end
47
+
48
+ # @param grouped_opts [Hash]
49
+ # @return [Hash<Integer, Array<PgEventstore::SubscriptionRepr>>]
50
+ def fetch_indexes_for_subscriptions(grouped_opts)
51
+ builders = grouped_opts.map do |subscription_id, opts|
52
+ filter = QueryBuilders::SubscriptionEventsFiltering.build(subscription_id, opts)
53
+ filter.to_sql_builder.select("#{subscription_id} as subscription_id")
54
+ end
55
+ final_builder = SQLBuilder.union_builders(builders)
56
+ res = @query_strategy.exec_params(*final_builder.to_exec_params)
57
+ res = res.group_by { _1['subscription_id'] }
58
+ res.to_h do |sid, indexes|
59
+ [
60
+ sid,
61
+ indexes.map do |attrs|
62
+ EventGlobalIndex::SubscriptionRepr.new(**attrs.except('subscription_id').transform_keys(&:to_sym))
63
+ end,
64
+ ]
65
+ end
66
+ end
67
+
68
+ # @param indexes [Array<EventGlobalIndex::ReadApiRepr>, Array<EventGlobalIndex::SubscriptionRepr>]
69
+ # @param resolve_link_tos [Boolean]
70
+ # @param direction [String, Symbol, nil]
71
+ # @return [Array<Hash>]
72
+ def resolve_indexes(indexes, resolve_link_tos:, direction: nil)
73
+ indexes = indexes.group_by(&:event_type_partition_id)
74
+ partitions = partition_queries.find_by_ids(indexes.keys).to_h { [_1['id'], _1] }
75
+ builders = indexes.map do |partition_id, idxs|
76
+ partition = partitions[partition_id]
77
+ events_filtering = QueryBuilders::EventsFiltering.new
78
+ events_filtering.add_stream_attrs(context: partition['context'], stream_name: partition['stream_name'])
79
+ events_filtering.add_event_types([partition['event_type']])
80
+ builder = events_filtering.to_sql_builder
81
+ builder.where('global_position = ANY(?)', idxs.map(&:global_position))
82
+ end
83
+ main_builder = SQLBuilder.union_builders(builders)
84
+ with_sorted_events = SQLBuilder.new.select('*').from(main_builder)
85
+ if direction
86
+ with_sorted_events.order("global_position #{QueryBuilders::BasicFiltering::SQL_DIRECTIONS[direction]}")
87
+ end
88
+ raw_events = @query_strategy.exec_params(*with_sorted_events.to_exec_params).to_a
89
+ resolve_link_tos ? links_resolver.resolve(raw_events) : raw_events
90
+ end
91
+
92
+ # @return [Integer, nil]
93
+ def max_global_position
94
+ builder = SQLBuilder.new.from(QueryBuilders::EventsGlobalIndexFiltering::PRIMARY_TABLE_NAME)
95
+ builder.select('max(global_position) as max_global_position')
96
+ @query_strategy.exec_params(*builder.to_exec_params).first['max_global_position']
97
+ end
98
+
99
+ # Takes an array of potentially persisted events and loads their ids from db. Those ids can be later used to check
100
+ # whether events are actually existing events.
101
+ # @param events [Array<PgEventstore::Event>]
102
+ # @return [Array<Integer>]
103
+ def global_positions_from_db(events)
104
+ builder = SQLBuilder.new.from(QueryBuilders::EventsGlobalIndexFiltering::PRIMARY_TABLE_NAME)
105
+ builder.where('global_position = ANY(?::bigint[])', events.map(&:global_position))
106
+ @query_strategy.exec_params(*builder.to_exec_params).map { _1['global_position'] }
107
+ end
108
+
109
+ private
110
+
111
+ # @return [PgEventstore::PartitionQueries]
112
+ def partition_queries
113
+ PartitionQueries.new(connection)
114
+ end
115
+
116
+ # @return [PgEventstore::LinksResolver]
117
+ def links_resolver
118
+ LinksResolver.new(connection, @query_strategy)
119
+ end
120
+
121
+ # @param repr [Symbol, nil]
122
+ # @return [Array<PgEventstore::EventGlobalIndex>, Array<PgEventstore::EventGlobalIndex::SubscriptionRepr>,
123
+ # Array<PgEventstore::EventGlobalIndex::ReadApiRepr>, Array<PgEventstore::EventGlobalIndex::WriteApiRepr>,
124
+ # Array<PgEventstore::EventGlobalIndex::RevisionCheckRepr>]
125
+ def deserialize(pg_result, repr: nil)
126
+ pg_result.map do |attrs|
127
+ EventGlobalIndex.create_representation(attrs.transform_keys(&:to_sym), repr:)
128
+ end
129
+ end
130
+ end
131
+ end