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
@@ -9,8 +9,9 @@ module PgEventstore
9
9
  private :connection
10
10
 
11
11
  # @param connection [PgEventstore::Connection]
12
- def initialize(connection)
12
+ def initialize(connection, query_strategy = QueryStrategy::Foreground.new(connection))
13
13
  @connection = connection
14
+ @query_strategy = query_strategy
14
15
  end
15
16
 
16
17
  # @param stream [PgEventstore::Stream]
@@ -28,24 +29,18 @@ module PgEventstore
28
29
  INSERT INTO partitions (#{attributes.keys.join(', ')})
29
30
  VALUES (#{Utils.positional_vars(attributes.values)}) RETURNING *
30
31
  SQL
31
- partition = connection.with do |conn|
32
+ connection.with do |conn|
32
33
  conn.exec_params(partition_sql, [*attributes.values])
33
34
  end.to_a.first
34
- connection.with do |conn|
35
- conn.exec(<<~SQL)
36
- CREATE TABLE #{attributes[:table_name]} PARTITION OF events
37
- FOR VALUES IN('#{conn.escape_string(stream.context)}') PARTITION BY LIST (stream_name)
38
- SQL
39
- end
40
- partition
41
35
  end
42
36
 
43
37
  # @param stream [PgEventstore::Stream]
44
- # @param context_partition_name [String]
38
+ # @param context_partition_id [Integer]
45
39
  # @return [Hash] partition attributes
46
- def create_stream_name_partition(stream, context_partition_name)
40
+ def create_stream_name_partition(stream, context_partition_id)
47
41
  attributes = {
48
- context: stream.context, stream_name: stream.stream_name, table_name: stream_name_partition_name(stream)
42
+ context: stream.context, stream_name: stream.stream_name, table_name: stream_name_partition_name(stream),
43
+ parent_context_partition_id: context_partition_id
49
44
  }
50
45
 
51
46
  loop do
@@ -58,26 +53,22 @@ module PgEventstore
58
53
  INSERT INTO partitions (#{attributes.keys.join(', ')})
59
54
  VALUES (#{Utils.positional_vars(attributes.values)}) RETURNING *
60
55
  SQL
61
- partition = connection.with do |conn|
56
+ connection.with do |conn|
62
57
  conn.exec_params(partition_sql, [*attributes.values])
63
58
  end.to_a.first
64
- connection.with do |conn|
65
- conn.exec(<<~SQL)
66
- CREATE TABLE #{attributes[:table_name]} PARTITION OF #{context_partition_name}
67
- FOR VALUES IN('#{conn.escape_string(stream.stream_name)}') PARTITION BY LIST (type)
68
- SQL
69
- end
70
- partition
71
59
  end
72
60
 
73
61
  # @param stream [PgEventstore::Stream]
74
62
  # @param event_type [String]
75
- # @param stream_name_partition_name [String]
63
+ # @param context_partition_id [Integer]
64
+ # @param stream_name_partition_id [Integer]
76
65
  # @return [Hash] partition attributes
77
- def create_event_type_partition(stream, event_type, stream_name_partition_name)
66
+ def create_event_type_partition(stream, event_type, context_partition_id,
67
+ stream_name_partition_id)
78
68
  attributes = {
79
- context: stream.context, stream_name: stream.stream_name, event_type: event_type,
80
- table_name: event_type_partition_name(stream, event_type)
69
+ context: stream.context, stream_name: stream.stream_name, event_type:,
70
+ table_name: event_type_partition_name(stream, event_type), parent_context_partition_id: context_partition_id,
71
+ parent_stream_name_partition_id: stream_name_partition_id
81
72
  }
82
73
 
83
74
  loop do
@@ -90,16 +81,9 @@ module PgEventstore
90
81
  INSERT INTO partitions (#{attributes.keys.join(', ')})
91
82
  VALUES (#{Utils.positional_vars(attributes.values)}) RETURNING *
92
83
  SQL
93
- partition = connection.with do |conn|
84
+ connection.with do |conn|
94
85
  conn.exec_params(partition_sql, [*attributes.values])
95
86
  end.to_a.first
96
- connection.with do |conn|
97
- conn.exec(<<~SQL)
98
- CREATE TABLE #{attributes[:table_name]} PARTITION OF #{stream_name_partition_name}
99
- FOR VALUES IN('#{conn.escape_string(event_type)}')
100
- SQL
101
- end
102
- partition
103
87
  end
104
88
 
105
89
  # @param stream [PgEventstore::Stream]
@@ -117,9 +101,15 @@ module PgEventstore
117
101
 
118
102
  context_partition = context_partition(stream) || create_context_partition(stream)
119
103
  stream_name_partition =
120
- stream_name_partition(stream) || create_stream_name_partition(stream, context_partition['table_name'])
104
+ stream_name_partition(stream) ||
105
+ create_stream_name_partition(stream, context_partition['id'])
121
106
 
122
- create_event_type_partition(stream, event_type, stream_name_partition['table_name'])
107
+ create_event_type_partition(
108
+ stream,
109
+ event_type,
110
+ context_partition['id'],
111
+ stream_name_partition['id']
112
+ )
123
113
  end
124
114
 
125
115
  # @param stream [PgEventstore::Stream]
@@ -171,47 +161,15 @@ module PgEventstore
171
161
  # @param ids [Array<Integer>]
172
162
  # @return [Array<Hash>]
173
163
  def find_by_ids(ids)
174
- connection.with do |conn|
175
- conn.exec_params('select * from partitions where id = ANY($1::bigint[])', [ids])
176
- end.to_a
164
+ @query_strategy.exec_params('select * from partitions where id = ANY($1::bigint[])', [ids]).to_a
177
165
  end
178
166
 
179
- # @param stream_filters [Array<Hash[Symbol, String]>]
180
- # @param event_filters [Array<String>]
167
+ # @param filters_collection [PgEventstore::QueryBuilders::Filters::Collection]
181
168
  # @param scope [Symbol] what kind of partition we want to receive. Available options are :event_type, :context,
182
169
  # :stream_name and :auto. In :auto mode the scope will be calculated based on stream_filters and event_filters.
183
170
  # @return [Array<PgEventstore::Partition>]
184
- def partitions(stream_filters, event_filters, scope: :event_type)
185
- stream_filters = stream_filters.select { QueryBuilders::PartitionsFiltering.correct_stream_filter?(_1) }
186
- sql_builder =
187
- if event_filters.any?
188
- # When event type filters are present - they apply constraints to any stream filter. Thus, we can't look up
189
- # partitions by stream attributes separately.
190
- filter = QueryBuilders::PartitionsFiltering.new
191
- stream_filters.each { |attrs| filter.add_stream_attrs(**attrs) }
192
- filter.add_event_types(event_filters)
193
- set_partitions_scope(filter, stream_filters, event_filters, scope)
194
- else
195
- # When event type filters are absent - we can look up partitions by context and context/stream_name
196
- # separately, thus potentially producing one-to-one mapping of filter-to-partition with :auto scope. For
197
- # example, let's say we have stream attributes filter like
198
- # [{ context: 'FooCtx', stream_name: 'Bar'}, { context: 'BarCtx' }], then we would be able to look up
199
- # partitions by the exact match, returning only two of them according to the provided filters - stream
200
- # partition for first filter and context partition for second filter.
201
- builders = stream_filters.map do |attrs|
202
- filter = QueryBuilders::PartitionsFiltering.new
203
- filter.add_stream_attrs(**attrs)
204
- set_partitions_scope(filter, [attrs], event_filters, scope)
205
- end
206
-
207
- sql_builder = SQLBuilder.union_builders(builders) if builders.any?
208
- sql_builder ||
209
- begin
210
- builder = QueryBuilders::PartitionsFiltering.new
211
- set_partitions_scope(builder, stream_filters, event_filters, scope)
212
- end
213
- end
214
-
171
+ def partitions(filters_collection, scope: :event_type)
172
+ sql_builder = QueryBuilders::PartitionsFiltering.assemble_sql_builder(filters_collection, scope:)
215
173
  connection.with do |conn|
216
174
  conn.exec_params(*sql_builder.to_exec_params)
217
175
  end.map(&method(:deserialize))
@@ -236,45 +194,29 @@ module PgEventstore
236
194
  "event_types_#{Digest::MD5.hexdigest("#{stream.context}-#{stream.stream_name}-#{event_type}")[0..5]}"
237
195
  end
238
196
 
239
- private
197
+ # @param partition [PgEventstore::Partition]
198
+ # @return [void]
199
+ def detach_event_type_partition(partition)
200
+ parent = deserialize(find_by_ids([partition.parent_stream_name_partition_id]).first)
201
+ connection.with do |conn|
202
+ conn.exec("alter table #{parent.table_name} detach partition #{partition.table_name}")
203
+ end
204
+ end
240
205
 
241
- # @param partitions_filter [PgEventstore::QueryBuilders::PartitionsFiltering]
242
- # @param stream_filters [Array<Hash[Symbol, String]>]
243
- # @param event_filters [Array<String>]
244
- # @param scope [Symbol]
245
- # @return [PgEventstore::SQLBuilder]
246
- def set_partitions_scope(partitions_filter, stream_filters, event_filters, scope)
247
- case scope
248
- when :event_type
249
- partitions_filter.with_event_types
250
- when :stream_name
251
- filter = QueryBuilders::PartitionsFiltering.new
252
- filter.without_event_types
253
- filter.with_stream_names
254
- builder = filter.to_sql_builder
255
- builder.where(
256
- '(context, stream_name) in ?',
257
- partitions_filter.to_sql_builder.unselect.select('context, stream_name').group('context, stream_name')
206
+ # @param partition [PgEventstore::Partition]
207
+ # @return [void]
208
+ def attach_event_type_partition(partition)
209
+ parent = deserialize(find_by_ids([partition.parent_stream_name_partition_id]).first)
210
+ connection.with do |conn|
211
+ event_type = conn.escape_string(partition.event_type)
212
+ conn.exec(
213
+ "alter table #{parent.table_name} attach partition #{partition.table_name} for values in ('#{event_type}')"
258
214
  )
259
- when :context
260
- filter = QueryBuilders::PartitionsFiltering.new
261
- filter.without_event_types
262
- filter.without_stream_names
263
- builder = filter.to_sql_builder
264
- builder.where('context in ?', partitions_filter.to_sql_builder.unselect.select('context').group('context'))
265
- when :auto
266
- if event_filters.any?
267
- set_partitions_scope(partitions_filter, stream_filters, event_filters, :event_type)
268
- elsif stream_filters.any? { _1[:stream_name] }
269
- set_partitions_scope(partitions_filter, stream_filters, event_filters, :stream_name)
270
- else
271
- set_partitions_scope(partitions_filter, stream_filters, event_filters, :context)
272
- end
273
- else
274
- raise NotImplementedError, "Don't know how to handle #{scope.inspect} scope!"
275
215
  end
276
216
  end
277
217
 
218
+ private
219
+
278
220
  # @param attrs [Hash]
279
221
  # @return [PgEventstore::Partition]
280
222
  def deserialize(attrs)
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ # @!visibility private
5
+ class ReplicaQueries
6
+ MAX_PARTITIONS_TO_RESOLVE_PER_CALL = 100
7
+
8
+ # @!attribute connection
9
+ # @return [PgEventstore::Connection]
10
+ attr_reader :connection
11
+ private :connection
12
+
13
+ # @param connection [PgEventstore::Connection]
14
+ # @param query_strategy [PgEventstore::QueryStrategy]
15
+ def initialize(connection, query_strategy)
16
+ @connection = connection
17
+ @query_strategy = query_strategy
18
+ end
19
+
20
+ # @param indexes [Array<PgEventstore::EventGlobalIndex::SubscriptionRepr>]
21
+ # @return [Array<Integer>]
22
+ def load_subscription_positions(indexes)
23
+ params = [indexes.first.subscription_position, indexes.last.subscription_position]
24
+ exists = @query_strategy.exec_params(<<~SQL, params).first&.[]('one')
25
+ select 1 as one from event_subscription_positions where subscription_position between $1 and $2 limit 1
26
+ SQL
27
+ return [] unless exists
28
+
29
+ @query_strategy.exec_params(<<~SQL, [indexes.map(&:subscription_position)]).map { _1['subscription_position'] }
30
+ select subscription_position from event_subscription_positions where subscription_position = any($1::bigint[])
31
+ SQL
32
+ end
33
+
34
+ # @param indexes [Array<PgEventstore::EventGlobalIndex::SubscriptionRepr>]
35
+ # @return [Array<PgEventstore::RawEvent>]
36
+ def load_events(indexes)
37
+ indexes = indexes.dup
38
+ raw_events = []
39
+ loop do
40
+ range = Utils.range_to_slice(indexes.map(&:event_type_partition_id), MAX_PARTITIONS_TO_RESOLVE_PER_CALL)
41
+ indexes_to_resolve = indexes.slice!(range)
42
+ break if indexes_to_resolve.empty?
43
+
44
+ raw_events.push(*events_global_index_queries.resolve_indexes(indexes_to_resolve, resolve_link_tos: false))
45
+ end
46
+ raw_events.map { RawEvent.new(**_1.transform_keys(&:to_sym)) }
47
+ end
48
+
49
+ # @param indexes [Array<PgEventstore::EventGlobalIndex::SubscriptionRepr>]
50
+ # @return [Array<PgEventstore::EventGlobalIndex>]
51
+ def load_events_global_index(indexes)
52
+ result = @query_strategy.exec_params(<<~SQL, [indexes.map(&:global_position)])
53
+ select * from events_global_index where global_position = any($1::bigint[])
54
+ SQL
55
+ result.map { EventGlobalIndex.new(**_1.transform_keys(&:to_sym)) }
56
+ end
57
+
58
+ # @param indexes [Array<PgEventstore::EventGlobalIndex>]
59
+ # @return [Array<PgEventstore::StreamGlobalIndex>]
60
+ def load_streams_global_index(indexes)
61
+ result = @query_strategy.exec_params(<<~SQL, [indexes.map(&:streams_global_index_id)])
62
+ select * from streams_global_index where id = any($1::bigint[])
63
+ SQL
64
+ stream_revisions_map = indexes.group_by(&:streams_global_index_id).to_h do |stream_id, events_idx|
65
+ [stream_id, events_idx.max_by(&:stream_revision).stream_revision]
66
+ end
67
+ result.map do |attrs|
68
+ attrs = attrs.transform_keys(&:to_sym)
69
+ attrs[:stream_revision] = stream_revisions_map[attrs[:id]]
70
+ StreamGlobalIndex.new(**attrs)
71
+ end
72
+ end
73
+
74
+ # @param indexes [Array<PgEventstore::EventGlobalIndex::SubscriptionRepr>]
75
+ # @return [Array<PgEventstore::EventMarkerIndex>]
76
+ def load_event_markers_index(indexes)
77
+ result = @query_strategy.exec_params(<<~SQL, [indexes.map(&:global_position)])
78
+ select * from event_markers_index where global_position = any($1::bigint[])
79
+ SQL
80
+ result.map { EventMarkerIndex.new(**_1.transform_keys(&:to_sym)) }
81
+ end
82
+
83
+ # @param indexes [Array<PgEventstore::EventMarkerIndex>]
84
+ # @return [Array<PgEventstore::EventMarker>]
85
+ def load_markers(indexes)
86
+ result = @query_strategy.exec_params(<<~SQL, [indexes.map(&:marker_id).uniq])
87
+ select * from event_markers where id = any($1::bigint[])
88
+ SQL
89
+ result.map { EventMarker.new(**_1.transform_keys(&:to_sym)) }
90
+ end
91
+
92
+ # @param indexes [Array<PgEventstore::EventGlobalIndex>]
93
+ # @return [Array<PgEventstore::Partition>]
94
+ def load_partitions(indexes)
95
+ ids = indexes.map(&:context_partition_id).uniq + indexes.map(&:stream_name_partition_id).uniq +
96
+ indexes.map(&:event_type_partition_id).uniq
97
+ result = @query_strategy.exec_params(<<~SQL, [ids])
98
+ select * from partitions where id = any($1::bigint[]) order by id
99
+ SQL
100
+ result.map { Partition.new(**_1.transform_keys(&:to_sym)) }
101
+ end
102
+
103
+ private
104
+
105
+ # @return [PgEventstore::EventsGlobalIndexQueries]
106
+ def events_global_index_queries
107
+ EventsGlobalIndexQueries.new(connection, @query_strategy)
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ # @!visibility private
5
+ class StreamsGlobalIndexQueries
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 stream [PgEventstore::Stream]
19
+ # @return [PgEventstore::StreamGlobalIndex]
20
+ def find_or_create_by(stream)
21
+ transaction_queries.transaction do
22
+ find_by(stream) || create(stream)
23
+ end
24
+ end
25
+
26
+ # @param stream [PgEventstore::Stream]
27
+ # @return [PgEventstore::StreamGlobalIndex, nil]
28
+ def find_by(stream)
29
+ idx_partitions_filter = QueryBuilders::Filters::Collection.from_options({ filter: { streams: [stream.to_hash] } })
30
+ filtering = QueryBuilders::StreamsGlobalIndexFiltering.new
31
+ idx_partitions_filter.collection.each(&filtering.method(:add_filter_row))
32
+ deserialize_one(@query_strategy.exec_params(*filtering.to_exec_params))
33
+ end
34
+
35
+ # @param stream [PgEventstore::Stream]
36
+ # @return [PgEventstore::StreamGlobalIndex]
37
+ def find_by!(stream)
38
+ find_by(stream) ||
39
+ raise(RecordNotFound.new(QueryBuilders::StreamsGlobalIndexFiltering::PRIMARY_TABLE_NAME, stream))
40
+ end
41
+
42
+ # @param id [Integer]
43
+ # @param attrs [Hash]
44
+ # @return [void]
45
+ def update(id, **attrs)
46
+ attrs_sql = attrs.keys.map.with_index(2) do |attr, index|
47
+ "#{attr} = $#{index}"
48
+ end.join(', ')
49
+ @query_strategy.exec_params(<<~SQL, [id, *attrs.values])
50
+ UPDATE streams_global_index SET #{attrs_sql} WHERE id = $1
51
+ SQL
52
+ end
53
+
54
+ # @param stream [PgEventstore::Stream]
55
+ # @return [PgEventstore::StreamGlobalIndex]
56
+ def create(stream)
57
+ filter_collection = QueryBuilders::Filters::Collection.from_options(
58
+ { filter: { streams: [{ context: stream.context, stream_name: stream.stream_name }] } }
59
+ )
60
+ partitions_sql_builder = QueryBuilders::PartitionsFiltering.assemble_sql_builder(
61
+ filter_collection,
62
+ scope: :stream_name
63
+ )
64
+ partitions_sql_builder.unselect.select('id')
65
+ sql, positional_args = partitions_sql_builder.to_exec_params
66
+ stream_revision = Stream::NON_EXISTING_STREAM_REVISION
67
+ starting_position = StreamGlobalIndex::INITIAL_STARTING_POSITION
68
+ stream_id = PG::Connection.escape(stream.stream_id)
69
+ value = "((#{sql}), '#{stream_id}', #{stream_revision}, #{starting_position})"
70
+
71
+ res = @query_strategy.exec_params(<<~SQL, positional_args)
72
+ INSERT INTO streams_global_index ("partition_id", "stream_id", "stream_revision", "starting_position")
73
+ VALUES #{value} RETURNING *
74
+ SQL
75
+ deserialize_one(res)
76
+ end
77
+
78
+ # @param id [Integer]
79
+ # @return [Boolean]
80
+ def delete(id)
81
+ res = @query_strategy.exec_params('delete from streams_global_index where id = $1', [id])
82
+ res.cmd_tuples == 1
83
+ end
84
+
85
+ # @param stream [PgEventstore::Stream]
86
+ # @return [Boolean]
87
+ def stream_exists?(stream)
88
+ idx_partitions_filter = QueryBuilders::Filters::Collection.from_options({ filter: { streams: [stream.to_hash] } })
89
+ filtering = QueryBuilders::StreamsGlobalIndexFiltering.new
90
+ idx_partitions_filter.collection.each(&filtering.method(:add_filter_row))
91
+ builder = filtering.to_sql_builder.unselect.select('1 as exists')
92
+ @query_strategy.exec_params(*builder.to_exec_params).ntuples == 1
93
+ end
94
+
95
+ # @param stream [PgEventstore::Stream]
96
+ # @return [Integer, nil]
97
+ def stream_revision(stream)
98
+ idx_partitions_filter = QueryBuilders::Filters::Collection.from_options({ filter: { streams: [stream.to_hash] } })
99
+ filtering = QueryBuilders::StreamsGlobalIndexFiltering.new
100
+ idx_partitions_filter.collection.each(&filtering.method(:add_filter_row))
101
+ builder = filtering.to_sql_builder.unselect.select('stream_revision')
102
+ @query_strategy.exec_params(*builder.to_exec_params).first&.dig('stream_revision')
103
+ end
104
+
105
+ # @param options [Hash]
106
+ # @option options [Integer, nil] :from_position
107
+ # @option options [Integer, nil] :max_count
108
+ # @option options [String, Symbol, nil] :direction
109
+ # @return [Array<PgEventstore::StreamGlobalIndex>]
110
+ def streams_global_index(options)
111
+ sql_builder = QueryBuilders::StreamsGlobalIndexFiltering.sql_builder_for_basic_pagination(options)
112
+ deserialize_many(@query_strategy.exec_params(*sql_builder.to_exec_params))
113
+ end
114
+
115
+ # @param indexes [Array<PgEventstore::StreamGlobalIndex>]
116
+ # @return [Array<PgEventstore::Stream>]
117
+ def resolve_indexes(indexes)
118
+ return [] if indexes.empty?
119
+
120
+ partition_ids = indexes.map(&:partition_id).uniq
121
+ partitions = partition_queries.find_by_ids(partition_ids).to_h { [_1['id'], _1] }
122
+ indexes.map do |stream_global_index|
123
+ partition = partitions[stream_global_index.partition_id]
124
+ Stream.new(
125
+ context: partition['context'],
126
+ stream_name: partition['stream_name'],
127
+ stream_id: stream_global_index.stream_id,
128
+ stream_revision: stream_global_index.stream_revision,
129
+ starting_position: stream_global_index.starting_position
130
+ )
131
+ end
132
+ end
133
+
134
+ private
135
+
136
+ # @return [PgEventstore::TransactionQueries]
137
+ def transaction_queries
138
+ TransactionQueries.new(connection)
139
+ end
140
+
141
+ # @param pg_result [PG::Result]
142
+ # @return [PgEventstore::StreamGlobalIndex, nil]
143
+ def deserialize_one(pg_result)
144
+ first_result = pg_result.first
145
+ return unless first_result
146
+
147
+ StreamGlobalIndex.new(**first_result.transform_keys(&:to_sym))
148
+ end
149
+
150
+ # @param pg_result [PG::Result]
151
+ # @return [Array<PgEventstore::StreamGlobalIndex>]
152
+ def deserialize_many(pg_result)
153
+ pg_result.map do |attrs|
154
+ StreamGlobalIndex.new(**attrs.transform_keys(&:to_sym))
155
+ end
156
+ end
157
+
158
+ # @return [PgEventstore::PartitionQueries]
159
+ def partition_queries
160
+ PartitionQueries.new(connection)
161
+ end
162
+ end
163
+ end
@@ -11,6 +11,8 @@ module PgEventstore
11
11
  }.tap do |h|
12
12
  h.default = h[:serializable]
13
13
  end.freeze
14
+ # @return [Integer]
15
+ MAX_NUMBER_OF_UNIQUE_CONSTRAINTS_ERROR_RETRIES = 5
14
16
 
15
17
  # @!attribute connection
16
18
  # @return [PgEventstore::Connection]
@@ -23,39 +25,85 @@ module PgEventstore
23
25
  end
24
26
 
25
27
  # @param level [Symbol] transaction isolation level
28
+ # @param read_only [Boolean] whether transaction is read-only
26
29
  # @return [void]
27
- def transaction(level = :serializable, &blk)
30
+ def transaction(level = :serializable, read_only: false, &blk)
28
31
  connection.with do |conn|
29
32
  # We are inside a transaction already - no need to start another one
30
33
  next yield if [PG::PQTRANS_ACTIVE, PG::PQTRANS_INTRANS].include?(conn.transaction_status)
31
34
 
32
- pg_transaction(ISOLATION_LEVELS[level], conn, &blk)
35
+ pg_transaction(ISOLATION_LEVELS[level], read_only, conn, &blk)
33
36
  end
34
37
  end
35
38
 
36
39
  private
37
40
 
38
41
  # @param level [String] PostgreSQL transaction isolation level
42
+ # @param read_only [Boolean]
39
43
  # @param pg_connection [PG::Connection]
40
44
  # @return [void]
41
- def pg_transaction(level, pg_connection, &_blk)
42
- pg_connection.transaction do
43
- pg_connection.exec("SET TRANSACTION ISOLATION LEVEL #{level}")
44
- yield
45
+ def pg_transaction(level, read_only, pg_connection, &)
46
+ retries_count = 0
47
+ begin
48
+ _transaction(pg_connection) do
49
+ if read_only
50
+ pg_connection.exec("SET TRANSACTION ISOLATION LEVEL #{level} READ ONLY")
51
+ else
52
+ pg_connection.exec("SET TRANSACTION ISOLATION LEVEL #{level}")
53
+ end
54
+ yield
55
+ end
56
+ rescue PG::TRSerializationFailure, PG::TRDeadlockDetected
57
+ retry
58
+ rescue PG::UniqueViolation
59
+ retries_count += 1
60
+ raise if retries_count >= MAX_NUMBER_OF_UNIQUE_CONSTRAINTS_ERROR_RETRIES
61
+
62
+ retry
45
63
  end
46
- rescue PG::TRSerializationFailure, PG::TRDeadlockDetected
47
- retry
48
64
  rescue MissingPartitions => error
49
65
  error.event_types.each do |event_type|
50
66
  transaction do
51
67
  partition_queries.create_partitions(error.stream, event_type)
52
68
  end
53
- rescue PG::UniqueViolation
54
- retry
55
69
  end
56
70
  retry
57
71
  end
58
72
 
73
+ # TODO: next pg gem release (presumably v1.6.4) should already include the fix of PG::Connection#transaction.
74
+ # Changes are already in master branch
75
+ # https://github.com/ged/ruby-pg/commit/c3d2aabd0a19d20bb1f2b2fa2e5b30f03b043aff
76
+ # Once it is released - change pg gem requirement in gemspec to '>= 1.6.4' and remove this implementation.
77
+ # @param conn [PG::Connection]
78
+ # rubocop:disable Lint/RescueException
79
+ def _transaction(conn)
80
+ rollback = false
81
+ conn.exec('BEGIN')
82
+ yield
83
+ rescue PG::RollbackTransaction
84
+ rollback = true
85
+ perform_rollback(conn)
86
+ rescue Exception
87
+ rollback = true
88
+ perform_rollback(conn)
89
+ raise
90
+ ensure
91
+ unless rollback
92
+ if Thread.current.status == 'aborting'
93
+ perform_rollback(conn)
94
+ else
95
+ conn.exec('COMMIT')
96
+ end
97
+ end
98
+ end
99
+ # rubocop:enable Lint/RescueException
100
+
101
+ def perform_rollback(conn)
102
+ conn.cancel if conn.transaction_status == PG::PQTRANS_ACTIVE
103
+ conn.block
104
+ conn.exec('ROLLBACK')
105
+ end
106
+
59
107
  # @return [PgEventstore::PartitionQueries]
60
108
  def partition_queries
61
109
  PartitionQueries.new(connection)
@@ -1,14 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'sql_builder'
4
+ require_relative 'query_builders/filters/collection'
5
+ require_relative 'query_builders/read_cursor/stream_cursor'
4
6
  require_relative 'query_builders/basic_filtering'
7
+ require_relative 'query_builders/subscription_events_filtering'
5
8
  require_relative 'query_builders/events_filtering'
6
9
  require_relative 'query_builders/partitions_filtering'
10
+ require_relative 'query_builders/events_global_index_filtering'
11
+ require_relative 'query_builders/streams_global_index_filtering'
12
+ require_relative 'query_builders/event_subscription_positions_filtering'
13
+ require_relative 'query_builders/event_markers_filtering'
14
+ require_relative 'query_builders/event_markers_index_filtering'
15
+ require_relative 'query_builders/index_based_events_filtering'
7
16
  require_relative 'queries/transaction_queries'
8
17
  require_relative 'queries/event_queries'
9
18
  require_relative 'queries/partition_queries'
10
19
  require_relative 'queries/links_resolver'
11
20
  require_relative 'queries/maintenance_queries'
21
+ require_relative 'queries/events_global_index_queries'
22
+ require_relative 'queries/streams_global_index_queries'
23
+ require_relative 'queries/event_marker_queries'
24
+ require_relative 'queries/index_filtering_queries'
25
+ require_relative 'queries/replica_queries'
12
26
 
13
27
  module PgEventstore
14
28
  # @!visibility private
@@ -24,9 +38,23 @@ module PgEventstore
24
38
  # @!attribute transactions
25
39
  # @return [PgEventstore::TransactionQueries, nil]
26
40
  attribute(:transactions)
27
-
28
41
  # @!attribute maintenance
29
42
  # @return [PgEventstore::MaintenanceQueries, nil]
30
43
  attribute(:maintenance)
44
+ # @!attribute maintenance
45
+ # @return [PgEventstore::EventsGlobalIndexQueries, nil]
46
+ attribute(:events_global_index)
47
+ # @!attribute maintenance
48
+ # @return [PgEventstore::StreamsGlobalIndexQueries, nil]
49
+ attribute(:streams_global_index)
50
+ # @!attribute event_subscription_positions
51
+ # @return [PgEventstore::EventSubscriptionPositionQueries, nil]
52
+ attribute(:event_subscription_positions)
53
+ # @!attribute event_subscription_positions
54
+ # @return [PgEventstore::EventMarkerQueries, nil]
55
+ attribute(:event_markers)
56
+ # @!attribute event_subscription_positions
57
+ # @return [PgEventstore::IndexFilteringQueries, nil]
58
+ attribute(:index_filtering)
31
59
  end
32
60
  end