pg_eventstore 2.0.0 → 3.0.1

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 +190 -20
  4. data/README.md +27 -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 +98 -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 +41 -25
  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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a4799e200314277c53e9991483c429267213a9243a93c0ea85e7a23fa8c9b1ad
4
- data.tar.gz: 38c68fd540c903d19ac316aca849007f3b4c27ab94dc09cfb2e71a9b9acdd734
3
+ metadata.gz: 24899ca6d10bac21341f41f3a8cfee09e10ce10bf81fb4525e9660f6c708d3af
4
+ data.tar.gz: 07cbbca86a3cb488d9123d663e77cb66cdbf850f93e12f1f15e46dc8f465879c
5
5
  SHA512:
6
- metadata.gz: 347354eb5327bf7a38df0682710f9b5a676488910ed3d0e7e967c9bcccd1d4e4b784c87e178dc6f4b5fcea2c8f681bfa0209618cf58f76dd5b36db1577d656f0
7
- data.tar.gz: a567b9bfee1112e16d4f8b96b130e0f01c12b1f5349429c1ac873f4f3b4daa36628239b041eeecdc0f69a9807210d36bb30352dcb05b4100ef14eda37f1c6133
6
+ metadata.gz: bc33cc4bcdda545ffa4476e80b55ca2541de4da036d715644f3957d1889170bbdf3b05b1391c7f981f28163da0ce36d06b0a8282ddeddb7560eb1892df43cfaa
7
+ data.tar.gz: 64b63413791d2c993315d4012acb1a5bec19b237eae6761c6b9da8725483e4284f5fbf3b956df5c4a750719460087f30cec6c9db1f176ce728ad985f2aefa213
data/.rubocop.yml CHANGED
@@ -27,7 +27,7 @@ RSpec/IndexedLet:
27
27
  Enabled: false
28
28
 
29
29
  RSpec/NestedGroups:
30
- Max: 6
30
+ Enabled: false
31
31
 
32
32
  RSpec/LetSetup:
33
33
  Enabled: false
@@ -124,3 +124,12 @@ Gemspec/RequiredRubyVersion:
124
124
 
125
125
  Gemspec/RequireMFA:
126
126
  Enabled: false
