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
data/docs/how_it_works.md CHANGED
@@ -2,13 +2,24 @@
2
2
 
3
3
  ## Database architecture
4
4
 
5
- The database is designed specifically for Eventsourcing using Domain-Driven Design. `events` table is partitioned in next way:
5
+ The database is designed specifically for Event Sourcing alongside Domain-Driven Design. Internally, it uses a
6
+ Snowflake-based schema which consists of multiple tables:
7
+ - `events_global_index` is the primary event index fact table
8
+ - `event_markers_index` is a marker-specific factless bridge/index table, and is used as the primary access path for
9
+ marker-based event queries
10
+ - `streams_global_index` is normalized stream-level dimension/reference table for `events_global_index`
11
+ - `partitions` is a dictionary table for physical event partitions
12
+ - `events` is a table that holds all information about an event - its payload, metadata, markers, etc
13
+
14
+ `events` table is additionally partitioned in next way:
6
15
 
7
16
  - For each `Stream#context` there is a subpartition of `events` table. Those tables have `contexts_` prefix.
8
17
  - For each `Stream#stream_name` there is a subpartition of `contexts_` table. Those tables have `stream_names_` prefix.
9
18
  - For each `Event#type` there is a subpartition of `stream_names_` table. Those tables have `event_types_` prefix.
10
19
 
