pg_eventstore 1.13.4 → 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 (319) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +11 -2
  3. data/CHANGELOG.md +195 -16
  4. data/README.md +33 -10
  5. data/db/migrations/10_setup_pg_cron.rb +1 -0
  6. data/db/migrations/11_add_events_link_global_position.sql +1 -0
  7. data/db/migrations/12_migrate_legacy_links.rb +83 -0
  8. data/db/migrations/13_remove_events_link_id.sql +6 -0
  9. data/db/migrations/14_remove_ids_events_id_index.sql +1 -0
  10. data/db/migrations/15_create_streams_global_index.sql +17 -0
  11. data/db/migrations/16_populate_streams_global_index.rb +150 -0
  12. data/db/migrations/17_create_events_global_index.sql +62 -0
  13. data/db/migrations/18_populate_events_global_index.rb +124 -0
  14. data/db/migrations/19_populate_events_global_index_p2.rb +25 -0
  15. data/db/migrations/20_migrate_subscriptions_current_position.sql +19 -0
  16. data/db/migrations/21_create_partitions_stats.sql +2 -0
  17. data/db/migrations/22_drop_streams_sql_view.sql +2 -0
  18. data/db/migrations/23_adjust_partitions_indexes.sql +5 -0
  19. data/db/migrations/24_drop_legacy_events_indexes.sql +3 -0
  20. data/db/migrations/25_add_parent_partitions_info_to_partitions.sql +16 -0
  21. data/db/migrations/26_change_events_stream_revision_type.rb +35 -0
  22. data/db/migrations/27_drop_events_horizon.sql +3 -0
  23. data/db/migrations/28_create_maintenance_tasks.sql +8 -0
  24. data/db/migrations/29_create_event_markers.sql +35 -0
  25. data/db/migrations/30_improve_partitions_search_in_admin_ui.rb +24 -0
  26. data/db/migrations/31_drop_events_id_default_value.sql +1 -0
  27. data/db/migrations/32_create_parittions_triggers.sql +101 -0
  28. data/db/migrations/9_create_events_horizon.sql +21 -0
  29. data/docs/AGENTS.fragment.md +696 -0
  30. data/docs/admin_ui.md +3 -1
  31. data/docs/appending_events.md +110 -49
  32. data/docs/configuration.md +69 -36
  33. data/docs/event_tracing.md +55 -0
  34. data/docs/events_and_streams.md +54 -30
  35. data/docs/how_it_works.md +57 -16
  36. data/docs/linking_events.md +2 -1
  37. data/docs/maintenance.md +14 -5
  38. data/docs/multiple_commands.md +30 -6
  39. data/docs/reading_events.md +208 -43
  40. data/docs/reading_streams.md +50 -0
  41. data/docs/replication.md +99 -0
  42. data/docs/subscriptions.md +165 -35
  43. data/docs/writing_middleware.md +25 -8
  44. data/ext/pg_eventstore_ext/extconf.rb +5 -0
  45. data/ext/pg_eventstore_ext/pg_eventstore_ext.c +74 -0
  46. data/lib/pg_eventstore/async_runner.rb +63 -0
  47. data/lib/pg_eventstore/basic_config.rb +15 -0
  48. data/lib/pg_eventstore/callbacks.rb +7 -5
  49. data/lib/pg_eventstore/chunks/chunk.rb +31 -0
  50. data/lib/pg_eventstore/chunks/read_api_events_index_chunk.rb +83 -0
  51. data/lib/pg_eventstore/chunks/replica_events_index_chunk.rb +35 -0
  52. data/lib/pg_eventstore/chunks/repository.rb +73 -0
  53. data/lib/pg_eventstore/chunks/subscription_checkpoint_chunk.rb +48 -0
  54. data/lib/pg_eventstore/chunks/subscription_events_index_chunk.rb +99 -0
  55. data/lib/pg_eventstore/chunks.rb +14 -0
  56. data/lib/pg_eventstore/cli/try_to_delete_subscriptions_set.rb +2 -2
  57. data/lib/pg_eventstore/client.rb +167 -52
  58. data/lib/pg_eventstore/commands/append.rb +62 -43
  59. data/lib/pg_eventstore/commands/delete_event.rb +7 -8
  60. data/lib/pg_eventstore/commands/delete_stream.rb +3 -3
  61. data/lib/pg_eventstore/commands/event_modifiers/prepare_link_event.rb +11 -6
  62. data/lib/pg_eventstore/commands/event_modifiers/prepare_regular_event.rb +12 -7
  63. data/lib/pg_eventstore/commands/link_to.rb +9 -9
  64. data/lib/pg_eventstore/commands/multiple.rb +2 -2
  65. data/lib/pg_eventstore/commands/read.rb +23 -5
  66. data/lib/pg_eventstore/commands/read_grouped.rb +46 -0
  67. data/lib/pg_eventstore/commands/read_streams.rb +18 -0
  68. data/lib/pg_eventstore/commands/read_streams_paginated.rb +56 -0
  69. data/lib/pg_eventstore/commands/regular_stream_read_paginated.rb +4 -4
  70. data/lib/pg_eventstore/commands/revision_check/current_revision.rb +51 -0
  71. data/lib/pg_eventstore/commands/revision_check/event_type_revisions_comparison.rb +46 -0
  72. data/lib/pg_eventstore/commands/revision_check/expected_revision.rb +135 -0
  73. data/lib/pg_eventstore/commands/revision_check/stream_revision_check.rb +98 -0
  74. data/lib/pg_eventstore/commands/revision_check/stream_revision_comparison.rb +36 -0
  75. data/lib/pg_eventstore/commands/stream_revision.rb +14 -0
  76. data/lib/pg_eventstore/commands/system_stream_read_paginated.rb +4 -4
  77. data/lib/pg_eventstore/commands.rb +4 -2
  78. data/lib/pg_eventstore/config.rb +30 -9
  79. data/lib/pg_eventstore/connection.rb +22 -37
  80. data/lib/pg_eventstore/errors.rb +137 -11
  81. data/lib/pg_eventstore/event.rb +39 -7
  82. data/lib/pg_eventstore/event_deserializer.rb +5 -4
  83. data/lib/pg_eventstore/event_global_index.rb +118 -0
  84. data/lib/pg_eventstore/event_marker.rb +16 -0
  85. data/lib/pg_eventstore/event_marker_index.rb +25 -0
  86. data/lib/pg_eventstore/event_serializer.rb +6 -0
  87. data/lib/pg_eventstore/extensions/acts_as_configurable.rb +88 -0
  88. data/lib/pg_eventstore/extensions/options_defaults.rb +25 -0
  89. data/lib/pg_eventstore/extensions/options_extension.rb +42 -13
  90. data/lib/pg_eventstore/feature_marker.rb +18 -0
  91. data/lib/pg_eventstore/maintenance.rb +11 -6
  92. data/lib/pg_eventstore/middleware/event_tracing.rb +53 -0
  93. data/lib/pg_eventstore/partition.rb +6 -0
  94. data/lib/pg_eventstore/pg_connection.rb +70 -14
  95. data/lib/pg_eventstore/queries/event_marker_queries.rb +74 -0
  96. data/lib/pg_eventstore/queries/event_queries.rb +9 -95
  97. data/lib/pg_eventstore/queries/events_global_index_queries.rb +131 -0
  98. data/lib/pg_eventstore/queries/index_filtering_queries.rb +179 -0
  99. data/lib/pg_eventstore/queries/links_resolver.rb +10 -7
  100. data/lib/pg_eventstore/queries/maintenance_queries.rb +159 -42
  101. data/lib/pg_eventstore/queries/partition_queries.rb +46 -104
  102. data/lib/pg_eventstore/queries/replica_queries.rb +110 -0
  103. data/lib/pg_eventstore/queries/streams_global_index_queries.rb +163 -0
  104. data/lib/pg_eventstore/queries/transaction_queries.rb +58 -10
  105. data/lib/pg_eventstore/queries.rb +29 -1
  106. data/lib/pg_eventstore/query_builders/basic_filtering.rb +38 -5
  107. data/lib/pg_eventstore/query_builders/event_markers_filtering.rb +47 -0
  108. data/lib/pg_eventstore/query_builders/event_markers_index_filtering.rb +275 -0
  109. data/lib/pg_eventstore/query_builders/event_subscription_positions_filtering.rb +37 -0
  110. data/lib/pg_eventstore/query_builders/events_filtering.rb +15 -164
  111. data/lib/pg_eventstore/query_builders/events_global_index_filtering.rb +227 -0
  112. data/lib/pg_eventstore/query_builders/filters/collection.rb +324 -0
  113. data/lib/pg_eventstore/query_builders/filters/event_type_filter.rb +37 -0
  114. data/lib/pg_eventstore/query_builders/filters/filter_row.rb +54 -0
  115. data/lib/pg_eventstore/query_builders/filters/marker_filter.rb +20 -0
  116. data/lib/pg_eventstore/query_builders/filters/marker_filter_row.rb +49 -0
  117. data/lib/pg_eventstore/query_builders/filters/stream_filter.rb +48 -0
  118. data/lib/pg_eventstore/query_builders/index_based_events_filtering.rb +260 -0
  119. data/lib/pg_eventstore/query_builders/partitions_filtering.rb +175 -51
  120. data/lib/pg_eventstore/query_builders/read_cursor/stream_cursor.rb +122 -0
  121. data/lib/pg_eventstore/query_builders/streams_global_index_filtering.rb +83 -0
  122. data/lib/pg_eventstore/query_builders/subscription_events_filtering.rb +79 -0
  123. data/lib/pg_eventstore/query_strategy/async.rb +64 -0
  124. data/lib/pg_eventstore/query_strategy/foreground.rb +25 -0
  125. data/lib/pg_eventstore/query_strategy.rb +19 -0
  126. data/lib/pg_eventstore/raw_event.rb +46 -0
  127. data/lib/pg_eventstore/rspec/test_helpers.rb +21 -8
  128. data/lib/pg_eventstore/sql_builder.rb +87 -8
  129. data/lib/pg_eventstore/stream.rb +14 -17
  130. data/lib/pg_eventstore/stream_global_index.rb +28 -0
  131. data/lib/pg_eventstore/subscriptions/basic_runner.rb +4 -4
  132. data/lib/pg_eventstore/subscriptions/callback_handlers/events_processor_handlers.rb +5 -17
  133. data/lib/pg_eventstore/subscriptions/callback_handlers/events_subscription_position_worker_handlers.rb +36 -0
  134. data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_feeder_handlers.rb +13 -1
  135. data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rb +19 -14
  136. data/lib/pg_eventstore/subscriptions/events_processor.rb +21 -19
  137. data/lib/pg_eventstore/subscriptions/events_processor_consumer/multiple.rb +63 -0
  138. data/lib/pg_eventstore/subscriptions/events_processor_consumer/replica.rb +68 -0
  139. data/lib/pg_eventstore/subscriptions/events_processor_consumer/single.rb +62 -0
  140. data/lib/pg_eventstore/subscriptions/events_processor_consumer.rb +44 -0
  141. data/lib/pg_eventstore/subscriptions/events_subscription_position_worker.rb +68 -0
  142. data/lib/pg_eventstore/subscriptions/queries/event_subscription_position_queries.rb +177 -0
  143. data/lib/pg_eventstore/subscriptions/queries/subscription_command_queries.rb +5 -5
  144. data/lib/pg_eventstore/subscriptions/queries/subscription_queries.rb +80 -56
  145. data/lib/pg_eventstore/subscriptions/queries/subscriptions_set_command_queries.rb +2 -2
  146. data/lib/pg_eventstore/subscriptions/queries/subscriptions_set_queries.rb +1 -1
  147. data/lib/pg_eventstore/subscriptions/replica_subscription_handler.rb +140 -0
  148. data/lib/pg_eventstore/subscriptions/replica_subscription_runner.rb +9 -0
  149. data/lib/pg_eventstore/subscriptions/subscription.rb +9 -7
  150. data/lib/pg_eventstore/subscriptions/subscription_feed_strategies/collection.rb +36 -0
  151. data/lib/pg_eventstore/subscriptions/subscription_feed_strategies/index_read_strategy.rb +82 -0
  152. data/lib/pg_eventstore/subscriptions/subscription_feed_strategies/replication_strategy.rb +75 -0
  153. data/lib/pg_eventstore/subscriptions/subscription_feed_strategy.rb +31 -0
  154. data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +26 -2
  155. data/lib/pg_eventstore/subscriptions/subscription_handler_performance.rb +5 -5
  156. data/lib/pg_eventstore/subscriptions/subscription_runner.rb +20 -22
  157. data/lib/pg_eventstore/subscriptions/subscription_runner_commands/reset_position.rb +1 -1
  158. data/lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb +10 -11
  159. data/lib/pg_eventstore/subscriptions/subscriptions_lifecycle.rb +0 -12
  160. data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +84 -29
  161. data/lib/pg_eventstore/{subscriptions/synchronized_array.rb → synchronized_array.rb} +15 -0
  162. data/lib/pg_eventstore/tasks/setup.rake +32 -33
  163. data/lib/pg_eventstore/utils.rb +39 -0
  164. data/lib/pg_eventstore/version.rb +1 -1
  165. data/lib/pg_eventstore/web/application.rb +104 -20
  166. data/lib/pg_eventstore/web/paginator/base_collection.rb +5 -1
  167. data/lib/pg_eventstore/web/paginator/event_types_collection.rb +14 -4
  168. data/lib/pg_eventstore/web/paginator/events_collection.rb +29 -33
  169. data/lib/pg_eventstore/web/paginator/helpers.rb +19 -12
  170. data/lib/pg_eventstore/web/paginator/markers_collection.rb +48 -0
  171. data/lib/pg_eventstore/web/paginator/stream_contexts_collection.rb +13 -2
  172. data/lib/pg_eventstore/web/paginator/stream_ids_collection.rb +21 -11
  173. data/lib/pg_eventstore/web/paginator/stream_names_collection.rb +18 -4
  174. data/lib/pg_eventstore/web/paginator/streams_collection.rb +95 -0
  175. data/lib/pg_eventstore/web/public/javascripts/pg_eventstore.js +49 -22
  176. data/lib/pg_eventstore/web/subscriptions/helpers.rb +2 -2
  177. data/lib/pg_eventstore/web/subscriptions/set_collection.rb +1 -1
  178. data/lib/pg_eventstore/web/subscriptions/subscriptions.rb +1 -1
  179. data/lib/pg_eventstore/web/subscriptions/with_state/set_collection.rb +1 -1
  180. data/lib/pg_eventstore/web/subscriptions/with_state/subscriptions.rb +1 -1
  181. data/lib/pg_eventstore/web/views/home/dashboard.erb +11 -9
  182. data/lib/pg_eventstore/web/views/home/partials/event_filter.erb +18 -5
  183. data/lib/pg_eventstore/web/views/home/partials/events.erb +38 -1
  184. data/lib/pg_eventstore/web/views/home/partials/markers_filter.erb +18 -0
  185. data/lib/pg_eventstore/web/views/layouts/application.erb +6 -0
  186. data/lib/pg_eventstore/web/views/streams/index.erb +110 -0
  187. data/lib/pg_eventstore/web/views/streams/partials/streams.erb +11 -0
  188. data/lib/pg_eventstore/web/views/subscriptions/index.erb +4 -1
  189. data/lib/pg_eventstore/web.rb +2 -0
  190. data/lib/pg_eventstore.rb +26 -63
  191. data/pg_eventstore.gemspec +6 -4
  192. data/sig/interfaces/_raw_events_handler.rbs +3 -0
  193. data/sig/interfaces/subscription_handler.rbs +1 -1
  194. data/sig/pg/result.rbs +9 -0
  195. data/sig/pg_eventstore/async_runner.rbs +13 -0
  196. data/sig/pg_eventstore/basic_config.rbs +9 -0
  197. data/sig/pg_eventstore/chunks/chunk.rbs +13 -0
  198. data/sig/pg_eventstore/chunks/read_api_events_index_chunk.rbs +30 -0
  199. data/sig/pg_eventstore/chunks/replica_events_index_chunk.rbs +11 -0
  200. data/sig/pg_eventstore/chunks/repository.rbs +22 -0
  201. data/sig/pg_eventstore/chunks/subscription_checkpoint_chunk.rbs +18 -0
  202. data/sig/pg_eventstore/chunks/subscription_events_index_chunk.rbs +37 -0
  203. data/sig/pg_eventstore/client.rbs +41 -43
  204. data/sig/pg_eventstore/commands/append.rbs +11 -25
  205. data/sig/pg_eventstore/commands/event_modifiers/prepare_link_event.rbs +9 -12
  206. data/sig/pg_eventstore/commands/event_modifiers/prepare_regular_event.rbs +5 -4
  207. data/sig/pg_eventstore/commands/link_to.rbs +6 -11
  208. data/sig/pg_eventstore/commands/multiple.rbs +1 -1
  209. data/sig/pg_eventstore/commands/read.rbs +2 -5
  210. data/sig/pg_eventstore/commands/read_grouped.rbs +7 -0
  211. data/sig/pg_eventstore/commands/read_streams.rbs +7 -0
  212. data/sig/pg_eventstore/commands/read_streams_paginated.rbs +16 -0
  213. data/sig/pg_eventstore/commands/regular_stream_read_paginated.rbs +9 -17
  214. data/sig/pg_eventstore/commands/revision_check/current_revision.rbs +25 -0
  215. data/sig/pg_eventstore/commands/revision_check/event_type_revisions_comparison.rbs +14 -0
  216. data/sig/pg_eventstore/commands/revision_check/expected_revision.rbs +67 -0
  217. data/sig/pg_eventstore/commands/revision_check/stream_revision_check.rbs +20 -0
  218. data/sig/pg_eventstore/commands/revision_check/stream_revision_comparison.rbs +9 -0
  219. data/sig/pg_eventstore/commands/stream_revision.rbs +7 -0
  220. data/sig/pg_eventstore/commands/system_stream_read_paginated.rbs +9 -14
  221. data/sig/pg_eventstore/config.rbs +15 -6
  222. data/sig/pg_eventstore/connection.rbs +11 -18
  223. data/sig/pg_eventstore/errors.rbs +64 -34
  224. data/sig/pg_eventstore/event.rbs +20 -6
  225. data/sig/pg_eventstore/event_deserializer.rbs +5 -10
  226. data/sig/pg_eventstore/event_global_index.rbs +60 -0
  227. data/sig/pg_eventstore/event_marker.rbs +9 -0
  228. data/sig/pg_eventstore/event_marker_index.rbs +12 -0
  229. data/sig/pg_eventstore/extensions/acts_as_configurable.rbs +29 -0
  230. data/sig/pg_eventstore/extensions/options_defaults.rbs +7 -0
  231. data/sig/pg_eventstore/extensions/options_extension.rbs +9 -1
  232. data/sig/pg_eventstore/feature_marker.rbs +10 -0
  233. data/sig/pg_eventstore/maintenance.rbs +12 -8
  234. data/sig/pg_eventstore/middleware/event_trace.rbs +14 -0
  235. data/sig/pg_eventstore/partition.rbs +2 -4
  236. data/sig/pg_eventstore/pg_connection.rbs +8 -0
  237. data/sig/pg_eventstore/queries/event_marker_queries.rbs +21 -0
  238. data/sig/pg_eventstore/queries/event_queries.rbs +6 -38
  239. data/sig/pg_eventstore/queries/events_global_index_queries.rbs +37 -0
  240. data/sig/pg_eventstore/queries/index_filtering_queries.rbs +50 -0
  241. data/sig/pg_eventstore/queries/links_resolver.rbs +7 -11
  242. data/sig/pg_eventstore/queries/maintenance_queries.rbs +12 -9
  243. data/sig/pg_eventstore/queries/partition_queries.rbs +32 -67
  244. data/sig/pg_eventstore/queries/replica_queries.rbs +31 -0
  245. data/sig/pg_eventstore/queries/streams_global_index_queries.rbs +39 -0
  246. data/sig/pg_eventstore/queries/transaction_queries.rbs +4 -2
  247. data/sig/pg_eventstore/queries.rbs +11 -21
  248. data/sig/pg_eventstore/query_builders/basic_filtering.rbs +10 -3
  249. data/sig/pg_eventstore/query_builders/event_markers_filtering.rbs +17 -0
  250. data/sig/pg_eventstore/query_builders/event_markers_index_filtering.rbs +69 -0
  251. data/sig/pg_eventstore/query_builders/event_subscription_positions_filtering.rbs +15 -0
  252. data/sig/pg_eventstore/query_builders/events_filtering_query.rbs +9 -51
  253. data/sig/pg_eventstore/query_builders/events_global_index_filtering.rbs +68 -0
  254. data/sig/pg_eventstore/query_builders/filters/collection.rbs +84 -0
  255. data/sig/pg_eventstore/query_builders/filters/event_type_filter.rbs +19 -0
  256. data/sig/pg_eventstore/query_builders/filters/filter_row.rbs +21 -0
  257. data/sig/pg_eventstore/query_builders/filters/marker_filter.rbs +13 -0
  258. data/sig/pg_eventstore/query_builders/filters/marker_filter_row.rbs +19 -0
  259. data/sig/pg_eventstore/query_builders/filters/stream_filter.rbs +24 -0
  260. data/sig/pg_eventstore/query_builders/index_based_events_filtering.rbs +60 -0
  261. data/sig/pg_eventstore/query_builders/partitions_filtering.rbs +29 -10
  262. data/sig/pg_eventstore/query_builders/read_cursor/stream_cursor.rbs +47 -0
  263. data/sig/pg_eventstore/query_builders/streams_global_index_filtering.rbs +27 -0
  264. data/sig/pg_eventstore/query_builders/subscription_events_filtering.rbs +26 -0
  265. data/sig/pg_eventstore/query_strategy/async.rbs +17 -0
  266. data/sig/pg_eventstore/query_strategy/foreground.rbs +11 -0
  267. data/sig/pg_eventstore/query_strategy.rbs +7 -0
  268. data/sig/pg_eventstore/raw_event.rbs +20 -0
  269. data/sig/pg_eventstore/sql_builder.rbs +32 -10
  270. data/sig/pg_eventstore/stream.rbs +10 -18
  271. data/sig/pg_eventstore/stream_global_index.rbs +14 -0
  272. data/sig/pg_eventstore/subscriptions/callback_handlers/events_processor_handlers.rbs +5 -4
  273. data/sig/pg_eventstore/subscriptions/callback_handlers/events_subscription_position_worker_handlers.rbs +9 -0
  274. data/sig/pg_eventstore/subscriptions/callback_handlers/subscription_feeder_handlers.rbs +17 -12
  275. data/sig/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rbs +16 -8
  276. data/sig/pg_eventstore/subscriptions/event_subscription_position_queries.rbs +30 -0
  277. data/sig/pg_eventstore/subscriptions/events_processor.rbs +12 -5
  278. data/sig/pg_eventstore/subscriptions/events_processor_consumer/multiple.rbs +17 -0
  279. data/sig/pg_eventstore/subscriptions/events_processor_consumer/replica.rbs +17 -0
  280. data/sig/pg_eventstore/subscriptions/events_processor_consumer/single.rbs +17 -0
  281. data/sig/pg_eventstore/subscriptions/events_processor_consumer.rbs +14 -0
  282. data/sig/pg_eventstore/subscriptions/events_subscription_position_worker.rbs +28 -0
  283. data/sig/pg_eventstore/subscriptions/queries/subscription_queries.rbs +18 -50
  284. data/sig/pg_eventstore/subscriptions/replica_subscription_handler.rbs +33 -0
  285. data/sig/pg_eventstore/subscriptions/replica_subscription_runner.rbs +4 -0
  286. data/sig/pg_eventstore/subscriptions/subscription_feed_strategy/collection.rbs +14 -0
  287. data/sig/pg_eventstore/subscriptions/subscription_feed_strategy/index_read_strategy.rbs +23 -0
  288. data/sig/pg_eventstore/subscriptions/subscription_feed_strategy/replication_strategy.rbs +23 -0
  289. data/sig/pg_eventstore/subscriptions/subscription_feed_strategy.rbs +11 -0
  290. data/sig/pg_eventstore/subscriptions/subscription_feeder.rbs +13 -10
  291. data/sig/pg_eventstore/subscriptions/subscription_handler_performance.rbs +1 -1
  292. data/sig/pg_eventstore/subscriptions/subscription_runner.rbs +13 -5
  293. data/sig/pg_eventstore/subscriptions/subscription_runners_feeder.rbs +5 -5
  294. data/sig/pg_eventstore/subscriptions/subscriptions_lifecycle.rbs +4 -6
  295. data/sig/pg_eventstore/subscriptions/subscriptions_manager.rbs +29 -43
  296. data/sig/pg_eventstore/synchronized_array.rbs +4 -0
  297. data/sig/pg_eventstore/utils.rbs +18 -11
  298. data/sig/pg_eventstore/web/application.rbs +13 -3
  299. data/sig/pg_eventstore/web/paginator/base_collection.rbs +2 -0
  300. data/sig/pg_eventstore/web/paginator/event_types_collection.rbs +4 -0
  301. data/sig/pg_eventstore/web/paginator/events_collection.rbs +11 -11
  302. data/sig/pg_eventstore/web/paginator/helpers.rbs +6 -15
  303. data/sig/pg_eventstore/web/paginator/markers_collection.rbs +13 -0
  304. data/sig/pg_eventstore/web/paginator/stream_contexts_collection.rbs +4 -0
  305. data/sig/pg_eventstore/web/paginator/stream_names_collection.rbs +4 -0
  306. data/sig/pg_eventstore/web/paginator/streams_collection.rbs +30 -0
  307. data/sig/pg_eventstore.rbs +8 -26
  308. metadata +181 -21
  309. data/lib/pg_eventstore/commands/all_stream_read_grouped.rb +0 -69
  310. data/lib/pg_eventstore/commands/regular_stream_read_grouped.rb +0 -31
  311. data/lib/pg_eventstore/subscriptions/queries/service_queries.rb +0 -73
  312. data/lib/pg_eventstore/subscriptions/subscription_position_evaluation.rb +0 -195
  313. data/lib/pg_eventstore/web/views/home/partials/system_stream_filter.erb +0 -15
  314. data/sig/pg_eventstore/commands/all_stream_read_grouped.rbs +0 -16
  315. data/sig/pg_eventstore/commands/regular_stream_read_grouped.rbs +0 -8
  316. data/sig/pg_eventstore/subscriptions/queries/service_queries.rbs +0 -15
  317. data/sig/pg_eventstore/subscriptions/subscription_position_evaluation.rbs +0 -53
  318. data/sig/pg_eventstore/subscriptions/synchronized_array.rbs +0 -4
  319. /data/sig/interfaces/{_raw_event_handler.rbs → raw_event_handler.rbs} +0 -0
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ # @!visibility private
5
+ class EventsGlobalIndexQueries
6
+ # @!attribute connection
7
+ # @return [PgEventstore::Connection]
8
+ attr_reader :connection
9
+ private :connection
10
+
11
+ # @param connection [PgEventstore::Connection]
12
+ # @param query_strategy [PgEventstore::QueryStrategy]
13
+ def initialize(connection, query_strategy)
14
+ @connection = connection
15
+ @query_strategy = query_strategy
16
+ end
17
+
18
+ # @param raw_events [Array<Hash>]
19
+ # @param affected_partitions [Array<PgEventstore::Partition>]
20
+ # @param stream_index_id [Integer]
21
+ # @return [Array<PgEventstore::EventGlobalIndex::WriteApiRepr>]
22
+ def index_events(raw_events, affected_partitions, stream_index_id)
23
+ partitions = affected_partitions.to_h { [_1.event_type, _1] }
24
+ values = raw_events.map do |event_attrs|
25
+ values = [
26
+ event_attrs['global_position'],
27
+ event_attrs['stream_revision'],
28
+ partitions[event_attrs['type']].parent_context_partition_id,
29
+ partitions[event_attrs['type']].parent_stream_name_partition_id,
30
+ partitions[event_attrs['type']].id,
31
+ stream_index_id,
32
+ ]
33
+ "(#{values.join(', ')})"
34
+ end
35
+ values = values.join(',')
36
+
37
+ result = @query_strategy.exec(<<~SQL)
38
+ INSERT INTO events_global_index
39
+ ("global_position", "stream_revision", "context_partition_id", "stream_name_partition_id",
40
+ "event_type_partition_id", "streams_global_index_id")
41
+ VALUES #{values}
42
+ RETURNING global_position, stream_revision, event_type_partition_id
43
+ SQL
44
+ # "returning" statement has no guarantees about the order in which rows are returned. Thus, sort them explicitly
45
+ deserialize(result, repr: EventGlobalIndex::ReprType::WRITE_API).sort_by(&:stream_revision)
46
+ end
47
+
48
+ # @param grouped_opts [Hash]
49
+ # @return [Hash<Integer, Array<PgEventstore::SubscriptionRepr>>]
50
+ def fetch_indexes_for_subscriptions(grouped_opts)
51
+ builders = grouped_opts.map do |subscription_id, opts|
52
+ filter = QueryBuilders::SubscriptionEventsFiltering.build(subscription_id, opts)
53
+ filter.to_sql_builder.select("#{subscription_id} as subscription_id")
54
+ end
55
+ final_builder = SQLBuilder.union_builders(builders)
56
+ res = @query_strategy.exec_params(*final_builder.to_exec_params)
57
+ res = res.group_by { _1['subscription_id'] }
58
+ res.to_h do |sid, indexes|
59
+ [
60
+ sid,
61
+ indexes.map do |attrs|
62
+ EventGlobalIndex::SubscriptionRepr.new(**attrs.except('subscription_id').transform_keys(&:to_sym))
63
+ end,
64
+ ]
65
+ end
66
+ end
67
+
68
+ # @param indexes [Array<EventGlobalIndex::ReadApiRepr>, Array<EventGlobalIndex::SubscriptionRepr>]
69
+ # @param resolve_link_tos [Boolean]
70
+ # @param direction [String, Symbol, nil]
71
+ # @return [Array<Hash>]
72
+ def resolve_indexes(indexes, resolve_link_tos:, direction: nil)
73
+ indexes = indexes.group_by(&:event_type_partition_id)
74
+ partitions = partition_queries.find_by_ids(indexes.keys).to_h { [_1['id'], _1] }
75
+ builders = indexes.map do |partition_id, idxs|
76
+ partition = partitions[partition_id]
77
+ events_filtering = QueryBuilders::EventsFiltering.new
78
+ events_filtering.add_stream_attrs(context: partition['context'], stream_name: partition['stream_name'])
79
+ events_filtering.add_event_types([partition['event_type']])
80
+ builder = events_filtering.to_sql_builder
81
+ builder.where('global_position = ANY(?)', idxs.map(&:global_position))
82
+ end
83
+ main_builder = SQLBuilder.union_builders(builders)
84
+ with_sorted_events = SQLBuilder.new.select('*').from(main_builder)
85
+ if direction
86
+ with_sorted_events.order("global_position #{QueryBuilders::BasicFiltering::SQL_DIRECTIONS[direction]}")
87
+ end
88
+ raw_events = @query_strategy.exec_params(*with_sorted_events.to_exec_params).to_a
89
+ resolve_link_tos ? links_resolver.resolve(raw_events) : raw_events
90
+ end
91
+
92
+ # @return [Integer, nil]
93
+ def max_global_position
94
+ builder = SQLBuilder.new.from(QueryBuilders::EventsGlobalIndexFiltering::PRIMARY_TABLE_NAME)
95
+ builder.select('max(global_position) as max_global_position')
96
+ @query_strategy.exec_params(*builder.to_exec_params).first['max_global_position']
97
+ end
98
+
99
+ # Takes an array of potentially persisted events and loads their ids from db. Those ids can be later used to check
100
+ # whether events are actually existing events.
101
+ # @param events [Array<PgEventstore::Event>]
102
+ # @return [Array<Integer>]
103
+ def global_positions_from_db(events)
104
+ builder = SQLBuilder.new.from(QueryBuilders::EventsGlobalIndexFiltering::PRIMARY_TABLE_NAME)
105
+ builder.where('global_position = ANY(?::bigint[])', events.map(&:global_position))
106
+ @query_strategy.exec_params(*builder.to_exec_params).map { _1['global_position'] }
107
+ end
108
+
109
+ private
110
+
111
+ # @return [PgEventstore::PartitionQueries]
112
+ def partition_queries
113
+ PartitionQueries.new(connection)
114
+ end
115
+
116
+ # @return [PgEventstore::LinksResolver]
117
+ def links_resolver
118
+ LinksResolver.new(connection, @query_strategy)
119
+ end
120
+
121
+ # @param repr [Symbol, nil]
122
+ # @return [Array<PgEventstore::EventGlobalIndex>, Array<PgEventstore::EventGlobalIndex::SubscriptionRepr>,
123
+ # Array<PgEventstore::EventGlobalIndex::ReadApiRepr>, Array<PgEventstore::EventGlobalIndex::WriteApiRepr>,
124
+ # Array<PgEventstore::EventGlobalIndex::RevisionCheckRepr>]
125
+ def deserialize(pg_result, repr: nil)
126
+ pg_result.map do |attrs|
127
+ EventGlobalIndex.create_representation(attrs.transform_keys(&:to_sym), repr:)
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ class IndexFilteringQueries
5
+ # @!attribute connection
6
+ # @return [PgEventstore::Connection]
7
+ attr_reader :connection
8
+ private :connection
9
+
10
+ # @param connection [PgEventstore::Connection]
11
+ # @param query_strategy [PgEventstore::QueryStrategy]
12
+ def initialize(connection, query_strategy)
13
+ @connection = connection
14
+ @query_strategy = query_strategy
15
+ end
16
+
17
+ # @param stream [PgEventstore::Stream]
18
+ # @param expected_revisions [Array<PgEventstore::Commands::RevisionCheck::ExpectedRevision::EventTypeRevision,
19
+ # PgEventstore::Commands::RevisionCheck::ExpectedRevision::EventTypeRevisionWithMarkers,
20
+ # PgEventstore::Commands::RevisionCheck::ExpectedRevision::MarkersRevision>]
21
+ # @return [Array<PgEventstore::EventGlobalIndex::RevisionCheckRepr>]
22
+ def fetch_indexes_for_revision_validation(stream, expected_revisions)
23
+ builders = expected_revisions.map do |expected_revision|
24
+ case expected_revision
25
+ when Commands::RevisionCheck::ExpectedRevision::EventTypeRevision
26
+ QueryBuilders::IndexBasedEventsFiltering.sql_builder_for_event_type_revision_validation(
27
+ stream, expected_revision
28
+ )
29
+ when Commands::RevisionCheck::ExpectedRevision::EventTypeRevisionWithMarkers
30
+ QueryBuilders::IndexBasedEventsFiltering.sql_builder_for_event_type_with_markers_revision_validation(
31
+ stream, expected_revision
32
+ )
33
+ when Commands::RevisionCheck::ExpectedRevision::MarkersRevision
34
+ QueryBuilders::IndexBasedEventsFiltering.sql_builder_for_markers_revision_validation(
35
+ stream, expected_revision
36
+ )
37
+ else
38
+ Utils.missing_implementation!(expected_revision)
39
+ end
40
+ end
41
+ final = SQLBuilder.union_builders(builders)
42
+ deserialize(@query_strategy.exec_params(*final.to_exec_params), repr: EventGlobalIndex::ReprType::REVISION_CHECK)
43
+ end
44
+
45
+ # @param filters_collection [PgEventstore::QueryBuilders::Filters::Collection]
46
+ # @param cursor [PgEventstore::QueryBuilders::ReadCursor::StreamCursor]
47
+ # @return [Array<PgEventstore::EventGlobalIndex::ReadApiRepr>]
48
+ def fetch_indexes_for_read_api(filters_collection, cursor)
49
+ filter_rows =
50
+ if filters_collection.collection.any?(&:ambiguous_event_type?)
51
+ expand_event_types(filters_collection)
52
+ else
53
+ filters_collection.collection
54
+ end
55
+
56
+ sql_builder = QueryBuilders::IndexBasedEventsFiltering.sql_builder_for_read_common(filter_rows, cursor)
57
+ deserialize(@query_strategy.exec_params(*sql_builder.to_exec_params), repr: EventGlobalIndex::ReprType::READ_API)
58
+ end
59
+
60
+ # @param filters_collection [PgEventstore::QueryBuilders::Filters::Collection]
61
+ # @param cursor [PgEventstore::QueryBuilders::ReadCursor::StreamCursor]
62
+ # @return [Array<PgEventstore::EventGlobalIndex::ReadApiRepr>]
63
+ def fetch_grouped_indexes_for_read_api(filters_collection, cursor)
64
+ filter_rows =
65
+ if filters_collection.collection.any?(&:ambiguous_event_type?)
66
+ expand_event_types(filters_collection)
67
+ else
68
+ filters_collection.collection
69
+ end
70
+
71
+ sql_builder = QueryBuilders::IndexBasedEventsFiltering.sql_builder_for_read_grouped(filter_rows, cursor)
72
+ deserialize(@query_strategy.exec_params(*sql_builder.to_exec_params), repr: EventGlobalIndex::ReprType::READ_API)
73
+ end
74
+
75
+ # @param indexes [Array<PgEventstore::EventGlobalIndex::ReadApiRepr>]
76
+ # @param resolve_link_tos [Boolean]
77
+ # @return [PgEventstore::Chunks::Repository]
78
+ def compute_read_api_chunks_repo(indexes, resolve_link_tos)
79
+ repo = Chunks::Repository.new
80
+ repo.add_chunk(Chunks::ReadApiEventsIndexChunk.new(indexes, connection, @query_strategy, resolve_link_tos))
81
+ repo
82
+ end
83
+
84
+ private
85
+
86
+ # @return [PgEventstore::PartitionQueries]
87
+ def partition_queries
88
+ PartitionQueries.new(connection)
89
+ end
90
+
91
+ # @param filters_collection [PgEventstore::QueryBuilders::Filters::Collection]
92
+ # @return [Array<PgEventstore::QueryBuilders::Filters::FilterRow>]
93
+ def expand_event_types(filters_collection)
94
+ filter_rows = filters_collection.collection
95
+ to_expand = filter_rows.select(&:ambiguous_event_type?)
96
+ rest_filter_rows = filter_rows - to_expand
97
+ partition_builders = to_expand.map do |filter_row|
98
+ partitions_filtering = QueryBuilders::PartitionsFiltering.new
99
+ partitions_filtering.with_event_types
100
+ filter_row = filter_row.to_filter_row if filter_row.is_a?(QueryBuilders::Filters::MarkerFilterRow)
101
+ partitions_filtering.add_filter_row(filter_row)
102
+ partitions_filtering.to_sql_builder.unselect.select('context, stream_name, event_type')
103
+ end
104
+ final_partition_builder = SQLBuilder.union_builders(partition_builders)
105
+ expanded_partitions_list = @query_strategy.exec_params(*final_partition_builder.to_exec_params)
106
+ expanded_partitions_list = expanded_partitions_list.group_by { _1['event_type'] }
107
+ expanded_filter_rows =
108
+ to_expand.flat_map do |filter_row|
109
+ case filter_row
110
+ when QueryBuilders::Filters::FilterRow
111
+ filter_row.event_type_filters.flat_map do |event_type_filter|
112
+ related_partitions =
113
+ if event_type_filter.prefix?
114
+ expanded_partitions_list.select do |event_type, _|
115
+ event_type.start_with?(event_type_filter.event_type)
116
+ end.values.flatten
117
+ else
118
+ expanded_partitions_list[event_type_filter.event_type]
119
+ end
120
+ next [] unless related_partitions
121
+
122
+ if filter_row.stream_filter
123
+ related_partitions = related_partitions.select do |attrs|
124
+ if filter_row.stream_filter.context?
125
+ attrs['context'] == filter_row.stream_filter.context
126
+ else
127
+ attrs['context'] == filter_row.stream_filter.context &&
128
+ attrs['stream_name'] == filter_row.stream_filter.stream_name
129
+ end
130
+ end
131
+ end
132
+ related_partitions.map do |partition_attrs|
133
+ stream_filter = QueryBuilders::Filters::StreamFilter.new(
134
+ context: partition_attrs['context'],
135
+ stream_name: partition_attrs['stream_name']
136
+ )
137
+ event_type_filter = QueryBuilders::Filters::EventTypeFilter.new(
138
+ event_type: partition_attrs['event_type'], prefix: false
139
+ )
140
+ QueryBuilders::Filters::FilterRow.new(stream_filter:, event_type_filters: [event_type_filter])
141
+ end
142
+ end
143
+ when QueryBuilders::Filters::MarkerFilterRow
144
+ related_partitions = expanded_partitions_list[filter_row.marker_filter.event_type]
145
+ next [] unless related_partitions
146
+
147
+ if filter_row.stream_filter
148
+ related_partitions = related_partitions.select do |attrs|
149
+ attrs['context'] == filter_row.stream_filter.context
150
+ end
151
+ end
152
+ related_partitions.map do |partition_attrs|
153
+ stream_filter = QueryBuilders::Filters::StreamFilter.new(
154
+ context: partition_attrs['context'],
155
+ stream_name: partition_attrs['stream_name']
156
+ )
157
+ QueryBuilders::Filters::MarkerFilterRow.new(stream_filter:, marker_filter: filter_row.marker_filter.dup)
158
+ end
159
+ else
160
+ Utils.missing_implementation!(filter_row)
161
+ end
162
+ end
163
+ adjusted_rows = expanded_filter_rows + rest_filter_rows
164
+ return [QueryBuilders::Filters::FilterRow.null_filter_row] if adjusted_rows.empty?
165
+
166
+ adjusted_rows
167
+ end
168
+
169
+ # @param repr [Symbol, nil]
170
+ # @return [Array<PgEventstore::EventGlobalIndex>, Array<PgEventstore::EventGlobalIndex::SubscriptionRepr>,
171
+ # Array<PgEventstore::EventGlobalIndex::ReadApiRepr>, Array<PgEventstore::EventGlobalIndex::WriteApiRepr>,
172
+ # Array<PgEventstore::EventGlobalIndex::RevisionCheckRepr>]
173
+ def deserialize(pg_result, repr: nil)
174
+ pg_result.map do |attrs|
175
+ EventGlobalIndex.create_representation(attrs.transform_keys(&:to_sym), repr:)
176
+ end
177
+ end
178
+ end
179
+ end
@@ -9,8 +9,10 @@ module PgEventstore
9
9
  private :connection