127
+
128
+ Style/StructInheritance:
129
+ Enabled: false
130
+
131
+ Lint/NonLocalExitFromIterator:
132
+ Enabled: false
133
+
134
+ Style/RegexpLiteral:
135
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,17 +1,145 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [3.0.1]
4
+
5
+ - Fix EventTracing bug: Respect user-supplied Event#correlation_id value when caused_by#correlation_id is absent
6
+
7
+ ## [3.0.0]
8
+
9
+ - New feature: pg_eventstore replication
10
+ - **Breaking change**: drop support of Ruby v3.2. The gem now requires Ruby v3.3+
11
+ - New feature: event tracing. You can now configure pg_eventstore to trace causation dependencies of events.
12
+ Read more [here](docs/event_tracing.md)
13
+ - **Breaking change**: when event is persisted using `#append_to_stream` - it now gets deserialized via each registered
14
+ middleware by default. Previously this wasn't the case - the deserialization phase was skipped. Such behavior was
15
+ creating ambiguity about assumptions when deserialization happens. If you need old behavior - you have to create
16
+ another middleware class which skips deserialization when publishing events and use it instead. Example:
17
+ Let's say you have this middleware configured
18
+ ```ruby
19
+ class MyMiddleware
20
+ include PgEventstore::Middleware
21
+
22
+ def serialize(event)
23
+ # do something with event before persisting it
24
+ end
25
+
26
+ def deserialize(event)
27
+ # do something with event after reading it from db
28
+ end
29
+ end
30
+
31
+ PgEventstore.configure do |config|
32
+ config.middleware = { my_middleware: MyMiddleware.new }
33
+ end
34
+ ```
35
+
36
+ Now define another middleware that has empty `#deserialize` method
37
+
38
+ ```ruby
39
+ class MyMiddlewareWithoutDeserialize < MyMiddleware
40
+ def deserialize(event)
41
+ end
42
+ end
43
+
44
+ PgEventstore.configure do |config|
45
+ config.middleware = {
46
+ my_middleware: MyMiddleware.new,
47
+ my_middleware_wo_deserialize: MyMiddlewareWithoutDeserialize.new
48
+ }
49
+ end
50
+ ```
51
+ and use only it when publishing events:
52
+
53
+ ```ruby
54
+ PgEventstore.client.append_to_stream(stream, event, middlewares: [:my_middleware_wo_deserialize])
55
+ ```
56
+
57
+ Alternatively, if you don't rely on `#multiple`, you can create another config specially for write operations:
58
+
59
+ ```ruby
60
+ # this is your default
61
+ PgEventstore.configure do |config|
62
+ config.middleware = { my_middleware: MyMiddleware.new }
63
+ end
64
+
65
+ PgEventstore.configure(name: :write) do |config|
66
+ config.middleware = { my_middleware: MyMiddlewareWithoutDeserialize.new }
67
+ end
68
+ ```
69
+
70
+ and use it for write operations:
71
+
72
+ ```ruby
73
+ PgEventstore.client(:write).append_to_stream(stream, event)
74
+ ```
75
+
76
+ - **Breaking change**: `Event#id` uniqueness is no longer guaranteed. It was dropped because there is not much usage of
77
+ it internally. The default value was moved from the database(it was `gen_random_uuid()`) to the application level and
78
+ is `SecureRandom.uuid_v7` now.
79
+ - **Breaking change**: event types, stream attributes, event metadata keys, markers that start from `▒`(`"\u2592"`
80
+ Unicode character) character are now reserved by pg_eventstore. It is less likely you have any, but if you do - you have
81
+ to adjust your implementation to no longer rely on it.
82
+ - New feature: event markers. You can now mark an event and later use those markers to read events, build a projection
83
+ using subscriptions or validate stream revision scoped to an event type and specific markers when publishing events as
84
+ a part of Dynamic Consistency Boundaries. Find more in [docs](docs/appending_events.md#event-markers)
85
+ - New config option `config.events_subscription_position_update_interval`. See more
86
+ in [Configuration](docs/configuration.md) docs
87
+ - **Breaking change**: `:from_position` option of `SubscriptionManager#subscribe`, "Current position" column in admin
88
+ web UI, "Reset position" form in admin web UI now refers to the event subscription position instead
89
+ `Event#global_position`. See more information in related
90
+ docs [Subscription position](docs/subscriptions.md#subscription-position)
91
+ - **Breaking change**: Drop `pg_cron` dependency
92
+ - Remove deprecated `SubscriptionsManager#force_lock!`
93
+ - New feature: explicit support of Dynamic Consistency Boundaries when publishing events. You can now provide "event
94
+ type"-to-"expected stream revision" hash map into `:expected_revision` option. It allows to validate against stream
95
+ revision at the specific event type instead validating the whole stream. Example:
96
+ `PgEventstore.client.append_to_stream(stream, events, options: { expected_revision: { 'Foo' => 10, 'Bar' => 20 } })`
97
+ - **Breaking change**: `WrongExpectedRevisionError#new` now requires `:verdict` keyword argument.
98
+ `Client#append_to_stream` now also may raise `WrongExpectedTypesRevisionError` error in case `:expected_revision`
99
+ option is "event type"-to-"expected stream revision" hash map
100
+ - Remove any practical limit of `Event#stream_revision` by changing its type to `bigint`. Stream size can be practically
101
+ unreachable now
102
+ - Fix issue when incomplete transaction block may be interrupted half way and commited successfully. The issue is
103
+ related to incorrect transaction termination flow during non-graceful thread shutdown(e.g. via `Thread.exit`)
104
+ - New feature: `Client#stream_revision` allows to quickly get stream's revision. Example:
105
+ `PgEventstore.client.stream_revision(stream) #=> number, greater than or equal to 0 or -1 if stream does not exist`
106
+ - **Breaking change**: `Client#read_grouped` now requires to provide `event_types` filter option explicitly. Example:
107
+ `PgEventstore.client.read_grouped(stream, options: { filter: { event_types: ['Foo', 'Bar'] } })`
108
+ - New feature: Subscription event types filter now support prefixes. So, if you have subscriptions like
109
+ `manager.subscribe('MyAwesomeSubscription', handler: my_handler, options: { filter: { event_types: ['Foo1', 'Foo2', 'Foo3'] } })`,
110
+ you can replace its filter as follows: `{ event_types: [{ prefix: 'Foo' }] }`. **Read API does not support this
111
+ feature yet**
112
+ - New feature: Read API now supports `:to_position`(and `:to_revision` when reading from specific stream) option. Use it
113
+ if you want to limit your result alongside `:max_count` option. Example:
114
+ `PgEventstore.client.read(PgEventstore::Stream.all_stream, options: { from_position: 100, to_position: 200 })`
115
+ - **Breaking change**: `Middleware#serialize` is now called outside SQL transaction that inserts an event tuple.
116
+ Previously it was happening right before an `INSERT` statement
117
+ - **Breaking change**: Database structure and the implementation of `#append_to_stream` was changed in a way it is not
118
+ compatible with v2. Public API stays the same
119
+ - New feature: allow subscription to process events in batches. You can now provide `in_batches: true` to `#subscribe`
120
+ to yield an array of events. See [docs](docs/subscriptions.md#processing-events-in-batches) for more info
121
+ - Introduce `PgEventstore::BasicConfig` and `PgEventstore::Extensions::ActsAsConfigurable` to allow to easily create
122
+ configurable objects
123
+
3
124
  ## [2.0.0]
4
125
 
5
126
  - **Breaking change**: `pg_eventstore` now requires [pg_cron](https://github.com/citusdata/pg_cron) extension
6
127
  - **Breaking change**: `pg_eventstore` now requires PostgreSQL v16+
7
128
  - Greatly decreased the number of connections, used by `pg_eventstore` subscriptions
8
129
  - **Breaking change**: drop support of Ruby v3.0 and v3.1. The gem now requires Ruby v3.2+
9
- - **Breaking change**: `PgEventstore::Extensions::OptionsExtension::Options` class is no longer a child of `Set` class - it has independent implementation now
130
+ - **Breaking change**: `PgEventstore::Extensions::OptionsExtension::Options` class is no longer a child of `Set` class -
131
+ it has independent implementation now
10
132
  - Add support of Ruby v4.0
11
- - `Client#multiple` method now accepts `read_only` keyword argument. When it is set to true - transaction is run in read-only mode
12
- - **Breaking change**: rework links implementation. This change boosts performance, but affects the database structure, so your previous database dumps become incompatible with this change. `PgEventstore::Event#link_id` was replaced by `PgEventstore::Event#link_global_position`
133
+ - `Client#multiple` method now accepts `read_only` keyword argument. When it is set to true - transaction is run in
134
+ read-only mode
135
+ - **Breaking change**: rework links implementation. This change boosts performance, but affects the database structure,
136
+ so your previous database dumps become incompatible with this change. `PgEventstore::Event#link_id` was replaced by
137
+ `PgEventstore::Event#link_global_position`
13
138
 
14
- Changes above require you to run migrations - `bundle exec rake pg_eventstore:migrate`. One of the migrations also migrates existing data using several concurrent workers(threads). You can adjust the number of workers using `CONCURRENCY` environment variable. Default number of concurrent workers is `10`. **Migrations require a downtime - no reads/writes should be performed during the time of the migrations, so plan your maintenance downtime accordingly.**
139
+ Changes above require you to run migrations - `bundle exec rake pg_eventstore:migrate`. One of the migrations also
140
+ migrates existing data using several concurrent workers(threads). You can adjust the number of workers using
141
+ `CONCURRENCY` environment variable. Default number of concurrent workers is `10`. **Migrations require a downtime - no
142
+ reads/writes should be performed during the time of the migrations, so plan your maintenance downtime accordingly.**
15
143
 
16
144
  ## [1.13.4]
17
145
 
@@ -31,7 +159,8 @@ Changes above require you to run migrations - `bundle exec rake pg_eventstore:mi
31
159
 
32
160
  ## [1.13.0]
33
161
 
34
- - Introduce automatic subscriptions recovery from connection errors. This way if a subscription process loses the connection to the database - it will be trying to reconnect until the connection is restored.
162
+ - Introduce automatic subscriptions recovery from connection errors. This way if a subscription process loses the
163
+ connection to the database - it will be trying to reconnect until the connection is restored.
35
164
  - Resolve ambiguity in usage of `PgEventstore.config` method. It now returns the frozen object.
36
165
 
37
166
  ## [1.12.0]
@@ -40,12 +169,17 @@ Changes above require you to run migrations - `bundle exec rake pg_eventstore:mi
40
169
 
41
170
  ## [1.11.0]
42
171
 
43
- - Add a global position that caused an error to the subscription's error JSON info. This will help you understand what event caused your subscription to fail.
172
+ - Add a global position that caused an error to the subscription's error JSON info. This will help you understand what
173
+ event caused your subscription to fail.
44
174
  - Improve long payloads in JSON preview in admin web UI in the way it does not moves content out of the visible area.
45
- - Admin UI: adjust events filtering and displaying of stream context, stream name, stream id and event type when values of them contain empty strings or non-displayable characters
175
+ - Admin UI: adjust events filtering and displaying of stream context, stream name, stream id and event type when values
176
+ of them contain empty strings or non-displayable characters
46
177
 
47
178
  ## [1.10.0]
48
- - Admin UI: Adjust `SubscriptionSet` "Stop"/"Delete" buttons appearance. Now if `SubscriptionsSet` is not alive anymore(the related process is dead or does not exist anymore) - "Delete" button is shown. If `SubscriptionSet` is alive - "Stop" button is shown
179
+
180
+ - Admin UI: Adjust `SubscriptionSet` "Stop"/"Delete" buttons appearance. Now if `SubscriptionsSet` is not alive anymore(
181
+ the related process is dead or does not exist anymore) - "Delete" button is shown. If `SubscriptionSet` is alive - "
182
+ Stop" button is shown
49
183
  - Admin IU: fixed several potential XSS vulnerabilities
50
184
  - Admin IU: Add "Copy to clipboard" button near stream id that copies ruby stream definition
51
185
  - Admin UI: allow deletion of streams with empty attribute values
@@ -57,23 +191,33 @@ Changes above require you to run migrations - `bundle exec rake pg_eventstore:mi
57
191
  - Add "Delete event" and "Delete stream" buttons into admin UI
58
192
 
59
193
  ## [1.8.0]
60
- - Introduce default config for admin web UI. Now if you define `:admin_web_ui` config - it will be preferred over default config
194
+
195
+ - Introduce default config for admin web UI. Now if you define `:admin_web_ui` config - it will be preferred over
196
+ default config
61
197
  - Fix pagination of events in admin UI
62
198
  - Improve partial index for `$streams` system stream
63
199
 
64
200
  ## [1.7.0]
201
+
65
202
  - Implement reading from `"$streams"` system stream
66
203
  - Disable Host authorization introduced in sinatra v4.1
67
204
 
68
205
  ## [1.6.0]
69
- - Introduce subscriptions CLI. Type `pg-eventstore subscriptions --help` to see available commands. The main purpose of it is to provide the single way to start/stop subscription processes. Check [Subscriptions](docs/subscriptions.md#creating-a-subscription) docs about the new way to start and keep running a subscriptions process.
206
+
207
+ - Introduce subscriptions CLI. Type `pg-eventstore subscriptions --help` to see available commands. The main purpose of
208
+ it is to provide the single way to start/stop subscription processes.
209
+ Check [Subscriptions](docs/subscriptions.md#creating-a-subscription) docs about the new way to start and keep running
210
+ a subscriptions process.
70
211
 
71
212
  ## [1.5.0]
213
+
72
214
  - Add ability to toggle link events in the admin UI
73
215
  - Mark linked events in the admin UI with "link" icon
74
216
 
75
217
  ## [1.4.0]
76
- - Add an ability to configure subscription graceful shutdown timeout globally and per subscription. Default value is 15 seconds. Previously it was hardcoded to 5 seconds. Examples:
218
+
219
+ - Add an ability to configure subscription graceful shutdown timeout globally and per subscription. Default value is 15
220
+ seconds. Previously it was hardcoded to 5 seconds. Examples:
77
221
 
78
222
  ```ruby
79
223
  # Set it globally, for all subscriptions
@@ -90,18 +234,24 @@ subscriptions_manager.subscribe(
90
234
  ```
91
235
 
92
236
  ## [1.3.4]
93
- - Fix `NoMethodError` error in `Client#read_paginated` when stream does not exist or when there are no events matching the given filter
237
+
238
+ - Fix `NoMethodError` error in `Client#read_paginated` when stream does not exist or when there are no events matching
239
+ the given filter
94
240
 
95
241
  ## [1.3.3]
242
+
96
243
  - Adjust default value of `subscription_max_retries` setting
97
244
 
98
245
  ## [1.3.2]
246
+
99
247
  - Fix UI when switching subscription status
100
248
 
101
249
  ## [1.3.1]
250
+
102
251
  - Swap "Search" button and "Add filter" button on Dashboard page
103
252
 
104
253
  ## [1.3.0]
254
+
105
255
  - Add ability to filter subscriptions by state in admin UI
106
256
  - Reset error-related subscription's attributes on subscription restore
107
257
  - Reset total processed events number when user changes subscription's position
@@ -109,9 +259,12 @@ subscriptions_manager.subscribe(
109
259
  - Relax sinatra version requirement to v3+
110
260
 
111
261
  ## [1.2.0]
262
+
112
263
  - Implement `failed_subscription_notifier` subscription hook.
113
264
 
114
- Now you are able to define a function that is called when subscription fails and no longer can be automatically restarted because it hit max number of retries. You can define the hook globally in the config and per subscription. Examples:
265
+ Now you are able to define a function that is called when subscription fails and no longer can be automatically
266
+ restarted because it hit max number of retries. You can define the hook globally in the config and per subscription.
267
+ Examples:
115
268
 
116
269
  ```ruby
117
270
  PgEventstore.configure do |config|
@@ -135,36 +288,48 @@ subscriptions_manager.subscribe(
135
288
  ```
136
289
 
137
290
  ## [1.1.5]
138
- - Review the way to handle SubscriptionAlreadyLockedError error. This removes noise when attempting to lock an already locked subscription.
291
+
292
+ - Review the way to handle SubscriptionAlreadyLockedError error. This removes noise when attempting to lock an already
293
+ locked subscription.
139
294
 
140
295
  ## [1.1.4]
296
+
141
297
  - Add rbs signatures
142
298
 
143
299
  ## [1.1.3]
300
+
144
301
  - Fix issue with assets caching between different gem's versions
145
302
 
146
303
  ## [1.1.2]
304
+
147
305
  - Improve web app compatibility with rails
148
306
 
149
307
  ## [1.1.1]
308
+
150
309
  - Allow case insensitive search by context, stream name and event type in admin UI
151
310
 
152
311
  ## [1.1.0]
312
+
153
313
  - Add "Reset position" button on Subscriptions Admin UI page
154
314
 
155
- **Note** This release includes a migration to support new functional. Please don't forget to run `rake pg_eventstore:migrate` to apply latest db changes.
315
+ **Note** This release includes a migration to support new functional. Please don't forget to run
316
+ `rake pg_eventstore:migrate` to apply latest db changes.
156
317
 
157
318
  ## [1.0.4]
319
+
158
320
  - Fix bug which caused slow Subscriptions to stop processing new events
159
321
  - Optimize Subscriptions update queries
160
322
 
161
323
  ## [1.0.3]
324
+
162
325
  - Do no update `Subscription#last_chunk_fed_at` if the chunk is empty
163
326
 
164
327
  ## [1.0.2]
328
+
165
329
  - UI: Fix opening of SubscriptionsSet tab of non-existing SubscriptionsSet
166
330
 
167
331
  ## [1.0.1]
332
+
168
333
  - Adjust assets urls to correctly act when mounting sinatra app under non-root url
169
334
 
170
335
  ## [1.0.0]
@@ -202,7 +367,8 @@ subscriptions_manager.subscribe(
202
367
 
203
368
  ## [0.8.0] - 2024-02-20
204
369
 
205
- - Allow float values for `subscription_pull_interval`. The default value of it was also set to `1.0`(it was `2` previously)
370
+ - Allow float values for `subscription_pull_interval`. The default value of it was also set to `1.0`(it was `2`
371
+ previously)
206
372
 
207
373
  ## [0.7.2] - 2024-02-14
208
374
 
@@ -214,7 +380,8 @@ subscriptions_manager.subscribe(
214
380
 
215
381
  ## [0.7.0] - 2024-02-09
216
382
 
217
- - Refactor `pg_eventstore:create` and `pg_eventstore:drop` rake tasks. They now actually create/drop the database. You will have to execute `delete from migrations where number > 6` query before deploying this version.
383
+ - Refactor `pg_eventstore:create` and `pg_eventstore:drop` rake tasks. They now actually create/drop the database. You
384
+ will have to execute `delete from migrations where number > 6` query before deploying this version.
218
385
  - Drop legacy migrations
219
386
 
220
387
  ## [0.6.0] - 2024-02-08
@@ -259,9 +426,11 @@ subscriptions_manager.subscribe(
259
426
 
260
427
  ## [0.2.4] - 2023-12-20
261
428
 
262
- Due to performance issues under certain circumstances, searching by event type was giving bad performance. I decided to extract `type` column from `events` table into separated table. **No breaking changes in public API though.**
429
+ Due to performance issues under certain circumstances, searching by event type was giving bad performance. I decided to
430
+ extract `type` column from `events` table into separated table. **No breaking changes in public API though.**
263
431
 
264
- **Warning** The migrations this version has, requires you to shut down applications that use `pg_eventstore` and only then run `rake pg_eventstore:migrate`.
432
+ **Warning** The migrations this version has, requires you to shut down applications that use `pg_eventstore` and only
433
+ then run `rake pg_eventstore:migrate`.
265
434
 
266
435
  ## [0.2.3] - 2023-12-18
267
436
 
@@ -273,7 +442,8 @@ Due to performance issues under certain circumstances, searching by event type w
273
442
 
274
443
  ## [0.2.1] - 2023-12-14
275
444
 
276
- Under certain circumstances `PG::TRSerializationFailure` exception wasn't retried. Adjust connection's states list to fix that.
445
+ Under certain circumstances `PG::TRSerializationFailure` exception wasn't retried. Adjust connection's states list to
446
+ fix that.
277
447
 
278
448
  ## [0.2.0] - 2023-12-14
279
449
 
data/README.md CHANGED
@@ -4,11 +4,23 @@ Implements database and API to store and read events in event sourced systems.
4
4
 
5
5
  ## Requirements
6
6
 
7
- - `pg_eventstore` requires a PostgreSQL v16+ with [pg_cron](https://github.com/citusdata/pg_cron) extension installed.
8
- - `pg_evenstore` requires a separate detabase. However, it is recommended that you spin it up on a separate PostgreSQL instance in a production environment.
7
+ - `pg_eventstore` requires a PostgreSQL v16+.
8
+ - `pg_evenstore` requires a separate database. However, it is recommended that you spin it up on a separate PostgreSQL instance in a production environment.
9
9
  - `pg_eventstore` requires `default_transaction_isolation` server config option to be set to `'read committed'` (default behavior). Having this value set to move strict isolation level may result in unexpected behavior.
10
10
  - It is recommended to use a connection pooler (for example [PgBouncer](https://www.pgbouncer.org/)) in `transaction` pool mode to lower the load on a database.
11
- - `pg_eventstore` requires ruby v3+. The development of this gem is targeted at [current](https://endoflife.date/ruby) ruby versions.
11
+ - `pg_eventstore` requires ruby v3.3+. The development of this gem is targeted at [current](https://endoflife.date/ruby) ruby versions.
12
+
13
+ ### Migrating to v3
14
+
15
+ v3 introduces several new tables to support the functional. Make sure you have enough disk space on your PostgreSQL host.
16
+
17
+ If you are migrating from v2 - please don't forget to delete cron jobs and `pg_cron` extension after migration to v3.
18
+ You can remove cron jobs as follows:
19
+
20
+ ```sql
21
+ SELECT cron.unschedule('prune_eventstore_events_horizon');
22
+ SELECT cron.unschedule('delete-job-run-details');
23
+ ```
12
24
 
13
25
  ## Installation
14
26
 
@@ -47,26 +59,22 @@ Documentation chapters:
47
59
  - [Appending events](docs/appending_events.md)
48
60
  - [Linking events](docs/linking_events.md)
49
61
  - [Reading events](docs/reading_events.md)
62
+ - [Reading streams](docs/reading_streams.md)
63
+ - [Tracing events](docs/event_tracing.md)
50
64
  - [Subscriptions](docs/subscriptions.md)
51
65
  - [Maintenance functions](docs/maintenance.md)
52
66
  - [Writing middlewares](docs/writing_middleware.md)
53
67
  - [How to make multiple commands atomic](docs/multiple_commands.md)
54
68
  - [Admin UI](docs/admin_ui.md)
69
+ - [Replication](docs/replication.md)
70
+
71
+ To help your AI assistant to better recognize the capabilities of this gem - there is a crafted [instructions](docs/AGENTS.fragment.md)
72
+ you can include into your `AGENTS.md`.
55
73
 
56
74
  ## CLI
57
75
 
58
76
  The gem is shipped with its own CLI. Use `pg-eventstore --help` to find out its capabilities.
59
77
 
60
- ## Maintenance
61
-
62
- You may want to backup your eventstore database. It is important to mention that you don't want to dump/restore records of `events_horizon` table. `events_horizon` table is used to supply subscriptions functionality and contains temporary data which is scoped to the PostgreSQL cluster they were created in. **Thus, it is even may be harmful if you restore records from this table into a new PostgreSQL cluster. Simply exclude that table's data when performing backups.** Example:
63
-
64
- ```bash
65
- pg_dump --exclude-table-data=events_horizon eventstore -U postgres > eventstore.sql
66
- ```
67
-
68
- Also, it is important you create and migrate new database via provided rake commands - they include an important setup of `pg_cron` jobs as well. **Even if you would like to restore your db backup on clean PostgreSQL instance - please initialize pg_eventstore via built-in tools first.**
69
-
70
78
  ## RSpec
71
79
 
72
80
  ### Clean up test db
@@ -117,14 +125,18 @@ end
117
125
  After checking out the repo, run:
118
126
  - `bundle` to install dependencies
119
127
  - `docker compose up` to start dev/test services
128
+ - `bundle exec rake compile` to compile native extension
120
129
  - `bin/setup_db` to create/re-create development and test databases, tables and related objects
121
130
  - `bundle exec rbs collection install` to install external rbs definitions
122
131
 
123
- Then, run `bin/rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
132
+ Then, run `bin/rspec` to run the tests(or just `rspec` if you want to temporary skip rbs checks). Optional env variables for testing:
133
+ - `DEBUG=1 bin/rspec`. Enable nicely formatted and highlighted SQL output into stdout.
134
+
135
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
124
136
 
125
137
  To install this gem onto your local machine, run `bundle exec rake install`.
126
138
 
127
- To run admin UI web server - run `puma` in your terminal. By default it will start web server on `http://0.0.0.0:9292`.
139
+ To run admin UI web server - run `puma` in your terminal. By default, it will start web server on `http://0.0.0.0:9292`.
128
140
 
129
141
  ### Benchmarks
130
142
 
@@ -1,23 +1 @@
1
- # frozen_string_literal: true
2
-
3
- PgEventstore.connection(:_postgres_db_connection).with do |conn|
4
- conn.exec(<<~SQL)
5
- CREATE EXTENSION IF NOT EXISTS pg_cron
6
- SQL
7
- conn.exec_params(<<~SQL, ["prune_#{PgEventstore::MigrationHelpers.db_name}_events_horizon", PgEventstore::MigrationHelpers.db_name])
8
- SELECT cron.schedule_in_database(
9
- $1,
10
- '*/10 * * * *',
11
- $$DELETE FROM events_horizon WHERE xact_id <= (SELECT xact_id FROM events_horizon ORDER BY xact_id DESC OFFSET 100 LIMIT 1)$$,
12
- $2
13
- )
14
- SQL
15
- # Store information about finished cron jobs for 1 day
16
- conn.exec(<<~SQL)
17
- SELECT cron.schedule(
18
- 'delete-job-run-details',
19
- '0 12 * * *',
20
- $$DELETE FROM cron.job_run_details WHERE end_time < now() - interval '1 day'$$
21
- );
22
- SQL
23
- end
1
+ # pg_cron was removed. This file is kept for informative purpose.
@@ -0,0 +1,17 @@
1
+ CREATE TABLE public.streams_global_index
2
+ (
3
+ id bigserial NOT NULL,
4
+ partition_id bigint NOT NULL,
5
+ stream_id character varying COLLATE "POSIX" NOT NULL,
6
+ stream_revision bigint NOT NULL,
7
+ starting_position bigint NOT NULL
8
+ );
9
+
10
+ ALTER TABLE ONLY public.streams_global_index
11
+ ADD CONSTRAINT streams_global_index_pkey PRIMARY KEY (id);
12
+
13
+ CREATE UNIQUE INDEX idx_streams_global_index_on_stream_id_and_partition_id ON public.streams_global_index
14
+ USING btree (stream_id, partition_id) INCLUDE (id);
15
+
16
+ CREATE INDEX idx_streams_global_index_on_starting_position ON public.streams_global_index
17
+ USING btree (starting_position);
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ CONCURRENCY = ENV['CONCURRENCY']&.to_i || 10
4
+
5
+ PgEventstore.configure(name: :_eventstore_db_connection) do |config|
6
+ config.connection_pool_size = CONCURRENCY * 10
7
+ end
8
+
9
+ partitions = PgEventstore.connection(:_eventstore_db_connection).with do |conn|
10
+ conn.exec('select * from partitions where event_type is null')
11
+ end.to_a
12
+
13
+ stream_name_partitions = partitions.reject { _1['stream_name'].nil? }.group_by { _1['context'] }
14
+ stream_name_partitions = stream_name_partitions.to_h do |context, context_parts|
15
+ context_parts = context_parts.to_h { |p| [p['stream_name'], p['id']] }
16
+ [context, context_parts]
17
+ end
18
+
19
+ total_streams = PgEventstore.connection(:_eventstore_db_connection).with do |conn|
20
+ conn.exec('explain select * from "$streams"')
21
+ end.first['QUERY PLAN'][/rows=\d+/].sub('rows=', '').to_i
22
+
23
+ tables = PgEventstore.connection(:_eventstore_db_connection).with do |conn|
24
+ conn.exec('select table_name from partitions where event_type is not null')
25
+ end.to_a.map { _1['table_name'] }
26
+
27
+ PgEventstore.connection(:_eventstore_db_connection).with do |conn|
28
+ conn.exec('delete from streams_global_index')
29
+ end
30
+
31
+ puts "Indexing streams. Approximate number of streams to process: #{total_streams}. Concurrency is #{CONCURRENCY} concurrent writers."
32
+ puts <<~TEXT
33
+ This migration consumes CONCURRENCY x10 connections. Make sure you have plenty of them. Migrations are idempotent, \
34
+ so you can break, adjust settings if needed and then start over.
35
+ TEXT
36
+ puts
37
+
38
+ processed = 0
39
+ processed_was = 0
40
+ time = Time.now
41
+ lock = Thread::Mutex.new
42
+ sub_partitions_count_cache = {}
43
+ threads = CONCURRENCY.times.map do |t|
44
+ Thread.new do
45
+ tables.each do |table|
46
+ global_position = 0
47
+ loop do
48
+ events = PgEventstore.connection(:_eventstore_db_connection).with do |conn|
49
+ conn.exec_params(<<~SQL, [global_position, CONCURRENCY, t])
50
+ select context, stream_name, stream_id, global_position
51
+ from "#{table}"
52
+ where global_position > $1 and global_position % $2 = $3 and stream_revision = 0
53
+ group by context, stream_name, stream_id, global_position
54
+ order by global_position asc
55
+ limit 100
56
+ SQL
57
+ end.to_a
58
+ break if events.empty?
59
+
60
+ global_position = events.last['global_position']
61
+
62
+ # retrieve stream revision
63
+ events.each_slice(10) do |sliced_events|
64
+ query_runner = PgEventstore::AsyncRunner.new
65
+ sliced_events.each do |event|
66
+ query_runner.async do
67
+ query_strategy = PgEventstore::QueryStrategy::Async.new(PgEventstore.connection(:_eventstore_db_connection))
68
+ context = event['context']
69
+ stream_name = event['stream_name']
70
+ stream_id = event['stream_id']
71
+ sub_partitions_count_cache[[context, stream_name]] ||= query_strategy.exec_params(
72
+ 'select count(*) as c_all from partitions where context = $1 and stream_name = $2',
73
+ [context, stream_name]
74
+ ).first['c_all']
75
+ if sub_partitions_count_cache[[context, stream_name]] > 50
76
+ sub_partitions = query_strategy.exec_params(<<~SQL, [context, stream_name]).map { _1['table_name'] }
77
+ select table_name from partitions where context = $1 and stream_name = $2 and event_type is not null
78
+ SQL
79
+ max_revisions = []
80
+ sub_partitions.each_slice(50) do |table_names|
81
+ q_parts = table_names.map do |table_name|
82
+ <<~SQL
83
+ select max(stream_revision) as stream_revision
84
+ from #{table_name}
85
+ where context = $1 and stream_name = $2 and stream_id = $3
86
+ SQL
87
+ end
88
+ q_parts = q_parts.map { "(#{_1})" }.join(' union all ')
89
+ q = "select coalesce(max(stream_revision), -1) as stream_revision from (#{q_parts})"
90
+ max_revisions.push(
91
+ query_strategy.exec_params(q, [context, stream_name, stream_id]).first['stream_revision']
92
+ )
93
+ end
94
+ event['stream_revision'] = max_revisions.max
95
+ else
96
+ q = <<~SQL
97
+ select max(stream_revision) as stream_revision
98
+ from events
99
+ where context = $1 and stream_name = $2 and stream_id = $3
100
+ SQL
101
+ attrs = query_strategy.exec_params(q, [context, stream_name, stream_id]).to_a.first
102
+ event['stream_revision'] = attrs['stream_revision']
103
+ end
104
+ end
105
+ end
106
+ query_runner.run
107
+ end
108
+
109
+ values = events.map do |event|
110
+ partition_id = stream_name_partitions[event['context']][event['stream_name']]
111
+ stream_id = PG::Connection.escape(event['stream_id'])
112
+ stream_revision = event['stream_revision']
113
+ starting_position = event['global_position']
114
+ "(#{partition_id}, '#{stream_id}', #{stream_revision}, #{starting_position})"
115
+ end.join(',')
116
+
117
+ PgEventstore.connection(:_eventstore_db_connection).with do |conn|
118
+ conn.exec(<<~SQL)
119
+ INSERT INTO streams_global_index
120
+ ("partition_id", "stream_id", "stream_revision", "starting_position")
121
+ VALUES #{values}
122
+ SQL
123
+ end
124
+
125
+ lock.synchronize do
126
+ processed += events.size
127
+ next if Time.now - time < 2
128
+
129
+ time_was = time
130
+ time = Time.now
131
+
132
+ performance_info = <<~TEXT.strip
133
+ Processed: #{processed}. Left: #{total_streams - processed} (approximately). \
134
+ Performance: #{((processed - processed_was) / (time - time_was)).round(2)} streams/second.
135
+ TEXT
136
+ processed_was = processed
137
+ print "#{performance_info} \r"
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
143
+ threads.each(&:join)
144
+
145
+ PgEventstore.connection(:_eventstore_db_connection).with do |conn|
146
+ puts 'Running cluster on streams_global_index. This may take some time.'
147
+ conn.exec('CLUSTER streams_global_index USING idx_streams_global_index_on_starting_position')
148
+ puts 'Running vacuum on streams_global_index. This may take some time.'
149
+ conn.exec('VACUUM (ANALYZE) streams_global_index')
150
+ end