11
- To implement partitions - Declarative Partitioning is used. Partitioning means that you should not have any random values in the combination of `Stream#context`, `Stream#stream_name` and `Event#type`. A combination of those values must have low cardinality(low distinct values number) and must be pre-defined in your application. Otherwise it will lead to the performance degradation. More about PostgreSQL partitions is [here](https://www.postgresql.org/docs/current/ddl-partitioning.html).
20
+ To implement partitions - Declarative Partitioning feature is used. While the implementation scales well with the number
21
+ of partitions - it is recommended your application pre-defines all combinations of contexts, stream names and event
22
+ types. More about PostgreSQL partitions is [here](https://www.postgresql.org/docs/current/ddl-partitioning.html).
12
23
 
13
24
  So, let's say you want to publish next event:
14
25
 
@@ -20,11 +31,15 @@ PgEventstore.client.append_to_stream(stream, event)
20
31
 
21
32
  To actually create `events` record next partitions will be created:
22
33
 
23
- - `contexts_81820a` table which is a subpartition of `events` table. It is needed to handle all events which comes to `"SomeCtx"` context
24
- - `stream_names_ecb803` table which is a subpartition of `contexts_81820a` table. It is needed to handle all events which comes to `"SomeStream"` stream name of `"SomeCtx"` context
25
- - `event_types_aeadd5` table which is a subpartition of `stream_names_ecb803` table. It is needed to handle all events which have `"SomethingChanged"` event type of `"SomeStream"` stream name of `"SomeCtx"` context
34
+ - `contexts_81820a` table which is a subpartition of `events` table. It is needed to handle all events which comes to
35
+ `"SomeCtx"` context
36
+ - `stream_names_ecb803` table which is a subpartition of `contexts_81820a` table. It is needed to handle all events
37
+ which comes to `"SomeStream"` stream name of `"SomeCtx"` context
38
+ - `event_types_aeadd5` table which is a subpartition of `stream_names_ecb803` table. It is needed to handle all events
39
+ which have `"SomethingChanged"` event type of `"SomeStream"` stream name of `"SomeCtx"` context
26
40
 
27
- You can check all partitions and associated with them contexts, stream names and event types by querying `partitions` table. Example(based on the publish sample above):
41
+ You can check all partitions and associated with them contexts, stream names and event types by querying special service
42
+ table called `partitions`. Example(based on the sample above):
28
43
 
29
44
  ```ruby
30
45
  PgEventstore.connection.with do |conn|
@@ -38,26 +53,47 @@ end.to_a
38
53
 
39
54
  ### PostgreSQL settings
40
55
 
41
- The more partitions you have, the more locks are required for operations that affect multiple partitions. Especially it concerns the case when you are [reading events from "all" stream](reading_events.md#reading-from-the-all-stream) without providing any filters. It may lead to the next error:
56
+ The more partitions you have, the more locks are required for operations that affect multiple partitions. It mainly
57
+ concerns the case when you involve many different event types when using `Client#multiple`. It may lead to the next
58
+ error:
42
59
 
43
60
  ```
44
61
  ERROR: out of shared memory (PG::OutOfMemory)
45
- HINT: You might need to increase max_locks_per_transaction.
62
+ HINT: You might need to increase max_pred_locks_per_transaction.
46
63
  ```
47
64
 
48
- PostgreSQL suggests to increase the `max_locks_per_transaction`(the description of it is [here](https://www.postgresql.org/docs/current/runtime-config-locks.html)). The default value is `64`. The good value of this setting really depends on your queries, the number of concurrent transactions, the values of `shared_buffers` and `work_mem` settings. In case you have several thousands of partitions - you may want to set it to `128` or event to `256` from the start. On the other hand - you may want to increase it even earlier(e.g. when having several hundreds of partitions) in case you involve high number of partitions into a single transaction(for example, when using [#multiple](multiple_commands.md)).
65
+ PostgreSQL suggests to increase the `max_pred_locks_per_transaction`(the description of it
66
+ is [here](https://www.postgresql.org/docs/current/runtime-config-locks.html)). The default value is `64`. In case you
67
+ have several thousands of partitions - you may want to set it to `128` or even to `256`.
68
+
69
+ You may also face similar error which refers to `max_locks_per_transaction` setting:
70
+
71
+ ```
72
+ PG::OutOfMemory: ERROR: out of shared memory (PG::OutOfMemory)
73
+ HINT: You might need to increase "max_locks_per_transaction".
74
+ ```
49
75
 
50
- Conclusion: monitor db logs, monitor exceptions and adjust your db settings accordingly.
76
+ The reason of it to appear is the same - too many objects(partition tables, indexes, etc) are involved in a single
77
+ transaction.
51
78
 
52
79
  ## Appending events and multiple commands
53
80
 
54
- You may want to get familiar with [Appending events](appending_events.md) and [multiple commands](multiple_commands.md) first.
81
+ You may want to get familiar with [Appending events](appending_events.md) and [multiple commands](multiple_commands.md)
82
+ first.
55
83
 
56
- `pg_eventstore` internally uses `Serializable` transaction isolation level(more about different transaction isolation levels in PostgreSQL is [here](https://www.postgresql.org/docs/current/transaction-iso.html)). On practice this means that any transaction may fail with serialization error, and the common approach is to restart this transaction. For ruby this means re-execution of the block of code. Which is why there is a warning regarding potential block re-execution when using `#multiple`. However current implementation allows to limit 99% of retries to the manipulations with one stream. For example, when two parallel processes changing the same stream. If different streams are being changed at the same time - it is less likely it would perform retry.
84
+ `pg_eventstore` internally uses `Serializable` and `Repeatable read` transaction isolation levels depending on the API
85
+ method(more about different transaction isolation levels in PostgreSQL is [here](https://www.postgresql.org/docs/current/transaction-iso.html)). On practice this means that any
86
+ transaction may fail with serialization error, and the common approach is to restart this transaction. For ruby this
87
+ means re-execution of the block of code. Which is why there is a warning regarding potential code re-execution when
88
+ using `#multiple`. However, current implementation allows to limit 99% of retries to the manipulations with one stream.
89
+ For example, when two parallel processes changing the same stream. If different streams are being changed at the same
90
+ time - it is less likely it would perform retry.
57
91
 
58
92
  Examples:
59
93
 
60
- - if "process 1" and "process 2" perform the append command at the same time - one of the append commands will be retried:
94
+ - if "process 1" and "process 2" perform the append command at the same time - one of the append commands will be
95
+ retried:
96
+
61
97
  ```ruby
62
98
  # process 1
63
99
  stream = PgEventstore::Stream.new(context: 'MyCtx', stream_name: 'MyStream', stream_id: '1')
@@ -70,7 +106,8 @@ event = PgEventstore::Event.new(type: 'SomethingElseChanged', data: { baz: :bar
70
106
  PgEventstore.client.append_to_stream(stream, event)
71
107
  ```
72
108
 
73
- - if "process 1" performs multiple commands at the same time "process 2" performs append command which involves the same stream from "process 1" - either block of `#multiple` or `#append_to_stream` will be retried:
109
+ - if "process 1" performs multiple commands at the same time "process 2" performs append command which involves the same
110
+ stream from "process 1" - either block of `#multiple` or `#append_to_stream` will be retried:
74
111
 
75
112
  ```ruby
76
113
  # process 1
@@ -88,6 +125,10 @@ event = PgEventstore::Event.new(type: 'SomethingChanged', data: { foo: :bar })
88
125
  PgEventstore.client.append_to_stream(stream2, event)
89
126
  ```
90
127
 
91
- Retries also concern your potential implementation of [middlewares](writing_middleware.md). For example, `YourAwesomeMiddleware#serialize` can be executed several times when append the event. This is especially important when you involve your microservices here - they can receive the same payload several times.
128
+ Retries also concern your potential implementation of [middlewares](writing_middleware.md). For example,
129
+ `YourAwesomeMiddleware#serialize` can be executed several times when appending an event. This is especially important
130
+ when you involve your microservices here - they can receive the same payload several times.
92
131
 
93
- Conclusion. When developing using `pg_eventstore` - always keep in mind that some parts of your implementation can be executed several times before successfully publishing an event, or event when reading events(`#deserializa` middleware method) if you perform reading withing `#multiple` block.
132
+ Conclusion. When developing using `pg_eventstore` - always keep in mind that some parts of your implementation can be
133
+ executed several times before successfully publishing an event, or event when reading events(`#deserializa` middleware
134
+ method) if you perform reading withing `#multiple` block. Thus, make sure your middlewares are idempotent.
@@ -54,6 +54,7 @@ Example:
54
54
 
55
55
  ```ruby
56
56
  require 'securerandom'
57
+
57
58
  class SomethingHappened < PgEventstore::Event
58
59
  end
59
60
 
@@ -61,7 +62,7 @@ event1 = SomethingHappened.new
61
62
  event2 = SomethingHappened.new
62
63
 
63
64
  events_stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'MyAwesomeStream', stream_id: '1')
64
- projection_stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'MyAwesomeProjection', stream_id: SecureRandom.uuid)
65
+ projection_stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'MyAwesomeProjection', stream_id: SecureRandom.uuid_v7)
65
66
 
66
67
  event1, event2 = PgEventstore.client.append_to_stream(events_stream, [event1, event2])
67
68
 
data/docs/maintenance.md CHANGED
@@ -9,14 +9,16 @@ stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'MyStream', st
9
9
  PgEventstore.client.append_to_stream(stream, PgEventstore::Event.new)
10
10
  PgEventstore.maintenance.delete_stream(stream) # => true
11
11
  ```
12
- deletes all events in the given stream. If the given stream does not exist - `false` is returned:
12
+
13
+ deletes all events in the given stream. If the given stream does not exist - `false` is returned:
13
14
 
14
15
  ```ruby
15
16
  stream = PgEventstore::Stream.new(context: 'NonExistingCtx', stream_name: 'NonExistingStream', stream_id: '1')
16
17
  PgEventstore.maintenance.delete_stream(stream) # => false
17
18
  ```
18
19
 
19
- **Please note that this operation does not automatically delete links to deleted events - you have to do delete them separately.**
20
+ **Please note that this operation does not automatically delete links to deleted events - you have to do delete them
21
+ separately.**
20
22
 
21
23
  ## Delete an event
22
24
 
@@ -27,6 +29,7 @@ PgEventstore.client.append_to_stream(stream, PgEventstore::Event.new)
27
29
  event = PgEventstore.client.read(stream, options: { max_count: 1 }).first
28
30
  PgEventstore.maintenance.delete_event(event) # => true
29
31
  ```
32
+
30
33
  deletes the given event. If the given event does not exist - `false` is returned:
31
34
 
32
35
  ```ruby
@@ -38,14 +41,20 @@ PgEventstore.maintenance.delete_event(event) # => true
38
41
  PgEventstore.maintenance.delete_event(event) # => false
39
42
  ```
40
43
 
41
- **Please note that this operation does not automatically delete links to the deleted event - you have to do delete them separately.**
44
+ **Please note that this operation does not automatically delete links to the deleted event - you have to do delete them
45
+ separately.**
42
46
 
43
47
  ### Deleting an event in a large stream
44
48
 
45
- There can be a situation when you would like to delete an event in a large stream. If an event is in the position where there are 1000+ events after it in a stream - a `PgEventstore::TooManyRecordsToLockError` error will be raised. This is because in addition to removing the event, we need to adjust the rest of the stream by updating the `#stream_revision` of all events that come after this event. To overcome this limitation - you can provide `force: true` flag:
49
+ There can be a situation when you would like to delete an event in a large stream. If an event is in the position where
50
+ there are 1000+ events after it in a stream - a `PgEventstore::TooManyRecordsToLockError` error will be raised. This is
51
+ because in addition to removing the event, we need to adjust the rest of the stream by updating the `#stream_revision`
52
+ of all events that come after this event. To overcome this limitation - you can provide `force: true` flag:
46
53
 
47
54
  ```ruby
48
55
  PgEventstore.maintenance.delete_event(event, force: true)
49
56
  ```
50
57
 
51
- Because the update operation can lock thousands of events - `#delete_event` can be slow and can slow down other processes which insert events in the same stream at the same moment. **Thus, it is recommended to avoid using `#delete_event` in your business logic.**
58
+ Because the update operation can lock thousands of events - `#delete_event` can be slow and can slow down other
59
+ processes which insert events in the same stream at the same moment. **Thus, it is recommended to avoid
60
+ using `#delete_event` in your business logic.**
@@ -1,17 +1,19 @@
1
1
  # Multiple commands
2
2
 
3
- `pg_eventstore` implements the `#multiple` method to allow you to make several different commands atomic. Internally it executes the given block within PostgreSQL transaction. Example:
3
+ `pg_eventstore` implements the `#multiple` method to allow you to make several different commands atomic. Internally it
4
+ executes the given block within PostgreSQL transaction. Example:
4
5
 
5
6
  ```ruby
6
7
  PgEventstore.client.multiple do
7
8
  unless PgEventstore.client.read(stream3, options: { max_count: 1, direction: 'Backwards' }).last&.type == 'Removed'
8
9
  PgEventstore.client.append_to_stream(stream1, event1)
9
10
  PgEventstore.client.append_to_stream(stream2, event2)
10
- end
11
+ end
11
12
  end
12
13
  ```
13
14
 
14
- Optionally, you can provide `read_only: true` argument to run the transaction in read-only mode. This, however, will raise `PG::ReadOnlySqlTransaction` exception if any mutating query is executed within the block. Example:
15
+ Optionally, you can provide `read_only: true` argument to run the transaction in read-only mode. This, however, will
16
+ raise `PG::ReadOnlySqlTransaction` exception if any mutating query is executed within the block. Example:
15
17
 
16
18
  ```ruby
17
19
  # Good
@@ -25,10 +27,15 @@ PgEventstore.client.multiple(read_only: true) do
25
27
  end
26
28
  ```
27
29
 
30
+ All commands inside a `multiple` block either all succeed or all fail. This allows you to easily implement complex
31
+ business rules. However, it comes with a price of performance. The more you put in a single block, the higher the chance
32
+ it will have conflicts with other commands run in parallel, increasing overall time to complete. **Because of this
33
+ performance implications, do not put more events than needed in a `multple` block.** You may still want to use it though
34
+ as it could simplify your implementation.
28
35
 
29
- All commands inside a `multiple` block either all succeed or all fail. This allows you to easily implement complex business rules. However, it comes with a price of performance. The more you put in a single block, the higher the chance it will have conflicts with other commands run in parallel, increasing overall time to complete. **Because of this performance implications, do not put more events than needed in a `multple` block.** You may still want to use it though as it could simplify your implementation.
30
-
31
- **Please take into account that due to concurrency of parallel commands, a block of code may be re-run several times before succeeding.** So, if you put any piece of code besides `pg_evenstore`'s commands - make sure it is ready for re-runs. A good and a bad examples:
36
+ **Please take into account that due to concurrency of parallel commands, a block of code may be re-run several times
37
+ before succeeding.** So, if you put any piece of code besides `pg_evenstore`'s commands - make sure it is ready for
38
+ re-runs. A good and a bad examples:
32
39
 
33
40
  ## Bad
34
41
 
@@ -37,7 +44,7 @@ PgEventstore.client.multiple do
37
44
  old_email = PgEventstore.client.read(user_stream, options: { filter: { event_types: ['UserEmailChanged'] }, max_count: 1, direction: 'Backwards' }).first&.data&.dig('email')
38
45
  # Email hasn't changed - prevent publishing unnecessary changes
39
46
  next if old_email == user.email
40
-
47
+
41
48
  PgEventstore.client.append_to_stream(user_stream, UserEmailChanged.new(data: { email: user.email }))
42
49
  # This is the mistake. UserMailer.notify_email_changed may be triggered several times
43
50
  UserMailer.notify_email_changed(user.id, old_email: old_email, new_email: user.email).deliver_later
@@ -62,4 +69,6 @@ UserMailer.notify_email_changed(user.id, old_email: old_email, new_email: user.e
62
69
 
63
70
  ## Side effect of internal implementation
64
71
 
65
- Please note that when publishing an event with a type as part of a `multiple` block that does not yet exist in the database, the block will run twice as the first attempt to publish will always fail due to the way `append_to_stream` is implemented. Consider this when writing expectations in your tests for example.
72
+ Please note that when publishing an event with a type as part of a `multiple` block that does not yet exist in the
73
+ database, the block will run twice as the first attempt to publish will always fail due to the way `append_to_stream` is
74
+ implemented. Consider this when writing expectations in your tests for example.
@@ -12,7 +12,9 @@ PgEventstore.client.read(stream)
12
12
 
13
13
  ### max_count
14
14
 
15
- You can provide the `:max_count` option. This option determines how many records to return in a response. Default is `1000` and it can be changed with the `:max_count` configuration setting (see [**"Configuration"**](configuration.md) chapter):
15
+ You can provide the `:max_count` option. This option determines how many records to return in a response. Default is
16
+ `1000` and it can be changed with the `:max_count` configuration setting (see [**"Configuration"**](configuration.md)
17
+ chapter):
16
18
 
17
19
  ```ruby
18
20
  stream = PgEventstore::Stream.new(context: 'MyAwesomeContext', stream_name: 'User', stream_id: 'f37b82f2-4152-424d-ab6b-0cc6f0a53aae')
@@ -21,7 +23,8 @@ PgEventstore.client.read(stream, options: { max_count: 100 })
21
23
 
22
24
  ### resolve_link_tos
23
25
 
24
- When reading streams with projected events (links to other events) you can chose to resolve those links by setting `resolve_link_tos` to `true`, returning the original event instead of the "link" event.
26
+ When reading streams with projected events (links to other events) you can chose to resolve those links by setting
27
+ `resolve_link_tos` to `true`, returning the original event instead of the "link" event.
25
28
 
26
29
  ```ruby
27
30
  stream = PgEventstore::Stream.new(context: 'MyAwesomeContext', stream_name: 'User', stream_id: 'f37b82f2-4152-424d-ab6b-0cc6f0a53aae')
@@ -39,7 +42,8 @@ PgEventstore.client.read(stream, options: { from_revision: 2 })
39
42
 
40
43
  ### direction
41
44
 
42
- As well as being able to read a stream forwards you can also go backwards. This can be achieved by providing the `:direction` option:
45
+ As well as being able to read a stream forwards you can also go backwards. This can be achieved by providing the
46
+ `:direction` option:
43
47
 
44
48
  ```ruby
45
49
  stream = PgEventstore::Stream.new(context: 'MyAwesomeContext', stream_name: 'User', stream_id: 'f37b82f2-4152-424d-ab6b-0cc6f0a53aae')
@@ -62,7 +66,8 @@ end
62
66
 
63
67
  ## Reading from the "all" stream
64
68
 
65
- "all" stream definition means that you don't scope your events when reading them from the database. To get the "all" `PgEventstore::Stream` instance you have to call the `all_stream` method:
69
+ "all" stream definition means that you don't scope your events when reading them from the database. To get the "all"
70
+ `PgEventstore::Stream` instance you have to call the `all_stream` method:
66
71
 
67
72
  ```ruby
68
73
  PgEventstore::Stream.all_stream
@@ -74,24 +79,18 @@ Now you can use it to read from the "all" stream:
74
79
  PgEventstore.client.read(PgEventstore::Stream.all_stream)
75
80
  ```
76
81
 
77
- You can read from a specific position of the "all" stream. This is very similar to reading from a specific revision of a specific stream, but instead of the `:from_revision` option you have to provide the `:from_position` option:
82
+ You can read from a specific position of the "all" stream. This is very similar to reading from a specific revision of a
83
+ specific stream, but instead of the `:from_revision` option you have to provide the `:from_position` option:
78
84
 
79
85
  ```ruby
80
86
  PgEventstore.client.read(PgEventstore::Stream.all_stream, options: { from_position: 9023, direction: 'Backwards' })
81
87
  ```
82
88
 
83
- ## Reading from "$streams" system stream
84
-
85
- `"$streams"` is a special stream which consists of events with `stream_revision == 0`. This allows you to effectively query all streams. Example:
86
-
87
- ```ruby
88
- stream = PgEventstore::Stream.system_stream("$streams")
89
- PgEventstore.client.read(stream).map(&:stream) # => array of unique streams
90
- ```
91
-
92
89
  ## Middlewares
93
90
 
94
- If you would like to skip some of your registered middlewares from processing events after they being read from a stream - you should use the `:middlewares` argument which allows you to override the list of middlewares you would like to use.
91
+ If you would like to skip some of your registered middlewares from processing events after they being read from a
92
+ stream - you should use the `:middlewares` argument which allows you to override the list of middlewares you would like
93
+ to use.
95
94
 
96
95
  Let's say you have these registered middlewares:
97
96
 
@@ -101,7 +100,8 @@ PgEventstore.configure do |config|
101
100
  end
102
101
  ```
103
102
 
104
- And you want to skip `FooMiddleware` and `BazMiddleware`. You simply have to provide an array of corresponding middleware keys you would like to use:
103
+ And you want to skip `FooMiddleware` and `BazMiddleware`. You simply have to provide an array of corresponding
104
+ middleware keys you would like to use:
105
105
 
106
106
  ```ruby
107
107
  PgEventstore.client.read(PgEventstore::Stream.all_stream, middlewares: %i[bar])
@@ -111,78 +111,239 @@ See [Writing middleware](writing_middleware.md) chapter for info about what is m
111
111
 
112
112
  ## Filtering
113
113
 
114
- When reading events, you can additionally filter the result. Available attributes for filtering depend on the type of stream you are reading from. Reading from the "all" stream supports filters by stream attributes and event types. Reading from a specific stream supports filters by event types only.
114
+ When reading events, you can additionally filter the result. Available attributes for filtering depend on the type of
115
+ stream you are reading from. Reading from the "all" stream supports filters by stream attributes, event types and event
116
+ markers. Reading from a specific stream supports filters by event types and event markers only.
115
117
 
116
118
  ### Specific stream filtering
117
119
 
118
- Filtering events by their types:
120
+ #### Filtering events by their types
119
121
 
122
+ Read `Foo` and `Bar` events from the given stream:
120
123
  ```ruby
121
- stream = PgEventstore::Stream.new(context: 'MYAwesomeContext', stream_name: 'User', stream_id: 'f37b82f2-4152-424d-ab6b-0cc6f0a53aae')
124
+ stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'Foo', stream_id: '1')
122
125
  PgEventstore.client.read(stream, options: { filter: { event_types: %w[Foo Bar] } })
123
126
  ```
124
127
 
128
+ Also, you can provide event type values using `{ type: 'MyType' }` syntax. Example:
129
+
130
+ ```ruby
131
+ stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'Foo', stream_id: '1')
132
+ PgEventstore.client.read(stream, options: { filter: { event_types: [{ type: 'Foo' }, { type: 'Bar' }] } })
133
+ ```
134
+
135
+ #### Filtering events by markers
136
+
137
+ Read events, marked as either `'foo'` **or** `'bar'`:
138
+
139
+ ```ruby
140
+ stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'Foo', stream_id: '1')
141
+ PgEventstore.client.read(stream, options: { filter: { event_types: [{ markers: %w[foo bar] }] } })
142
+ ```
143
+
144
+ ##### Limitations
145
+
146
+ Please note - there is no functional to filter events that are marked with both `'foo'` **and** `'bar'` markers. If you
147
+ need a combination of both - you have to create another event marker - for example `'foobar'` - and additionally mark
148
+ events using it that have both - `'foo'` and `'bar'` markers. Example:
149
+
150
+ ```ruby
151
+ event1 = PgEventstore::Event.new(markers: ['foo'])
152
+ event2 = PgEventstore::Event.new(markers: ['bar'])
153
+ event3 = PgEventstore::Event.new(markers: %w[foo bar foobar])
154
+ stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'Foo', stream_id: '3')
155
+ PgEventstore.client.append_to_stream(stream, [event1, event2, event3])
156
+ # Returns all three events
157
+ PgEventstore.client.read(stream, options: { filter: { event_types: [{ markers: %w[foo bar] }] } })
158
+ # Returns only the third event
159
+ PgEventstore.client.read(stream, options: { filter: { event_types: [{ markers: ['foobar'] }] } })
160
+ ```
161
+
162
+ #### Filtering events by event types with markers
163
+
164
+ You can provide per event type markers list. The effect of this is that each event type is additionally restricted with
165
+ the set of markers. In next example events with `'Foo'` type is only returned when they are additionally marked with
166
+ `'foo'` or `'bar'` markers:
167
+
168
+ ```ruby
169
+ stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'Foo', stream_id: '1')
170
+ PgEventstore.client.read(stream, options: { filter: { event_types: [{ type: 'Foo', markers: %w[foo bar] }] } })
171
+ ```
172
+
173
+ You can also combine event types filter, event type with markers filter and markers filter. Next query returns all
174
+ events that are either:
175
+ - marked with `'baz'`
176
+ - or have type `'Foo'` that are marked with either `'foo'` or `'bar'`
177
+ - or have type `'Bar'`
178
+ ```ruby
179
+ stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'Foo', stream_id: '1')
180
+ PgEventstore.client.read(
181
+ stream,
182
+ options: {
183
+ filter: {
184
+ event_types: [
185
+ { markers: ['baz'] },
186
+ { type: 'Foo', markers: %w[foo bar] },
187
+ { type: 'Bar' }
188
+ ]
189
+ }
190
+ }
191
+ )
192
+ ```
193
+
125
194
  ### "all" stream filtering
126
195
 
127
- **Warning** There is a restriction on a set of stream attributes that can be used when filtering an "all" stream result. Available combinations:
196
+ #### Limitations
197
+
198
+ There is a limitation on a set of stream attributes that can be used when filtering an "all" stream result.
199
+ Available combinations:
128
200
 
129
201
  - `:context`
130
202
  - `:context` and `:stream_name`
131
203
  - `:context`, `:stream_name` and `:stream_id`
132
204
 
133
- All other combinations, like providing only `:stream_name` or providing `:context` with `:stream_id` will be ignored.
205
+ So this is not allowed(filter is simply ignored):
134
206
 
207
+ ```ruby
208
+ PgEventstore.client.read(
209
+ PgEventstore::Stream.all_stream,
210
+ options: { filter: { streams: [{ stream_name: 'FooCtx' }] } }
211
+ ) # => Array of events like if there is no filter at all
212
+ PgEventstore.client.read(
213
+ PgEventstore::Stream.all_stream,
214
+ options: { filter: { streams: [{ stream_name: 'FooCtx', stream_id: '1' }] } }
215
+ ) # => Array of events like if there is no filter at all
216
+ PgEventstore.client.read(
217
+ PgEventstore::Stream.all_stream,
218
+ options: { filter: { streams: [{ stream_id: '1' }] } }
219
+ ) # => Array of events like if there is no filter at all
220
+ ```
135
221
 
136
- Filtering events by type:
222
+ but this is allowed:
137
223
 
138
224
  ```ruby
139
- PgEventstore.client.read(PgEventstore::Stream.all_stream, options: { filter: { event_types: %w[Foo Bar] } })
225
+ PgEventstore.client.read(
226
+ PgEventstore::Stream.all_stream,
227
+ options: { filter: { streams: [{ context: 'FooCtx' }] } }
228
+ )
229
+ PgEventstore.client.read(
230
+ PgEventstore::Stream.all_stream,
231
+ options: { filter: { streams: [{ context: 'FooCtx', stream_name: 'Foo' }] } }
232
+ )
233
+ PgEventstore.client.read(
234
+ PgEventstore::Stream.all_stream,
235
+ options: { filter: { streams: [{ context: 'FooCtx', stream_name: 'Foo', stream_id: '1' }] } }
236
+ )
237
+ ```
238
+
239
+ Also, you can't filter by markers only without a specific event type with only `:context` or `:context` and
240
+ `:stream_name`. So, this is not allowed:
241
+
242
+ ```ruby
243
+ PgEventstore.client.read(
244
+ PgEventstore::Stream.all_stream,
245
+ options: { filter: { streams: [{ context: 'FooCtx' }], event_types: [{ markers: ['foo'] }] } }
246
+ ) # => exception
247
+ PgEventstore.client.read(
248
+ PgEventstore::Stream.all_stream,
249
+ options: { filter: { streams: [{ context: 'FooCtx', stream_name: 'Foo' }], event_types: [{ markers: ['foo'] }] } }
250
+ ) # => exception
251
+ ```
252
+
253
+ but this is allowed:
254
+
255
+ ```ruby
256
+ PgEventstore.client.read(
257
+ PgEventstore::Stream.all_stream,
258
+ options: { filter: { streams: [{ context: 'FooCtx' }], event_types: [{ markers: ['foo'], type: 'Foo' }] } }
259
+ ) # => Array of events
140
260
  ```
141
261
 
142
- Filtering events by context:
262
+ #### Filtering events by type and/or markers
263
+
264
+ Please refer to the documentation of [Specific stream filtering](reading_events.md#reading-from-a-specific-stream) for
265
+ more examples how to filter by event types and/or markers. The only thing which changes is stream argument. To filter
266
+ "all" stream - you have to provide `PgEventstore::Stream.all_stream` instead of specific stream. Example:
143
267
 
144
268
  ```ruby
145
- PgEventstore.client.read(PgEventstore::Stream.all_stream, options: { filter: { streams: [{ context: 'MyAwesomeContext' }] } })
269
+ PgEventstore.client.read(PgEventstore::Stream.all_stream, options: { filter: { event_types: %w[Foo Bar] } })
146
270
  ```
147
271
 
148
- Filtering events by context and name:
272
+ #### Filtering events by context
149
273
 
150
274
  ```ruby
151
- PgEventstore.client.read(PgEventstore::Stream.all_stream, options: { filter: { streams: [{ context: 'MyAwesomeContext', stream_name: 'User' }] } })
275
+ PgEventstore.client.read(
276
+ PgEventstore::Stream.all_stream,
277
+ options: { filter: { streams: [{ context: 'MyAwesomeContext' }] } }
278
+ )
152
279
  ```
153
280
 
154
- Filtering events by stream context, stream name and stream id:
281
+ #### Filtering events by context and name
155
282
 
156
283
  ```ruby
157
- PgEventstore.client.read(PgEventstore::Stream.all_stream, options: { filter: { streams: [{ context: 'MyAwesomeContext', stream_name: 'User', stream_id: 'f37b82f2-4152-424d-ab6b-0cc6f0a53aae' }] } })
284
+ PgEventstore.client.read(
285
+ PgEventstore::Stream.all_stream,
286
+ options: { filter: { streams: [{ context: 'MyAwesomeContext', stream_name: 'User' }] } }
287
+ )
158
288
  ```
159
289
 
160
- You can provide several sets of stream's attributes. The result will be a union of events that match those criteria. For example, next query will return all events that belong to streams with `AnotherContext` context and all events that belong to streams with `MyAwesomeContext` context and `User` stream name:
290
+ #### Filtering events by stream context, stream name and stream id
161
291
 
162
292
  ```ruby
163
- PgEventstore.client.read(PgEventstore::Stream.all_stream, options: { filter: { streams: [{ context: 'AnotherContext' }, { context: 'MyAwesomeContext', stream_name: 'User' }] } })
293
+ PgEventstore.client.read(
294
+ PgEventstore::Stream.all_stream,
295
+ options: {
296
+ filter: {
297
+ streams: [{ context: 'MyAwesomeContext', stream_name: 'User', stream_id: 'f37b82f2-4152-424d-ab6b-0cc6f0a53aae' }]
298
+ }
299
+ }
300
+ )
164
301
  ```
165
302
 
166
- You can also mix filtering by stream's attributes and event types. The result will be intersection of events matching stream's attributes and event's types. For example, next query will return events which type is either `Foo` or `Bar` and which belong to a stream with `MyAwesomeContext` context:
303
+ You can provide several sets of stream's attributes. The result will be a union of events that match those criteria. For
304
+ example, next query will return all events that belong to streams with `AnotherContext` context and all events that
305
+ belong to streams with `MyAwesomeContext` context and `User` stream name:
167
306
 
168
307
  ```ruby
169
- PgEventstore.client.read(PgEventstore::Stream.all_stream, options: { filter: { streams: [{ context: 'MyAwesomeContext' }], event_types: %w[Foo Bar] } })
308
+ PgEventstore.client.read(
309
+ PgEventstore::Stream.all_stream,
310
+ options: {
311
+ filter: {
312
+ streams: [{ context: 'AnotherContext' }, { context: 'MyAwesomeContext', stream_name: 'User' }]
313
+ }
314
+ }
315
+ )
170
316
  ```
171
317
 
172
- ### "$streams" stream filtering
318
+ #### Filtering events by stream attributes, event types and/or markers
319
+
320
+ Please refer to the documentation of [Specific stream filtering](reading_events.md#reading-from-a-specific-stream) for
321
+ more examples how to filter by event types and/or markers.
322
+
323
+ You can also mix filtering by stream attributes, event types and markers. The result is union of combinations of
324
+ each stream filter and event types filter. For example, next query returns events with type either `Foo`, marked
325
+ by `'foo'` marker, or `Bar` and belong to streams from `FooCtx` or `BarCtx` contexts:
173
326
 
174
- When reading from `"$streams"` same rules apply as when reading from "all" stream. For example, read all streams which have `context == "MyAwesomeContext"` and start from events with event type either `"Foo"` or `"Bar"`:
175
327
  ```ruby
176
- PgEventstore.client.read(PgEventstore::Stream.system_stream("$streams"), options: { filter: { streams: [{ context: 'MyAwesomeContext' }], event_types: %w[Foo Bar] } })
328
+ PgEventstore.client.read(
329
+ PgEventstore::Stream.all_stream,
330
+ options: {
331
+ filter: {
332
+ streams: [{ context: 'FooCtx' }, { context: 'BarCtx' }],
333
+ event_types: [{ type: 'Foo', markers: ['foo'] }, { type: 'Bar' }]
334
+ }
335
+ }
336
+ )
177
337
  ```
178
338
 
179
339
  ## Pagination
180
340
 
181
- You can use `#read_paginated` to iterate over all (filtered) events. It yields each batch of records that was found according to the filter options:
341
+ You can use `#read_paginated` to iterate over all (filtered) events. It yields each batch of records that was found
342
+ according to the filter options:
182
343
 
183
344
  ```ruby
184
345
  # Read from the specific stream
185
- stream = PgEventstore::Stream.new(context: 'MyAwesomeContext', stream_name: 'User', stream_id: 'f37b82f2-4152-424d-ab6b-0cc6f0a53aae')
346
+ stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'Foo', stream_id: '1')
186
347
  PgEventstore.client.read_paginated(stream).each do |events|
187
348
  events.each do |event|
188
349
  # iterate through events
@@ -201,7 +362,7 @@ Options are the same as for `#read` method. Several examples:
201
362
 
202
363
  ```ruby
203
364
  # Read "Foo" events only from the specific stream
204
- stream = PgEventstore::Stream.new(context: 'MyAwesomeContext', stream_name: 'User', stream_id: 'f37b82f2-4152-424d-ab6b-0cc6f0a53aae')
365
+ stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'Foo', stream_id: '1')
205
366
  PgEventstore.client.read_paginated(stream, options: { filter: { event_types: ['Foo'] } }).each do |events|
206
367
  events.each do |event|
207
368
  # iterate through events
@@ -223,7 +384,7 @@ PgEventstore.client.read_paginated(PgEventstore::Stream.all_stream, options: { m
223
384
  end
224
385
 
225
386
  # Reading from projection stream
226
- projection_stream = PgEventstore::Stream.new(context: 'MyAwesomeContext', stream_name: 'MyAwesomeProjection', stream_id: 'f37b82f2-4152-424d-ab6b-0cc6f0a53aae')
387
+ projection_stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'FooProjection', stream_id: '1')
227
388
  PgEventstore.client.read_paginated(projection_stream, options: { resolve_link_tos: true }).each do |events|
228
389
  events.each do |event|
229
390
  # iterate through events
@@ -246,7 +407,8 @@ PgEventstore.client.append_to_stream(stream, [event1, event2, event3])
246
407
  PgEventstore.client.read_grouped(stream) # => returns event1 and event3
247
408
  ```
248
409
 
249
- API is very similar to the API of `#read`, but it ignores `:max_count` options as the result is always returns a set of event types in your stream.
410
+ API is very similar to the API of `#read`, but it ignores `:max_count` options as the result is always returns a set of
411
+ event types in your stream.
250
412
 
251
413
  Reading most recent events:
252
414
 
@@ -307,11 +469,14 @@ PgEventstore.client.read_grouped(PgEventstore::Stream.all_stream, options: { dir
307
469
 
308
470
  ### Event types list lookup
309
471
 
310
- If you do not provide event types filter - event types list will be determined based on the rest of arguments(a stream argument or a stream filters option).
472
+ If you do not provide event types filter - event types list will be determined based on the rest of arguments(a stream
473
+ argument or a stream filters option).
311
474
 
312
475
  ### Multiple events of same type in the result
313
476
 
314
- If same event type appear in different streams(different by `#context` and `#stream_name`) - those events will appear in the result. This is because even though `Event#type` value may be the same - its meaning may have different meaning in different `context`/`stream_name` couple. Example:
477
+ If same event type appear in different streams(different by `#context` and `#stream_name`) - those events will appear in
478
+ the result. This is because even though `Event#type` value may be the same - its meaning may have different meaning in
479
+ different `context`/`stream_name` couple. Example:
315
480
 
316
481
  ```ruby
317
482
  stream1 = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'Foo', stream_id: '1')