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,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module QueryBuilders
5
+ # @!visibility private
6
+ class EventsGlobalIndexFiltering
7
+ include BasicFiltering
8
+
9
+ # @return [Integer]
10
+ DEFAULT_LIMIT = 1_000
11
+ # @return [String]
12
+ PRIMARY_TABLE_NAME = 'events_global_index'
13
+
14
+ class << self
15
+ # @param filter_row [PgEventstore::QueryBuilders::Filters::FilterRow]
16
+ # @param cursor [PgEventstore::QueryBuilders::ReadCursor::StreamCursor]
17
+ # @param seq_num [Integer]
18
+ # @return [PgEventstore::SQLBuilder]
19
+ def for_revision_validation_per_type(filter_row, cursor, seq_num)
20
+ builder = filtering_from_filter_row(filter_row, cursor).to_sql_builder
21
+ builder.unselect.select("stream_revision, #{seq_num} as sequence_number")
22
+ end
23
+
24
+ # @param filter_row [PgEventstore::QueryBuilders::Filters::FilterRow]
25
+ # @param cursor [PgEventstore::QueryBuilders::ReadCursor::StreamCursor]
26
+ # @return [PgEventstore::SQLBuilder]
27
+ def for_read_grouped(filter_row, cursor)
28
+ filtering_from_filter_row(filter_row, cursor).to_sql_builder
29
+ end
30
+
31
+ # @param filter_row [PgEventstore::QueryBuilders::Filters::FilterRow]
32
+ # @param cursor [PgEventstore::QueryBuilders::ReadCursor::StreamCursor]
33
+ # @return [PgEventstore::SQLBuilder]
34
+ def for_read_common(filter_row, cursor)
35
+ filtering_from_filter_row(filter_row, cursor).to_sql_builder
36
+ end
37
+
38
+ # @param cursor [PgEventstore::QueryBuilders::ReadCursor::StreamCursor]
39
+ # @return [PgEventstore::QueryBuilders::EventsGlobalIndexFiltering]
40
+ def default_filtering(cursor)
41
+ index_filtering = new
42
+ add_direction_and_limit(index_filtering, cursor)
43
+ if cursor.all_stream_cursor?
44
+ index_filtering.from_position(cursor.from, cursor.direction)
45
+ index_filtering.to_position(cursor.to, cursor.direction)
46
+ else
47
+ index_filtering.from_revision(cursor.from, cursor.direction)
48
+ index_filtering.to_revision(cursor.to, cursor.direction)
49
+ end
50
+ index_filtering
51
+ end
52
+
53
+ private
54
+
55
+ # @param filter_row [PgEventstore::QueryBuilders::Filters::FilterRow]
56
+ # @param cursor [PgEventstore::QueryBuilders::ReadCursor::StreamCursor]
57
+ # @return [PgEventstore::QueryBuilders::EventsGlobalIndexFiltering]
58
+ def filtering_from_filter_row(filter_row, cursor)
59
+ index_filtering = default_filtering(cursor)
60
+ index_filtering.add_filter_row(filter_row)
61
+ index_filtering
62
+ end
63
+
64
+ # @param index_filtering [PgEventstore::QueryBuilders::EventsGlobalIndexFiltering]
65
+ # @param cursor [PgEventstore::QueryBuilders::ReadCursor::StreamCursor]
66
+ # @return [void]
67
+ def add_direction_and_limit(index_filtering, cursor)
68
+ if cursor.all_stream_cursor?
69
+ index_filtering.add_global_position_direction(cursor.direction)
70
+ else
71
+ index_filtering.add_stream_revision_direction(cursor.direction)
72
+ end
73
+ index_filtering.add_limit(cursor.max_count)
74
+ end
75
+ end
76
+
77
+ def initialize
78
+ @sql_builder = SQLBuilder.new
79
+ @sql_builder.select("#{to_table_name}.global_position, #{to_table_name}.event_type_partition_id")
80
+ @sql_builder.from(to_table_name)
81
+ end
82
+
83
+ def to_sql_builder
84
+ @sql_builder
85
+ end
86
+
87
+ # @return [String]
88
+ def to_table_name
89
+ PRIMARY_TABLE_NAME
90
+ end
91
+
92
+ # @param filter_row [PgEventstore::QueryBuilders::Filters::FilterRow]
93
+ # @return [void]
94
+ def add_filter_row(filter_row)
95
+ stream_filter = filter_row.stream_filter
96
+ event_type_filters = filter_row.event_type_filters
97
+ operator, rhs = event_type_comparison(filter_row)
98
+ # Collapse context/stream name & event type filters into event type filters
99
+ if filter_row.collapsable_into_event_types_only?
100
+ affected_partitions = PartitionsFiltering.from_filter_row(filter_row)
101
+ partitions_builder = affected_partitions.select_id_only
102
+ return @sql_builder.where_or(
103
+ "#{to_table_name}.event_type_partition_id #{operator} #{rhs}",
104
+ partitions_builder
105
+ )
106
+ end
107
+
108
+ query_parts = []
109
+ if stream_filter&.stream?
110
+ streams_filter = StreamsGlobalIndexFiltering.new
111
+ streams_filter.add_filter_row(filter_row)
112
+ query_parts << [
113
+ "#{to_table_name}.streams_global_index_id = ?", streams_filter.to_sql_builder.select_id_only
114
+ ]
115
+ elsif stream_filter&.context?
116
+ affected_partitions = PartitionsFiltering.from_filter_row(filter_row, scope: :context)
117
+ query_parts << ["#{to_table_name}.context_partition_id = ?", affected_partitions.select_id_only]
118
+ elsif stream_filter&.stream_name?
119
+ affected_partitions = PartitionsFiltering.from_filter_row(filter_row, scope: :stream_name)
120
+ query_parts << ["#{to_table_name}.stream_name_partition_id = ?", affected_partitions.select_id_only]
121
+ end
122
+
123
+ if event_type_filters.any?
124
+ affected_partitions = PartitionsFiltering.from_filter_row(filter_row)
125
+ query_parts << [
126
+ "#{to_table_name}.event_type_partition_id #{operator} #{rhs}",
127
+ affected_partitions.select_id_only,
128
+ ]
129
+ end
130
+
131
+ attributes_sql, values = query_parts.transpose
132
+ @sql_builder.where_or(attributes_sql.join(' and '), *values)
133
+ end
134
+
135
+ # @param position [Integer, nil]
136
+ # @param direction [String, Symbol, nil]
137
+ # @return [void]
138
+ def from_position(position, direction)
139
+ return unless position
140
+
141
+ @sql_builder.where("#{to_table_name}.global_position #{direction_operator_from(direction)} ?", position)
142
+ end
143
+
144
+ # @param revision [Integer, nil]
145
+ # @param direction [String, Symbol, nil]
146
+ # @return [void]
147
+ def from_revision(revision, direction)
148
+ return unless revision
149
+
150
+ @sql_builder.where("#{to_table_name}.stream_revision #{direction_operator_from(direction)} ?", revision)
151
+ end
152
+
153
+ # @param position [Integer, nil]
154
+ # @param direction [String, Symbol, nil]
155
+ # @return [void]
156
+ def to_position(position, direction)
157
+ return unless position
158
+
159
+ @sql_builder.where("#{to_table_name}.global_position #{direction_operator_to(direction)} ?", position)
160
+ end
161
+
162
+ # @param revision [Integer, nil]
163
+ # @param direction [String, Symbol, nil]
164
+ # @return [void]
165
+ def to_revision(revision, direction)
166
+ return unless revision
167
+
168
+ @sql_builder.where("#{to_table_name}.stream_revision #{direction_operator_to(direction)} ?", revision)
169
+ end
170
+
171
+ # @param limit [Integer, nil]
172
+ # @return [void]
173
+ def add_limit(limit)
174
+ @sql_builder.limit(limit || DEFAULT_LIMIT)
175
+ end
176
+
177
+ # @param direction [String, Symbol, nil]
178
+ # @return [void]
179
+ def add_global_position_direction(direction)
180
+ @sql_builder.order("#{to_table_name}.global_position #{SQL_DIRECTIONS[direction]}")
181
+ end
182
+
183
+ # @param direction [String, Symbol, nil]
184
+ # @return [void]
185
+ def add_stream_revision_direction(direction)
186
+ @sql_builder.order("#{to_table_name}.stream_revision #{SQL_DIRECTIONS[direction]}")
187
+ end
188
+
189
+ # @return [void]
190
+ def for_subscription
191
+ spos_table_name = EventSubscriptionPositionsFiltering::PRIMARY_TABLE_NAME
192
+ @sql_builder.select("#{spos_table_name}.subscription_position")
193
+ @sql_builder.join("join #{spos_table_name} using(global_position)")
194
+ @sql_builder.where("#{spos_table_name}.subscription_position >= from_position")
195
+ @sql_builder.where("#{spos_table_name}.subscription_position <= to_position")
196
+ @sql_builder.where("#{to_table_name}.global_position >= from_gpos")
197
+ @sql_builder.where("#{to_table_name}.global_position <= to_gpos")
198
+ @sql_builder.order("#{spos_table_name}.subscription_position asc")
199
+ @sql_builder.limit('max_count')
200
+ end
201
+
202
+ private
203
+
204
+ # @param direction [String, Symbol, nil]
205
+ # @return [String]
206
+ def direction_operator_from(direction)
207
+ SQL_DIRECTIONS[direction] == 'ASC' ? '>=' : '<='
208
+ end
209
+
210
+ # @param direction [String, Symbol, nil]
211
+ # @return [String]
212
+ def direction_operator_to(direction)
213
+ SQL_DIRECTIONS[direction] == 'ASC' ? '<=' : '>='
214
+ end
215
+
216
+ # @param filter_row [PgEventstore::QueryBuilders::Filters::FilterRow]
217
+ # @return [Array<String>]
218
+ def event_type_comparison(filter_row)
219
+ if filter_row.event_type_filters.size > 1 || filter_row.ambiguous_event_type?
220
+ ['=', 'any(array(?)::bigint[])']
221
+ else
222
+ ['=', '?']
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,324 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'stream_filter'
4
+ require_relative 'event_type_filter'
5
+ require_relative 'filter_row'
6
+ require_relative 'marker_filter'
7
+ require_relative 'marker_filter_row'
8
+
9
+ module PgEventstore
10
+ module QueryBuilders
11
+ module Filters
12
+ # @!visibility private
13
+ class Collection
14
+ class << self
15
+ # @param stream [PgEventstore::Stream]
16
+ # @param options [Hash]
17
+ # @return [self]
18
+ def from_stream_and_options(stream, options)
19
+ return from_options(options) if stream.all_stream?
20
+
21
+ options in { filter: Hash => current_filter }
22
+ current_filter = (current_filter || {}).except(:streams)
23
+ from_options(options.merge(filter: { **current_filter, streams: [stream.to_hash] }))
24
+ end
25
+
26
+ # @param options [Hash]
27
+ # @return [self]
28
+ def from_options(options)
29
+ instance = new
30
+ case options
31
+ in { filter: { streams: Array => streams, event_types: Array => event_types } }
32
+ extract_streams_filter(instance, streams)
33
+ extract_event_types_filter(instance, event_types)
34
+ in { filter: { streams: Array => streams } }
35
+ extract_streams_filter(instance, streams)
36
+ in { filter: { event_types: Array => event_types } }
37
+ extract_event_types_filter(instance, event_types)
38
+ else
39
+ # empty filters
40
+ end
41
+ instance
42
+ end
43
+
44
+ private
45
+
46
+ # @param instance [PgEventstore::QueryBuilders::Filters::Collection]
47
+ # @param event_types [Array<String>]
48
+ # @return [void]
49
+ def extract_event_types_filter(instance, event_types)
50
+ event_types.each do |event_type|
51
+ case event_type
52
+ in String
53
+ instance.add_event_type(EventTypeFilter.new(event_type: event_type, prefix: false))
54
+ in { prefix: String => prefix }
55
+ instance.add_event_type(EventTypeFilter.new(event_type: prefix, prefix: true))
56
+ in { markers: [], type: String => type } # Consider empty markers array as regular event type filter
57
+ instance.add_event_type(EventTypeFilter.new(event_type: type, prefix: false))
58
+ in { markers: Array => markers, **opts }
59
+ str_markers = markers.grep(String)
60
+ if str_markers.empty?
61
+ PgEventstore.logger&.debug("Unsupported #{event_type.inspect} markers filter.")
62
+ next
63
+ end
64
+ opts in { type: String => type }
65
+ instance.add_marker(MarkerFilter.new(event_type: type, markers: str_markers))
66
+ in { type: String => type }
67
+ instance.add_event_type(EventTypeFilter.new(event_type: type, prefix: false))
68
+ else
69
+ PgEventstore.logger&.debug("Unsupported #{event_type.inspect} event type filter.")
70
+ end
71
+ end
72
+ end
73
+
74
+ # @param instance [PgEventstore::QueryBuilders::Filters::Collection]
75
+ # @param streams [Array<Hash>, nil]
76
+ # @return [void]
77
+ def extract_streams_filter(instance, streams)
78
+ streams&.each do |stream_attrs|
79
+ matches = (stream_attrs in { context: String, stream_name: String, stream_id: String } |
80
+ { context: String, stream_name: String } |
81
+ { context: String })
82
+ if matches
83
+ instance.add_stream(StreamFilter.new(**stream_attrs.slice(:context, :stream_name, :stream_id)))
84
+ next
85
+ end
86
+
87
+ unsupported_filter_warning(stream_attrs)
88
+ end
89
+ end
90
+
91
+ # @param stream_attrs
92
+ # @return [void]
93
+ def unsupported_filter_warning(stream_attrs)
94
+ PgEventstore.logger&.debug(<<~TEXT)
95
+ Ignoring unsupported stream filter format for searching #{stream_attrs.inspect}. \
96
+ See docs/reading_events.md docs for supported formats.
97
+ TEXT
98
+ end
99
+ end
100
+
101
+ # This class is responsible to index stream filters and compute non-overlapping collection of them. Two stream
102
+ # filters overlap when one stream filter affects on the same or more partitions as another stream filter.
103
+ # Example of overlapping filters:
104
+ # from_options(
105
+ # filter: {
106
+ # streams: [
107
+ # { context: 'FooCtx' },
108
+ # { context: 'FooCtx', stream_name: 'Bar' }
109
+ # ]
110
+ # }
111
+ # )
112
+ # In this particular case first stream filter affects on all partitions of 'FooCtx' context, including those
113
+ # partitions, which are related to 'Foo' stream name partitions from the second stream filter. Thus, was can
114
+ # safely throw away the second filter and keep only the first one.
115
+ # Example of non-overlapping filters:
116
+ # from_options(
117
+ # filter: {
118
+ # streams: [
119
+ # { context: 'FooCtx', stream_name: 'Foo' },
120
+ # { context: 'FooCtx', stream_name: 'Bar' }
121
+ # ]
122
+ # }
123
+ # )
124
+ # This example shows that two filters affect on different set of partitions - ('FooCtx', 'Foo') and
125
+ # ('FooCtx', 'Bar'). Thus, we keep both of them.
126
+ class Index
127
+ attr_reader :path
128
+ attr_accessor :stream_filter
129
+
130
+ def initialize
131
+ @path = {}
132
+ @stream_filter = nil
133
+ end
134
+
135
+ # @param stream_filter [PgEventstore::QueryBuilders::Filters::StreamFilter]
136
+ # @return [void]
137
+ def index(stream_filter)
138
+ root = self
139
+ stream_filter.to_h.each_value do |val|
140
+ root.path[val] ||= self.class.new
141
+ root = root.path[val]
142
+ end
143
+ root.stream_filter = stream_filter
144
+ end
145
+
146
+ # @return [Boolean]
147
+ def empty?
148
+ path.empty? && stream_filter.nil?
149
+ end
150
+
151
+ # @param root [PgEventstore::QueryBuilders::Filters::Collection::Index]
152
+ # @return [Array<PgEventstore::QueryBuilders::Filters::StreamFilter>]
153
+ def find_non_overlapping(root = self)
154
+ return [root.stream_filter] if root.stream_filter
155
+
156
+ root.path.each_value.flat_map do |branch|
157
+ find_non_overlapping(branch)
158
+ end
159
+ end
160
+ end
161
+ private_constant :Index
162
+
163
+ def initialize
164
+ @streams = Index.new
165
+ @event_types = Set.new
166
+ @markers = Set.new
167
+ @prefix_filter = false
168
+ @incomplete_stream_filter = false
169
+ @incomplete_markers_filter = false
170
+ reset
171
+ end
172
+
173
+ # @return [Array<PgEventstore::QueryBuilders::Filters::FilterRow>]
174
+ def collection
175
+ compile unless @compiled
176
+ @compiled
177
+ end
178
+
179
+ # @param stream_filter [PgEventstore::QueryBuilders::Filters::StreamFilter]
180
+ # @return [void]
181
+ def add_stream(stream_filter)
182
+ reset if @compiled
183
+ @incomplete_stream_filter ||= stream_filter.context? || stream_filter.stream_name?
184
+ @streams.index(stream_filter)
185
+ end
186
+
187
+ # @param event_type_filter [PgEventstore::QueryBuilders::Filters::EventTypeFilter]
188
+ # @return [void]
189
+ def add_event_type(event_type_filter)
190
+ reset if @compiled
191
+ @prefix_filter ||= event_type_filter.prefix?
192
+ @event_types.add(event_type_filter)
193
+ end
194
+
195
+ # @param marker_filter [PgEventstore::QueryBuilders::Filters::MarkerFilter]
196
+ # @return [void]
197
+ def add_marker(marker_filter)
198
+ reset if @compiled
199
+ @incomplete_markers_filter ||= marker_filter.event_type.nil?
200
+ @markers.add(marker_filter)
201
+ end
202
+
203
+ # rubocop:disable Naming/PredicatePrefix
204
+ # @return [Boolean]
205
+ def has_event_types?
206
+ @event_types.any?
207
+ end
208
+
209
+ # @return [Boolean]
210
+ def has_markers?
211
+ @markers.any?
212
+ end
213
+
214
+ # @return [Boolean]
215
+ def empty?
216
+ @event_types.empty? && @streams.empty? && @markers.empty?
217
+ end
218
+
219
+ # @return [Boolean]
220
+ def has_prefix_filter?
221
+ @prefix_filter
222
+ end
223
+
224
+ # @return [Boolean]
225
+ def has_incomplete_stream_filter?
226
+ @incomplete_stream_filter
227
+ end
228
+
229
+ # @return [Boolean]
230
+ def has_incomplete_markers_filter?
231
+ @incomplete_markers_filter
232
+ end
233
+ # rubocop:enable Naming/PredicatePrefix
234
+
235
+ private
236
+
237
+ # @return [void]
238
+ def reset
239
+ @compiled = nil
240
+ end
241
+
242
+ # @return [void]
243
+ def compile
244
+ event_types_with_prefixes, event_types_without_prefixes = non_overlapping_event_types
245
+ markers = non_overlapping_markers(event_types_with_prefixes, event_types_without_prefixes)
246
+ event_types = event_types_with_prefixes + event_types_without_prefixes
247
+ streams = @streams.find_non_overlapping
248
+ @compiled =
249
+ if streams.any?
250
+ streams.flat_map do |stream_filter|
251
+ rows = []
252
+ rows << FilterRow.new(stream_filter:, event_type_filters: event_types) if has_event_types?
253
+ markers.each do |marker_filter|
254
+ rows << MarkerFilterRow.new(stream_filter:, marker_filter:)
255
+ end
256
+ rows = [FilterRow.new(stream_filter:, event_type_filters: [])] if rows.empty?
257
+ rows
258
+ end
259
+ else
260
+ rows = []
261
+ rows << FilterRow.new(event_type_filters: event_types) if has_event_types?
262
+ markers.each do |marker_filter|
263
+ rows << MarkerFilterRow.new(marker_filter:)
264
+ end
265
+ rows
266
+ end
267
+ end
268
+
269
+ # @return [Array<Array<PgEventstore::QueryBuilders::Filters::EventTypeFilter>>]
270
+ def non_overlapping_event_types
271
+ with_prefixes = []
272
+ without_prefixes = []
273
+ @event_types.each do |event_type_filter|
274
+ if event_type_filter.prefix?
275
+ with_prefixes.push(event_type_filter)
276
+ else
277
+ without_prefixes.push(event_type_filter)
278
+ end
279
+ end
280
+ return [[], without_prefixes] if with_prefixes.empty?
281
+
282
+ with_prefixes = with_prefixes.sort_by(&:event_type)
283
+ with_prefixes = with_prefixes.each_with_object([with_prefixes.first]) do |event_type_filter, result|
284
+ result.push(event_type_filter) unless event_type_filter.event_type.start_with?(result.last.event_type)
285
+ end
286
+
287
+ without_prefixes = without_prefixes.reject do |event_type_filter|
288
+ with_prefixes.any? { |prefix| event_type_filter.event_type.start_with?(prefix.event_type) }
289
+ end
290
+ [with_prefixes, without_prefixes]
291
+ end
292
+
293
+ # @param event_types_with_prefixes [Array<PgEventstore::QueryBuilders::Filters::EventTypeFilter>]
294
+ # @param event_types_without_prefixes [Array<PgEventstore::QueryBuilders::Filters::EventTypeFilter>]
295
+ # @return [Array<PgEventstore::QueryBuilders::Filters::MarkerFilter>]
296
+ def non_overlapping_markers(event_types_with_prefixes, event_types_without_prefixes)
297
+ markers = @markers.group_by(&:event_type).map do |event_type, filters|
298
+ MarkerFilter.new(event_type:, markers: filters.flat_map(&:markers).uniq)
299
+ end
300
+ markers.reject do |marker|
301
+ next false if marker.event_type.nil?
302
+
303
+ marker_filter_overlaps_with_prefix_filter?(marker, event_types_with_prefixes) ||
304
+ marker_filter_overlaps_with_event_type_filter?(marker, event_types_without_prefixes)
305
+ end
306
+ end
307
+
308
+ # @param marker_filter [PgEventstore::QueryBuilders::Filters::MarkerFilter]
309
+ # @param prefix_filters [Array<PgEventstore::QueryBuilders::Filters::EventTypeFilter>]
310
+ # @return [Boolean]
311
+ def marker_filter_overlaps_with_prefix_filter?(marker_filter, prefix_filters)
312
+ prefix_filters.any? { marker_filter.event_type.start_with?(_1.event_type) }
313
+ end
314
+
315
+ # @param marker_filter [PgEventstore::QueryBuilders::Filters::MarkerFilter]
316
+ # @param event_type_filters [Array<PgEventstore::QueryBuilders::Filters::EventTypeFilter>]
317
+ def marker_filter_overlaps_with_event_type_filter?(marker_filter, event_type_filters)
318
+ event_type_filter = EventTypeFilter.new(event_type: marker_filter.event_type, prefix: false)
319
+ event_type_filters.include?(event_type_filter)
320
+ end
321
+ end
322
+ end
323
+ end
324
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module QueryBuilders
5
+ module Filters
6
+ # @!visibility private
7
+ class EventTypeFilter
8
+ include Extensions::OptionsExtension
9
+ include Extensions::OptionsDefaults
10
+
11
+ class << self
12
+ # @return [PgEventstore::QueryBuilders::Filters::EventTypeFilter]
13
+ def null_filter
14
+ new(event_type: nil, prefix: false)
15
+ end
16
+ end
17
+
18
+ # @!attribute value
19
+ # @return [String, nil]
20
+ attribute(:event_type)
21
+ # @!attribute prefix
22
+ # @return [Boolean]
23
+ attribute(:prefix)
24
+
25
+ # @return [Boolean]
26
+ def prefix?
27
+ prefix
28
+ end
29
+
30
+ # @return [String, nil]
31
+ def to_sql_value
32
+ prefix? ? "#{event_type}%" : event_type
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module QueryBuilders
5
+ module Filters
6
+ # @!visibility private
7
+ class FilterRow
8
+ include Extensions::OptionsExtension
9
+ include Extensions::OptionsDefaults
10
+
11
+ class << self
12
+ # @return [PgEventstore::QueryBuilders::Filters::FilterRow]
13
+ def null_filter_row
14
+ new(event_type_filters: [EventTypeFilter.null_filter])
15
+ end
16
+ end
17
+
18
+ # @!attribute stream_filter
19
+ # @return [PgEventstore::QueryBuilders::Filters::StreamFilter, nil]
20
+ attribute(:stream_filter)
21
+ # @!attribute event_type_filters
22
+ # @return [Array<PgEventstore::QueryBuilders::Filters::EventTypeFilter>]
23
+ attribute(:event_type_filters)
24
+
25
+ # @return [Array<PgEventstore::QueryBuilders::Filters::FilterRow>]
26
+ def flatten
27
+ return [dup] if event_type_filters.none?
28
+
29
+ event_type_filters.map do |event_type_filter|
30
+ self.class.new(stream_filter:, event_type_filters: [event_type_filter])
31
+ end
32
+ end
33
+
34
+ # When we don't have stream filter or stream filter consists of only context or context and stream name, along
35
+ # with event types filter - we can say that this row can be converted into event type only filter if needed.
36
+ # This means we can craft a query by event_type_partition_id column only without involving other columns such as
37
+ # context_partition_id, stream_name_partition_id or streams_global_index_id
38
+ # @return [Boolean]
39
+ def collapsable_into_event_types_only?
40
+ (stream_filter.nil? || stream_filter.context? || stream_filter.stream_name?) && event_type_filters.any?
41
+ end
42
+
43
+ # Event type is ambiguous in case we can't tell which partition it belongs to. We **can** tell the exact
44
+ # partition of the given event type only when we have context and stream name as well.
45
+ # @return [Boolean]
46
+ def ambiguous_event_type?
47
+ return true if event_type_filters.any?(&:prefix?)
48
+
49
+ (stream_filter.nil? || stream_filter.context?) && event_type_filters.any?
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module QueryBuilders
5
+ module Filters
6
+ # @!visibility private
7
+ class MarkerFilter
8
+ include Extensions::OptionsExtension
9
+ include Extensions::OptionsDefaults
10
+
11
+ # @!attribute marker
12
+ # @return [Array<String>]
13
+ attribute(:markers)
14
+ # @!attribute event_type
15
+ # @return [String, nil]
16
+ attribute(:event_type)
17
+ end
18
+ end
19
+ end
20
+ end