pg_eventstore 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (303) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +10 -1
  3. data/CHANGELOG.md +186 -20
  4. data/README.md +25 -15
  5. data/db/migrations/10_setup_pg_cron.rb +1 -23
  6. data/db/migrations/15_create_streams_global_index.sql +17 -0
  7. data/db/migrations/16_populate_streams_global_index.rb +150 -0
  8. data/db/migrations/17_create_events_global_index.sql +62 -0
  9. data/db/migrations/18_populate_events_global_index.rb +124 -0
  10. data/db/migrations/19_populate_events_global_index_p2.rb +25 -0
  11. data/db/migrations/20_migrate_subscriptions_current_position.sql +19 -0
  12. data/db/migrations/21_create_partitions_stats.sql +2 -0
  13. data/db/migrations/22_drop_streams_sql_view.sql +2 -0
  14. data/db/migrations/23_adjust_partitions_indexes.sql +5 -0
  15. data/db/migrations/24_drop_legacy_events_indexes.sql +3 -0
  16. data/db/migrations/25_add_parent_partitions_info_to_partitions.sql +16 -0
  17. data/db/migrations/26_change_events_stream_revision_type.rb +35 -0
  18. data/db/migrations/27_drop_events_horizon.sql +3 -0
  19. data/db/migrations/28_create_maintenance_tasks.sql +8 -0
  20. data/db/migrations/29_create_event_markers.sql +35 -0
  21. data/db/migrations/30_improve_partitions_search_in_admin_ui.rb +24 -0
  22. data/db/migrations/31_drop_events_id_default_value.sql +1 -0
  23. data/db/migrations/32_create_parittions_triggers.sql +101 -0
  24. data/docs/AGENTS.fragment.md +696 -0
  25. data/docs/admin_ui.md +3 -1
  26. data/docs/appending_events.md +110 -49
  27. data/docs/configuration.md +69 -36
  28. data/docs/event_tracing.md +55 -0
  29. data/docs/events_and_streams.md +54 -30
  30. data/docs/how_it_works.md +57 -16
  31. data/docs/linking_events.md +2 -1
  32. data/docs/maintenance.md +14 -5
  33. data/docs/multiple_commands.md +17 -8
  34. data/docs/reading_events.md +208 -43
  35. data/docs/reading_streams.md +50 -0
  36. data/docs/replication.md +99 -0
  37. data/docs/subscriptions.md +165 -35
  38. data/docs/writing_middleware.md +25 -8
  39. data/ext/pg_eventstore_ext/extconf.rb +5 -0
  40. data/ext/pg_eventstore_ext/pg_eventstore_ext.c +74 -0
  41. data/lib/pg_eventstore/async_runner.rb +63 -0
  42. data/lib/pg_eventstore/basic_config.rb +15 -0
  43. data/lib/pg_eventstore/chunks/chunk.rb +31 -0
  44. data/lib/pg_eventstore/chunks/read_api_events_index_chunk.rb +83 -0
  45. data/lib/pg_eventstore/chunks/replica_events_index_chunk.rb +35 -0
  46. data/lib/pg_eventstore/chunks/repository.rb +73 -0
  47. data/lib/pg_eventstore/chunks/subscription_checkpoint_chunk.rb +48 -0
  48. data/lib/pg_eventstore/chunks/subscription_events_index_chunk.rb +99 -0
  49. data/lib/pg_eventstore/chunks.rb +14 -0
  50. data/lib/pg_eventstore/client.rb +163 -50
  51. data/lib/pg_eventstore/commands/append.rb +62 -43
  52. data/lib/pg_eventstore/commands/delete_event.rb +7 -8
  53. data/lib/pg_eventstore/commands/delete_stream.rb +3 -3
  54. data/lib/pg_eventstore/commands/event_modifiers/prepare_link_event.rb +9 -7
  55. data/lib/pg_eventstore/commands/event_modifiers/prepare_regular_event.rb +12 -7
  56. data/lib/pg_eventstore/commands/link_to.rb +5 -5
  57. data/lib/pg_eventstore/commands/read.rb +23 -5
  58. data/lib/pg_eventstore/commands/read_grouped.rb +46 -0
  59. data/lib/pg_eventstore/commands/read_streams.rb +18 -0
  60. data/lib/pg_eventstore/commands/read_streams_paginated.rb +56 -0
  61. data/lib/pg_eventstore/commands/regular_stream_read_paginated.rb +4 -4
  62. data/lib/pg_eventstore/commands/revision_check/current_revision.rb +51 -0
  63. data/lib/pg_eventstore/commands/revision_check/event_type_revisions_comparison.rb +46 -0
  64. data/lib/pg_eventstore/commands/revision_check/expected_revision.rb +135 -0
  65. data/lib/pg_eventstore/commands/revision_check/stream_revision_check.rb +98 -0
  66. data/lib/pg_eventstore/commands/revision_check/stream_revision_comparison.rb +36 -0
  67. data/lib/pg_eventstore/commands/stream_revision.rb +14 -0
  68. data/lib/pg_eventstore/commands/system_stream_read_paginated.rb +4 -4
  69. data/lib/pg_eventstore/commands.rb +4 -2
  70. data/lib/pg_eventstore/config.rb +30 -9
  71. data/lib/pg_eventstore/connection.rb +21 -2
  72. data/lib/pg_eventstore/errors.rb +136 -10
  73. data/lib/pg_eventstore/event.rb +34 -2
  74. data/lib/pg_eventstore/event_deserializer.rb +5 -4
  75. data/lib/pg_eventstore/event_global_index.rb +118 -0
  76. data/lib/pg_eventstore/event_marker.rb +16 -0
  77. data/lib/pg_eventstore/event_marker_index.rb +25 -0
  78. data/lib/pg_eventstore/event_serializer.rb +6 -0
  79. data/lib/pg_eventstore/extensions/acts_as_configurable.rb +88 -0
  80. data/lib/pg_eventstore/extensions/options_defaults.rb +25 -0
  81. data/lib/pg_eventstore/extensions/options_extension.rb +2 -2
  82. data/lib/pg_eventstore/feature_marker.rb +18 -0
  83. data/lib/pg_eventstore/maintenance.rb +11 -6
  84. data/lib/pg_eventstore/middleware/event_tracing.rb +53 -0
  85. data/lib/pg_eventstore/partition.rb +6 -0
  86. data/lib/pg_eventstore/pg_connection.rb +70 -14
  87. data/lib/pg_eventstore/queries/event_marker_queries.rb +74 -0
  88. data/lib/pg_eventstore/queries/event_queries.rb +7 -93
  89. data/lib/pg_eventstore/queries/events_global_index_queries.rb +131 -0
  90. data/lib/pg_eventstore/queries/index_filtering_queries.rb +179 -0
  91. data/lib/pg_eventstore/queries/links_resolver.rb +4 -4
  92. data/lib/pg_eventstore/queries/maintenance_queries.rb +159 -42
  93. data/lib/pg_eventstore/queries/partition_queries.rb +45 -103
  94. data/lib/pg_eventstore/queries/replica_queries.rb +110 -0
  95. data/lib/pg_eventstore/queries/streams_global_index_queries.rb +163 -0
  96. data/lib/pg_eventstore/queries/transaction_queries.rb +52 -10
  97. data/lib/pg_eventstore/queries.rb +29 -1
  98. data/lib/pg_eventstore/query_builders/basic_filtering.rb +38 -5
  99. data/lib/pg_eventstore/query_builders/event_markers_filtering.rb +47 -0
  100. data/lib/pg_eventstore/query_builders/event_markers_index_filtering.rb +275 -0
  101. data/lib/pg_eventstore/query_builders/event_subscription_positions_filtering.rb +37 -0
  102. data/lib/pg_eventstore/query_builders/events_filtering.rb +15 -164
  103. data/lib/pg_eventstore/query_builders/events_global_index_filtering.rb +227 -0
  104. data/lib/pg_eventstore/query_builders/filters/collection.rb +324 -0
  105. data/lib/pg_eventstore/query_builders/filters/event_type_filter.rb +37 -0
  106. data/lib/pg_eventstore/query_builders/filters/filter_row.rb +54 -0
  107. data/lib/pg_eventstore/query_builders/filters/marker_filter.rb +20 -0
  108. data/lib/pg_eventstore/query_builders/filters/marker_filter_row.rb +49 -0
  109. data/lib/pg_eventstore/query_builders/filters/stream_filter.rb +48 -0
  110. data/lib/pg_eventstore/query_builders/index_based_events_filtering.rb +260 -0
  111. data/lib/pg_eventstore/query_builders/partitions_filtering.rb +175 -51
  112. data/lib/pg_eventstore/query_builders/read_cursor/stream_cursor.rb +122 -0
  113. data/lib/pg_eventstore/query_builders/streams_global_index_filtering.rb +83 -0
  114. data/lib/pg_eventstore/query_builders/subscription_events_filtering.rb +79 -0
  115. data/lib/pg_eventstore/query_strategy/async.rb +64 -0
  116. data/lib/pg_eventstore/query_strategy/foreground.rb +25 -0
  117. data/lib/pg_eventstore/query_strategy.rb +19 -0
  118. data/lib/pg_eventstore/raw_event.rb +46 -0
  119. data/lib/pg_eventstore/rspec/test_helpers.rb +21 -8
  120. data/lib/pg_eventstore/sql_builder.rb +87 -8
  121. data/lib/pg_eventstore/stream.rb +13 -16
  122. data/lib/pg_eventstore/stream_global_index.rb +28 -0
  123. data/lib/pg_eventstore/subscriptions/callback_handlers/events_processor_handlers.rb +5 -17
  124. data/lib/pg_eventstore/subscriptions/callback_handlers/events_subscription_position_worker_handlers.rb +36 -0
  125. data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_feeder_handlers.rb +12 -0
  126. data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rb +21 -7
  127. data/lib/pg_eventstore/subscriptions/events_processor.rb +20 -18
  128. data/lib/pg_eventstore/subscriptions/events_processor_consumer/multiple.rb +63 -0
  129. data/lib/pg_eventstore/subscriptions/events_processor_consumer/replica.rb +68 -0
  130. data/lib/pg_eventstore/subscriptions/events_processor_consumer/single.rb +62 -0
  131. data/lib/pg_eventstore/subscriptions/events_processor_consumer.rb +44 -0
  132. data/lib/pg_eventstore/subscriptions/events_subscription_position_worker.rb +68 -0
  133. data/lib/pg_eventstore/subscriptions/queries/event_subscription_position_queries.rb +177 -0
  134. data/lib/pg_eventstore/subscriptions/queries/subscription_queries.rb +78 -54
  135. data/lib/pg_eventstore/subscriptions/replica_subscription_handler.rb +140 -0
  136. data/lib/pg_eventstore/subscriptions/replica_subscription_runner.rb +9 -0
  137. data/lib/pg_eventstore/subscriptions/subscription.rb +2 -1
  138. data/lib/pg_eventstore/subscriptions/subscription_feed_strategies/collection.rb +36 -0
  139. data/lib/pg_eventstore/subscriptions/subscription_feed_strategies/index_read_strategy.rb +82 -0
  140. data/lib/pg_eventstore/subscriptions/subscription_feed_strategies/replication_strategy.rb +75 -0
  141. data/lib/pg_eventstore/subscriptions/subscription_feed_strategy.rb +31 -0
  142. data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +24 -0
  143. data/lib/pg_eventstore/subscriptions/subscription_handler_performance.rb +4 -2
  144. data/lib/pg_eventstore/subscriptions/subscription_runner.rb +20 -9
  145. data/lib/pg_eventstore/subscriptions/subscription_runner_commands/reset_position.rb +1 -1
  146. data/lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb +9 -18
  147. data/lib/pg_eventstore/subscriptions/subscriptions_lifecycle.rb +0 -12
  148. data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +74 -14
  149. data/lib/pg_eventstore/{subscriptions/synchronized_array.rb → synchronized_array.rb} +15 -0
  150. data/lib/pg_eventstore/tasks/setup.rake +2 -2
  151. data/lib/pg_eventstore/utils.rb +31 -0
  152. data/lib/pg_eventstore/version.rb +1 -1
  153. data/lib/pg_eventstore/web/application.rb +100 -16
  154. data/lib/pg_eventstore/web/paginator/base_collection.rb +5 -1
  155. data/lib/pg_eventstore/web/paginator/event_types_collection.rb +14 -4
  156. data/lib/pg_eventstore/web/paginator/events_collection.rb +29 -33
  157. data/lib/pg_eventstore/web/paginator/helpers.rb +16 -9
  158. data/lib/pg_eventstore/web/paginator/markers_collection.rb +48 -0
  159. data/lib/pg_eventstore/web/paginator/stream_contexts_collection.rb +13 -2
  160. data/lib/pg_eventstore/web/paginator/stream_ids_collection.rb +21 -11
  161. data/lib/pg_eventstore/web/paginator/stream_names_collection.rb +18 -4
  162. data/lib/pg_eventstore/web/paginator/streams_collection.rb +95 -0
  163. data/lib/pg_eventstore/web/public/javascripts/pg_eventstore.js +49 -22
  164. data/lib/pg_eventstore/web/subscriptions/set_collection.rb +1 -1
  165. data/lib/pg_eventstore/web/subscriptions/subscriptions.rb +1 -1
  166. data/lib/pg_eventstore/web/subscriptions/with_state/set_collection.rb +1 -1
  167. data/lib/pg_eventstore/web/subscriptions/with_state/subscriptions.rb +1 -1
  168. data/lib/pg_eventstore/web/views/home/dashboard.erb +11 -9
  169. data/lib/pg_eventstore/web/views/home/partials/event_filter.erb +18 -5
  170. data/lib/pg_eventstore/web/views/home/partials/events.erb +38 -1
  171. data/lib/pg_eventstore/web/views/home/partials/markers_filter.erb +18 -0
  172. data/lib/pg_eventstore/web/views/layouts/application.erb +6 -0
  173. data/lib/pg_eventstore/web/views/streams/index.erb +110 -0
  174. data/lib/pg_eventstore/web/views/streams/partials/streams.erb +11 -0
  175. data/lib/pg_eventstore/web/views/subscriptions/index.erb +4 -1
  176. data/lib/pg_eventstore/web.rb +2 -0
  177. data/lib/pg_eventstore.rb +23 -60
  178. data/pg_eventstore.gemspec +6 -4
  179. data/sig/interfaces/_raw_events_handler.rbs +3 -0
  180. data/sig/interfaces/subscription_handler.rbs +1 -1
  181. data/sig/pg/result.rbs +9 -0
  182. data/sig/pg_eventstore/async_runner.rbs +13 -0
  183. data/sig/pg_eventstore/basic_config.rbs +9 -0
  184. data/sig/pg_eventstore/chunks/chunk.rbs +13 -0
  185. data/sig/pg_eventstore/chunks/read_api_events_index_chunk.rbs +30 -0
  186. data/sig/pg_eventstore/chunks/replica_events_index_chunk.rbs +11 -0
  187. data/sig/pg_eventstore/chunks/repository.rbs +22 -0
  188. data/sig/pg_eventstore/chunks/subscription_checkpoint_chunk.rbs +18 -0
  189. data/sig/pg_eventstore/chunks/subscription_events_index_chunk.rbs +37 -0
  190. data/sig/pg_eventstore/client.rbs +40 -42
  191. data/sig/pg_eventstore/commands/append.rbs +11 -25
  192. data/sig/pg_eventstore/commands/event_modifiers/prepare_link_event.rbs +9 -12
  193. data/sig/pg_eventstore/commands/event_modifiers/prepare_regular_event.rbs +5 -4
  194. data/sig/pg_eventstore/commands/link_to.rbs +6 -11
  195. data/sig/pg_eventstore/commands/read.rbs +2 -5
  196. data/sig/pg_eventstore/commands/read_grouped.rbs +7 -0
  197. data/sig/pg_eventstore/commands/read_streams.rbs +7 -0
  198. data/sig/pg_eventstore/commands/read_streams_paginated.rbs +16 -0
  199. data/sig/pg_eventstore/commands/regular_stream_read_paginated.rbs +9 -17
  200. data/sig/pg_eventstore/commands/revision_check/current_revision.rbs +25 -0
  201. data/sig/pg_eventstore/commands/revision_check/event_type_revisions_comparison.rbs +14 -0
  202. data/sig/pg_eventstore/commands/revision_check/expected_revision.rbs +67 -0
  203. data/sig/pg_eventstore/commands/revision_check/stream_revision_check.rbs +20 -0
  204. data/sig/pg_eventstore/commands/revision_check/stream_revision_comparison.rbs +9 -0
  205. data/sig/pg_eventstore/commands/stream_revision.rbs +7 -0
  206. data/sig/pg_eventstore/commands/system_stream_read_paginated.rbs +9 -14
  207. data/sig/pg_eventstore/config.rbs +15 -6
  208. data/sig/pg_eventstore/connection.rbs +11 -18
  209. data/sig/pg_eventstore/errors.rbs +64 -34
  210. data/sig/pg_eventstore/event.rbs +16 -2
  211. data/sig/pg_eventstore/event_deserializer.rbs +5 -10
  212. data/sig/pg_eventstore/event_global_index.rbs +60 -0
  213. data/sig/pg_eventstore/event_marker.rbs +9 -0
  214. data/sig/pg_eventstore/event_marker_index.rbs +12 -0
  215. data/sig/pg_eventstore/extensions/acts_as_configurable.rbs +29 -0
  216. data/sig/pg_eventstore/extensions/options_defaults.rbs +7 -0
  217. data/sig/pg_eventstore/feature_marker.rbs +10 -0
  218. data/sig/pg_eventstore/maintenance.rbs +12 -8
  219. data/sig/pg_eventstore/middleware/event_trace.rbs +14 -0
  220. data/sig/pg_eventstore/partition.rbs +2 -4
  221. data/sig/pg_eventstore/pg_connection.rbs +8 -0
  222. data/sig/pg_eventstore/queries/event_marker_queries.rbs +21 -0
  223. data/sig/pg_eventstore/queries/event_queries.rbs +4 -36
  224. data/sig/pg_eventstore/queries/events_global_index_queries.rbs +37 -0
  225. data/sig/pg_eventstore/queries/index_filtering_queries.rbs +50 -0
  226. data/sig/pg_eventstore/queries/links_resolver.rbs +5 -9
  227. data/sig/pg_eventstore/queries/maintenance_queries.rbs +12 -9
  228. data/sig/pg_eventstore/queries/partition_queries.rbs +32 -67
  229. data/sig/pg_eventstore/queries/replica_queries.rbs +31 -0
  230. data/sig/pg_eventstore/queries/streams_global_index_queries.rbs +39 -0
  231. data/sig/pg_eventstore/queries/transaction_queries.rbs +2 -0
  232. data/sig/pg_eventstore/queries.rbs +11 -21
  233. data/sig/pg_eventstore/query_builders/basic_filtering.rbs +10 -3
  234. data/sig/pg_eventstore/query_builders/event_markers_filtering.rbs +17 -0
  235. data/sig/pg_eventstore/query_builders/event_markers_index_filtering.rbs +69 -0
  236. data/sig/pg_eventstore/query_builders/event_subscription_positions_filtering.rbs +15 -0
  237. data/sig/pg_eventstore/query_builders/events_filtering_query.rbs +9 -51
  238. data/sig/pg_eventstore/query_builders/events_global_index_filtering.rbs +68 -0
  239. data/sig/pg_eventstore/query_builders/filters/collection.rbs +84 -0
  240. data/sig/pg_eventstore/query_builders/filters/event_type_filter.rbs +19 -0
  241. data/sig/pg_eventstore/query_builders/filters/filter_row.rbs +21 -0
  242. data/sig/pg_eventstore/query_builders/filters/marker_filter.rbs +13 -0
  243. data/sig/pg_eventstore/query_builders/filters/marker_filter_row.rbs +19 -0
  244. data/sig/pg_eventstore/query_builders/filters/stream_filter.rbs +24 -0
  245. data/sig/pg_eventstore/query_builders/index_based_events_filtering.rbs +60 -0
  246. data/sig/pg_eventstore/query_builders/partitions_filtering.rbs +29 -10
  247. data/sig/pg_eventstore/query_builders/read_cursor/stream_cursor.rbs +47 -0
  248. data/sig/pg_eventstore/query_builders/streams_global_index_filtering.rbs +27 -0
  249. data/sig/pg_eventstore/query_builders/subscription_events_filtering.rbs +26 -0
  250. data/sig/pg_eventstore/query_strategy/async.rbs +17 -0
  251. data/sig/pg_eventstore/query_strategy/foreground.rbs +11 -0
  252. data/sig/pg_eventstore/query_strategy.rbs +7 -0
  253. data/sig/pg_eventstore/raw_event.rbs +20 -0
  254. data/sig/pg_eventstore/sql_builder.rbs +32 -10
  255. data/sig/pg_eventstore/stream.rbs +10 -18
  256. data/sig/pg_eventstore/stream_global_index.rbs +14 -0
  257. data/sig/pg_eventstore/subscriptions/callback_handlers/events_processor_handlers.rbs +5 -4
  258. data/sig/pg_eventstore/subscriptions/callback_handlers/events_subscription_position_worker_handlers.rbs +9 -0
  259. data/sig/pg_eventstore/subscriptions/callback_handlers/subscription_feeder_handlers.rbs +17 -12
  260. data/sig/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rbs +17 -6
  261. data/sig/pg_eventstore/subscriptions/event_subscription_position_queries.rbs +30 -0
  262. data/sig/pg_eventstore/subscriptions/events_processor.rbs +12 -5
  263. data/sig/pg_eventstore/subscriptions/events_processor_consumer/multiple.rbs +17 -0
  264. data/sig/pg_eventstore/subscriptions/events_processor_consumer/replica.rbs +17 -0
  265. data/sig/pg_eventstore/subscriptions/events_processor_consumer/single.rbs +17 -0
  266. data/sig/pg_eventstore/subscriptions/events_processor_consumer.rbs +14 -0
  267. data/sig/pg_eventstore/subscriptions/events_subscription_position_worker.rbs +28 -0
  268. data/sig/pg_eventstore/subscriptions/queries/subscription_queries.rbs +7 -39
  269. data/sig/pg_eventstore/subscriptions/replica_subscription_handler.rbs +33 -0
  270. data/sig/pg_eventstore/subscriptions/replica_subscription_runner.rbs +4 -0
  271. data/sig/pg_eventstore/subscriptions/subscription_feed_strategy/collection.rbs +14 -0
  272. data/sig/pg_eventstore/subscriptions/subscription_feed_strategy/index_read_strategy.rbs +23 -0
  273. data/sig/pg_eventstore/subscriptions/subscription_feed_strategy/replication_strategy.rbs +23 -0
  274. data/sig/pg_eventstore/subscriptions/subscription_feed_strategy.rbs +11 -0
  275. data/sig/pg_eventstore/subscriptions/subscription_feeder.rbs +13 -10
  276. data/sig/pg_eventstore/subscriptions/subscription_handler_performance.rbs +1 -1
  277. data/sig/pg_eventstore/subscriptions/subscription_runner.rbs +13 -2
  278. data/sig/pg_eventstore/subscriptions/subscription_runners_feeder.rbs +1 -8
  279. data/sig/pg_eventstore/subscriptions/subscriptions_lifecycle.rbs +4 -6
  280. data/sig/pg_eventstore/subscriptions/subscriptions_manager.rbs +29 -43
  281. data/sig/pg_eventstore/synchronized_array.rbs +4 -0
  282. data/sig/pg_eventstore/utils.rbs +10 -11
  283. data/sig/pg_eventstore/web/application.rbs +13 -3
  284. data/sig/pg_eventstore/web/paginator/base_collection.rbs +2 -0
  285. data/sig/pg_eventstore/web/paginator/event_types_collection.rbs +4 -0
  286. data/sig/pg_eventstore/web/paginator/events_collection.rbs +11 -11
  287. data/sig/pg_eventstore/web/paginator/helpers.rbs +6 -15
  288. data/sig/pg_eventstore/web/paginator/markers_collection.rbs +13 -0
  289. data/sig/pg_eventstore/web/paginator/stream_contexts_collection.rbs +4 -0
  290. data/sig/pg_eventstore/web/paginator/stream_names_collection.rbs +4 -0
  291. data/sig/pg_eventstore/web/paginator/streams_collection.rbs +30 -0
  292. data/sig/pg_eventstore.rbs +8 -26
  293. metadata +175 -20
  294. data/Dockerfile +0 -3
  295. data/lib/pg_eventstore/commands/all_stream_read_grouped.rb +0 -69
  296. data/lib/pg_eventstore/commands/regular_stream_read_grouped.rb +0 -31
  297. data/lib/pg_eventstore/subscriptions/queries/subscription_service_queries.rb +0 -78
  298. data/lib/pg_eventstore/web/views/home/partials/system_stream_filter.erb +0 -15
  299. data/sig/pg_eventstore/commands/all_stream_read_grouped.rbs +0 -16
  300. data/sig/pg_eventstore/commands/regular_stream_read_grouped.rbs +0 -8
  301. data/sig/pg_eventstore/subscriptions/queries/subscription_service_queries.rbs +0 -19
  302. data/sig/pg_eventstore/subscriptions/synchronized_array.rbs +0 -4
  303. /data/sig/interfaces/{_raw_event_handler.rbs → raw_event_handler.rbs} +0 -0
@@ -0,0 +1,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
@@ -46,9 +48,7 @@ module PgEventstore
46
48
  top_builder.union(builder)
47
49
  end
48
50
 
49
- connection.with do |conn|
50
- conn.exec_params(*sql_builder.to_exec_params)
51
- end.to_a
51
+ @query_strategy.exec_params(*sql_builder.to_exec_params).to_a
52
52
  end
53
53
 
54
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
@@ -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
69
  context: stream.context, stream_name: stream.stream_name, event_type:,
80
- table_name: event_type_partition_name(stream, 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)