10
10
 
11
11
  # @param connection [PgEventstore::Connection]
12
- def initialize(connection)
12
+ # @param query_strategy [PgEventstore::QueryStrategy]
13
+ def initialize(connection, query_strategy)
13
14
  @connection = connection
15
+ @query_strategy = query_strategy
14
16
  end
15
17
 
16
18
  # Takes an array of events, look for link events in there and replaces link events with original events
@@ -20,9 +22,9 @@ module PgEventstore
20
22
  link_events = raw_events.select { _1['link_partition_id'] }.group_by { _1['link_partition_id'] }
21
23
  return raw_events if link_events.empty?
22
24
 
23
- original_events = load_original_events(link_events).to_h { |attrs| [attrs['id'], attrs] }
25
+ original_events = load_original_events(link_events).to_h { |attrs| [attrs['global_position'], attrs] }
24
26
  raw_events.map do |attrs|
25
- original_event = original_events[attrs['link_id']]
27
+ original_event = original_events[attrs['link_global_position']]
26
28
  next attrs unless original_event
27
29
 
28
30
  original_event.merge('link' => attrs).merge(attrs.except(*original_event.keys))
@@ -37,15 +39,16 @@ module PgEventstore
37
39
  partitions = partition_queries.find_by_ids(link_events.keys)
38
40
  sql_builders = partitions.map do |partition|
39
41
  sql_builder = SQLBuilder.new.select('*').from(partition['table_name'])
40
- sql_builder.where('id = ANY(?::uuid[])', link_events[partition['id']].map { _1['link_id'] })
42
+ sql_builder.where(
43
+ 'global_position = ANY(?::bigint[])',
44
+ link_events[partition['id']].map { _1['link_global_position'] }
45
+ )
41
46
  end
42
47
  sql_builder = sql_builders[1..].each_with_object(sql_builders.first) do |builder, top_builder|
43
48
  top_builder.union(builder)
44
49
  end
45
50
 
46
- connection.with do |conn|
47
- conn.exec_params(*sql_builder.to_exec_params)
48
- end.to_a
51
+ @query_strategy.exec_params(*sql_builder.to_exec_params).to_a
49
52
  end
50
53
 
51
54
  # @return [PgEventstore::PartitionQueries]
@@ -3,6 +3,11 @@
3
3
  module PgEventstore
4
4
  # @!visibility private
5
5
  class MaintenanceQueries
6
+ # @return [Integer]
7
+ EVENT_INDEXES_TO_REMOVE_PER_QUERY = 10_000
8
+ # @return [Integer]
9
+ EVENT_INDEXES_TO_UPDATE_PER_QUERY = 10_000
10
+
6
11
  # @!attribute connection
7
12
  # @return [PgEventstore::Connection]
8
13
  attr_reader :connection
@@ -13,36 +18,155 @@ module PgEventstore
13
18
  @connection = connection
14
19
  end
15
20
 
21
+ # TODO: refactor me. Reason: fatty
16
22
  # @param stream [PgEventstore::Stream]
17
23
  # @return [Integer] number of deleted events of the given stream
18
24
  def delete_stream(stream)
19
- connection.with do |conn|
20
- conn.exec_params(<<~SQL, stream.deconstruct)
21
- DELETE FROM events WHERE context = $1 AND stream_name = $2 AND stream_id = $3
22
- SQL
23
- end.cmd_tuples
25
+ total_removed = 0
26
+ transaction_queries.transaction(:read_committed) do
27
+ stream_global_idx = streams_global_index_queries.find_by!(stream)
28
+ streams_global_index_queries.delete(stream_global_idx.id)
29
+ global_position = 1
30
+ loop do
31
+ deleted_events_global_index = connection.with do |conn|
32
+ conn.exec_params(<<~SQL, [stream_global_idx.id, global_position, EVENT_INDEXES_TO_REMOVE_PER_QUERY]).to_a
33
+ delete from events_global_index
34
+ where global_position in (
35
+ select global_position from events_global_index
36
+ where streams_global_index_id = $1 and global_position >= $2
37
+ order by global_position asc
38
+ limit $3
39
+ )
40
+ returning event_type_partition_id, global_position
41
+ SQL
42
+ end
43
+ break if deleted_events_global_index.empty?
44
+
45
+ global_position = deleted_events_global_index.last['global_position'] + 1
46
+ total_removed += deleted_events_global_index.size
47
+ deleted_events_global_index = deleted_events_global_index.group_by { _1['event_type_partition_id'] }
48
+ partitions = partition_queries.find_by_ids(deleted_events_global_index.keys).to_h { [_1['id'], _1] }
49
+ queries = deleted_events_global_index.map do |partition_id, idxs|
50
+ partition = partitions[partition_id]
51
+ positions = idxs.map { _1['global_position'] }.join(', ')
52
+ context = PG::Connection.escape(partition['context'])
53
+ stream_name = PG::Connection.escape(partition['stream_name'])
54
+ event_type = PG::Connection.escape(partition['event_type'])
55
+ <<~SQL
56
+ delete from events
57
+ where context = '#{context}' and stream_name = '#{stream_name}' and type = '#{event_type}'
58
+ and global_position in (#{positions});
59
+ delete from event_subscription_positions where global_position in (#{positions});
60
+ delete from event_subscription_positions_unprocessed where global_position in (#{positions});
61
+ delete from event_markers_index where global_position in (#{positions});
62
+ SQL
63
+ end
64
+ connection.with do |conn|
65
+ conn.exec(queries.join("\n"))
66
+ end
67
+ end
68
+ end
69
+ total_removed
24
70
  end
25
71
 
72
+ # TODO: refactor me. Reason: fatty
26
73
  # @param event [PgEventstore::Event]
27
- # @return [Integer] number of deleted events
74
+ # @return [void]
28
75
  def delete_event(event)
29
- connection.with do |conn|
30
- conn.exec_params(<<~SQL, [event.stream.context, event.stream.stream_name, event.type, event.global_position])
31
- DELETE FROM events WHERE context = $1 AND stream_name = $2 AND type = $3 AND global_position = $4
32
- SQL
33
- end.cmd_tuples
34
- end
76
+ stream = event.stream
77
+ transaction_queries.transaction(:read_commited) do
78
+ stream_global_idx = streams_global_index_queries.find_by!(stream)
79
+ current_stream_revision = connection.with do |conn|
80
+ conn.exec_params(
81
+ 'select stream_revision from streams_global_index where id = $1 for update', [stream_global_idx.id]
82
+ )
83
+ end.to_a.first['stream_revision']
84
+ exists =
85
+ connection.with do |conn|
86
+ conn.exec_params(<<~SQL, [event.global_position]).first&.[]('one') == 1
87
+ select 1 as one from events_global_index where global_position = $1
88
+ SQL
89
+ end
90
+ raise RecordNotFound.new('events_global_index', event.global_position) unless exists
91
+ # We are deleting the only event. Thus, we should delete the whole stream
92
+ return delete_stream(stream) if current_stream_revision == 0
35
93
 
36
- # @param stream [PgEventstore::Stream]
37
- # @param after_revision [Integer]
38
- # @return [void]
39
- def adjust_stream_revisions(stream, after_revision)
40
- connection.with do |conn|
41
- conn.exec_params(<<~SQL, [stream.context, stream.stream_name, stream.stream_id, after_revision])
42
- UPDATE events SET stream_revision = stream_revision - 1
43
- WHERE context = $1 AND stream_name = $2
44
- AND stream_id = $3 AND stream_revision > $4
45
- SQL
94
+ deleted_event = connection.with do |conn|
95
+ conn.exec_params(<<~SQL, [stream_global_idx.id, event.global_position]).to_a.first
96
+ delete from events_global_index where streams_global_index_id = $1 and global_position = $2
97
+ returning global_position, event_type_partition_id, stream_revision
98
+ SQL
99
+ end
100
+
101
+ affected_partition = partition_queries.find_by_ids([deleted_event['event_type_partition_id']]).first
102
+ connection.with do |conn|
103
+ delete_args = [
104
+ affected_partition['context'],
105
+ affected_partition['stream_name'],
106
+ affected_partition['event_type'],
107
+ deleted_event['global_position'],
108
+ ]
109
+ conn.exec_params(<<~SQL, delete_args)
110
+ with _1 as (delete from events where context = $1 and stream_name = $2 and type = $3 and global_position = $4),
111
+ _2 as (delete from event_subscription_positions where global_position = $4),
112
+ _3 as (delete from event_subscription_positions_unprocessed where global_position = $4),
113
+ _4 as (delete from event_markers_index where global_position = $4)
114
+ select 1;
115
+ SQL
116
+ end
117
+ first_updated_event = nil
118
+ stream_revision = deleted_event['stream_revision']
119
+ loop do
120
+ updated_events = connection.with do |conn|
121
+ conn.exec_params(<<~SQL, [stream_global_idx.id, stream_revision, EVENT_INDEXES_TO_UPDATE_PER_QUERY]).to_a
122
+ update event_markers_index set stream_revision = stream_revision - 1
123
+ where global_position in (
124
+ select global_position from events_global_index
125
+ where streams_global_index_id = $1 and stream_revision > $2
126
+ order by stream_revision asc
127
+ limit $3
128
+ )
129
+ SQL
130
+ conn.exec_params(<<~SQL, [stream_global_idx.id, stream_revision, EVENT_INDEXES_TO_UPDATE_PER_QUERY]).to_a
131
+ update events_global_index set stream_revision = stream_revision - 1
132
+ where global_position in (
133
+ select global_position from events_global_index
134
+ where streams_global_index_id = $1 and stream_revision > $2
135
+ order by stream_revision asc
136
+ limit $3
137
+ )
138
+ returning event_type_partition_id, global_position, stream_revision
139
+ SQL
140
+ end
141
+ first_updated_event ||= updated_events.min_by { _1['stream_revision'] }
142
+ break if updated_events.empty?
143
+
144
+ stream_revision += updated_events.size
145
+ updated_events = updated_events.group_by { _1['event_type_partition_id'] }
146
+ partitions = partition_queries.find_by_ids(updated_events.keys).to_h { [_1['id'], _1] }
147
+ queries = updated_events.flat_map do |partition_id, idxs|
148
+ partition = partitions[partition_id]
149
+ context = PG::Connection.escape(partition['context'])
150
+ stream_name = PG::Connection.escape(partition['stream_name'])
151
+ event_type = PG::Connection.escape(partition['event_type'])
152
+ idxs.map do |idx|
153
+ <<~SQL
154
+ update events set stream_revision = #{idx['stream_revision']}
155
+ where context = '#{context}' and stream_name = '#{stream_name}' and type = '#{event_type}' and
156
+ global_position = #{idx['global_position']};
157
+ SQL
158
+ end
159
+ end
160
+ connection.with do |conn|
161
+ conn.exec(queries.join("\n"))
162
+ end
163
+ end
164
+ stream_idx_attrs_to_update = { stream_revision: current_stream_revision - 1 }
165
+ # Adjust starting_position in case zero revision event was deleted
166
+ if deleted_event['stream_revision'] == 0
167
+ stream_idx_attrs_to_update[:starting_position] = first_updated_event['global_position']
168
+ end
169
+ streams_global_index_queries.update(stream_global_idx.id, **stream_idx_attrs_to_update)
46
170
  end
47
171
  end
48
172
 
@@ -50,32 +174,25 @@ module PgEventstore
50
174
  # @param after_revision [Integer]
51
175
  # @return [Integer]
52
176
  def events_to_lock_count(stream, after_revision)
53
- connection.with do |conn|
54
- conn.exec_params(<<~SQL, [*stream.deconstruct, after_revision])
55
- EXPLAIN SELECT * FROM events
56
- WHERE context = $1 AND stream_name = $2 AND stream_id = $3 AND stream_revision > $4
57
- SQL
58
- end.to_a.first['QUERY PLAN'].match(/rows=(\d+)/)[1].to_i
177
+ stream_index = streams_global_index_queries.find_by!(stream)
178
+ stream_index.stream_revision - after_revision
59
179
  end
60
180
 
61
- # @param event [PgEventstore::Event]
62
- # @return [PgEventstore::Event]
63
- def reload_event(event)
64
- event_attrs = connection.with do |conn|
65
- conn.exec_params(<<~SQL, [event.stream&.context, event.stream&.stream_name, event.type, event.global_position])
66
- SELECT * FROM events WHERE context = $1 AND stream_name = $2 AND type = $3 AND global_position = $4 LIMIT 1
67
- SQL
68
- end.to_a.first
69
- return unless event_attrs
181
+ private
70
182
 
71
- basic_deserializer.deserialize(event_attrs)
183
+ # @return [PgEventstore::StreamsGlobalIndexQueries]
184
+ def streams_global_index_queries
185
+ StreamsGlobalIndexQueries.new(connection, QueryStrategy::Foreground.new(connection))
72
186
  end
73
187
 
74
- private
188
+ # @return [PgEventstore::TransactionQueries]
189
+ def transaction_queries
190
+ TransactionQueries.new(connection)
191
+ end
75
192
 
76
- # @return [PgEventstore::EventDeserializer]
77
- def basic_deserializer
78
- EventDeserializer.new([], ->(_event_type) { Event })
193
+ # @return [PgEventstore::PartitionQueries]
194
+ def partition_queries
195
+ PartitionQueries.new(connection)
79
196
  end
80
197
  end
81
198
  end