geneva_drive 0.6.0 → 0.8.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -1
  3. data/MANUAL.md +260 -1
  4. data/README.md +2 -0
  5. data/Rakefile +14 -1
  6. data/bin/matrix +76 -0
  7. data/bin/md2html +114 -0
  8. data/lib/generators/geneva_drive/install/install_generator.rb +5 -0
  9. data/lib/generators/geneva_drive/install/templates/add_resumable_step_support.rb +5 -9
  10. data/lib/generators/geneva_drive/install/templates/add_signals_support.rb +89 -0
  11. data/lib/geneva_drive/executor.rb +131 -10
  12. data/lib/geneva_drive/flow_control.rb +43 -3
  13. data/lib/geneva_drive/jobs/housekeeping_job.rb +83 -3
  14. data/lib/geneva_drive/json_column.rb +72 -0
  15. data/lib/geneva_drive/migration_helpers.rb +27 -0
  16. data/lib/geneva_drive/removed_step_definition.rb +34 -0
  17. data/lib/geneva_drive/signal.rb +245 -0
  18. data/lib/geneva_drive/signal_matcher.rb +60 -0
  19. data/lib/geneva_drive/step_collection.rb +31 -7
  20. data/lib/geneva_drive/step_definition.rb +91 -1
  21. data/lib/geneva_drive/step_execution.rb +39 -2
  22. data/lib/geneva_drive/test_helpers.rb +89 -2
  23. data/lib/geneva_drive/version.rb +1 -1
  24. data/lib/geneva_drive/workflow.rb +497 -24
  25. data/lib/geneva_drive.rb +24 -0
  26. data/test/dsl/signal_step_definition_test.rb +132 -0
  27. data/test/dummy/config/database.yml +40 -5
  28. data/test/dummy/config/initializers/geneva_drive.rb +46 -0
  29. data/test/dummy_install/db/migrate/20260217112949_create_geneva_drive_workflows.rb +76 -0
  30. data/test/dummy_install/db/migrate/20260217112950_create_geneva_drive_step_executions.rb +99 -0
  31. data/test/dummy_install/db/migrate/20260217112951_add_finished_at_to_geneva_drive_step_executions.rb +27 -0
  32. data/test/dummy_install/db/migrate/20260217112952_add_error_class_name_to_geneva_drive_step_executions.rb +9 -0
  33. data/test/dummy_install/db/migrate/20260217112953_add_resumable_step_support_to_geneva_drive_step_executions.rb +128 -0
  34. data/test/dummy_install/db/migrate/20260217112954_add_chained_step_executions_to_geneva_drive_step_executions.rb +136 -0
  35. data/test/dummy_install/db/schema.rb +75 -0
  36. data/test/dummy_install/log/test.log +1852 -0
  37. data/test/jobs/housekeeping_signals_test.rb +198 -0
  38. data/test/migration_helpers_test.rb +1 -1
  39. data/test/test_helper.rb +53 -13
  40. data/test/workflow/removed_step_test.rb +294 -0
  41. data/test/workflow/resume_at_test.rb +170 -0
  42. data/test/workflow/signal_consumption_test.rb +397 -0
  43. data/test/workflow/signal_delivery_test.rb +255 -0
  44. data/test/workflow/signal_durability_test.rb +164 -0
  45. data/test/workflow/signal_rendezvous_test.rb +581 -0
  46. data/test/workflow/signals_without_migration_test.rb +97 -0
  47. metadata +26 -2
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ # license: LGPLv3.0
4
+
5
+ class AddChainedStepExecutionsToGenevaDriveStepExecutions < ActiveRecord::Migration[7.2]
6
+ include GenevaDrive::MigrationHelpers
7
+
8
+ def change
9
+ # Add continues_from_id to link chained step executions.
10
+ # Match the primary key type (bigint or uuid) of the step_executions table.
11
+ # No foreign key constraint — SQLite rewrites the table on add_foreign_key,
12
+ # which can destroy data.
13
+ add_column :geneva_drive_step_executions, :continues_from_id, geneva_drive_key_type
14
+ add_index :geneva_drive_step_executions, :continues_from_id
15
+
16
+ # Remove completed_iterations - it's unnecessary bookkeeping
17
+ remove_column :geneva_drive_step_executions, :completed_iterations, :integer
18
+
19
+ # Remove 'suspended' state - executions now complete and spawn successors
20
+ reversible do |direction|
21
+ direction.up do
22
+ # Convert any suspended executions to completed
23
+ execute <<-SQL
24
+ UPDATE geneva_drive_step_executions
25
+ SET state = 'completed'
26
+ WHERE state = 'suspended';
27
+ SQL
28
+
29
+ # Update unique constraint: only scheduled and in_progress are active
30
+ update_unique_index_without_suspended
31
+ end
32
+
33
+ direction.down do
34
+ restore_suspended_unique_index
35
+ end
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def update_unique_index_without_suspended
42
+ adapter = connection.adapter_name.downcase
43
+
44
+ if adapter.include?("postgresql")
45
+ execute <<-SQL
46
+ DROP INDEX IF EXISTS index_geneva_drive_step_executions_one_active;
47
+ SQL
48
+ execute <<-SQL
49
+ CREATE UNIQUE INDEX index_geneva_drive_step_executions_one_active
50
+ ON geneva_drive_step_executions (workflow_id)
51
+ WHERE state IN ('scheduled', 'in_progress');
52
+ SQL
53
+ elsif adapter.include?("mysql")
54
+ execute <<-SQL
55
+ DROP INDEX index_geneva_drive_step_executions_one_active
56
+ ON geneva_drive_step_executions;
57
+ SQL
58
+ execute <<-SQL
59
+ ALTER TABLE geneva_drive_step_executions
60
+ DROP COLUMN active_unique_key;
61
+ SQL
62
+ execute <<-SQL
63
+ ALTER TABLE geneva_drive_step_executions
64
+ ADD COLUMN active_unique_key VARCHAR(767)
65
+ AS (
66
+ CASE
67
+ WHEN state IN ('scheduled', 'in_progress')
68
+ THEN CAST(workflow_id AS CHAR)
69
+ ELSE NULL
70
+ END
71
+ ) STORED;
72
+ SQL
73
+ execute <<-SQL
74
+ CREATE UNIQUE INDEX index_geneva_drive_step_executions_one_active
75
+ ON geneva_drive_step_executions (active_unique_key);
76
+ SQL
77
+ elsif adapter.include?("sqlite")
78
+ execute <<-SQL
79
+ DROP INDEX IF EXISTS index_geneva_drive_step_executions_one_active;
80
+ SQL
81
+ execute <<-SQL
82
+ CREATE UNIQUE INDEX index_geneva_drive_step_executions_one_active
83
+ ON geneva_drive_step_executions (workflow_id)
84
+ WHERE state IN ('scheduled', 'in_progress');
85
+ SQL
86
+ end
87
+ end
88
+
89
+ def restore_suspended_unique_index
90
+ adapter = connection.adapter_name.downcase
91
+
92
+ if adapter.include?("postgresql")
93
+ execute <<-SQL
94
+ DROP INDEX IF EXISTS index_geneva_drive_step_executions_one_active;
95
+ SQL
96
+ execute <<-SQL
97
+ CREATE UNIQUE INDEX index_geneva_drive_step_executions_one_active
98
+ ON geneva_drive_step_executions (workflow_id)
99
+ WHERE state IN ('scheduled', 'in_progress', 'suspended');
100
+ SQL
101
+ elsif adapter.include?("mysql")
102
+ execute <<-SQL
103
+ DROP INDEX index_geneva_drive_step_executions_one_active
104
+ ON geneva_drive_step_executions;
105
+ SQL
106
+ execute <<-SQL
107
+ ALTER TABLE geneva_drive_step_executions
108
+ DROP COLUMN active_unique_key;
109
+ SQL
110
+ execute <<-SQL
111
+ ALTER TABLE geneva_drive_step_executions
112
+ ADD COLUMN active_unique_key VARCHAR(767)
113
+ AS (
114
+ CASE
115
+ WHEN state IN ('scheduled', 'in_progress', 'suspended')
116
+ THEN CAST(workflow_id AS CHAR)
117
+ ELSE NULL
118
+ END
119
+ ) STORED;
120
+ SQL
121
+ execute <<-SQL
122
+ CREATE UNIQUE INDEX index_geneva_drive_step_executions_one_active
123
+ ON geneva_drive_step_executions (active_unique_key);
124
+ SQL
125
+ elsif adapter.include?("sqlite")
126
+ execute <<-SQL
127
+ DROP INDEX IF EXISTS index_geneva_drive_step_executions_one_active;
128
+ SQL
129
+ execute <<-SQL
130
+ CREATE UNIQUE INDEX index_geneva_drive_step_executions_one_active
131
+ ON geneva_drive_step_executions (workflow_id)
132
+ WHERE state IN ('scheduled', 'in_progress', 'suspended');
133
+ SQL
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,75 @@
1
+ # This file is auto-generated from the current state of the database. Instead
2
+ # of editing this file, please use the migrations feature of Active Record to
3
+ # incrementally modify your database, and then regenerate this schema definition.
4
+ #
5
+ # This file is the source Rails uses to define your schema when running `bin/rails
6
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7
+ # be faster and is potentially less error prone than running all of your
8
+ # migrations from scratch. Old migrations may fail to apply correctly if those
9
+ # migrations use external dependencies or application code.
10
+ #
11
+ # It's strongly recommended that you check this file into your version control system.
12
+
13
+ ActiveRecord::Schema[8.1].define(version: 2026_02_17_112954) do
14
+ # These are extensions that must be enabled in order to support this database
15
+ enable_extension "pg_catalog.plpgsql"
16
+
17
+ create_table "geneva_drive_step_executions", force: :cascade do |t|
18
+ t.datetime "canceled_at"
19
+ t.datetime "completed_at"
20
+ t.bigint "continues_from_id"
21
+ t.datetime "created_at", null: false
22
+ t.jsonb "cursor"
23
+ t.text "error_backtrace"
24
+ t.string "error_class_name"
25
+ t.text "error_message"
26
+ t.datetime "failed_at"
27
+ t.datetime "finished_at"
28
+ t.string "job_id"
29
+ t.string "outcome"
30
+ t.datetime "scheduled_for", null: false
31
+ t.datetime "skipped_at"
32
+ t.datetime "started_at"
33
+ t.string "state", default: "scheduled", null: false
34
+ t.string "step_name", null: false
35
+ t.datetime "updated_at", null: false
36
+ t.bigint "workflow_id", null: false
37
+ t.index ["continues_from_id"], name: "index_geneva_drive_step_executions_on_continues_from_id"
38
+ t.index ["finished_at"], name: "index_geneva_drive_step_executions_on_finished_at"
39
+ t.index ["scheduled_for"], name: "index_geneva_drive_step_executions_on_scheduled_for"
40
+ t.index ["state", "scheduled_for"], name: "index_geneva_drive_step_executions_scheduled"
41
+ t.index ["state"], name: "index_geneva_drive_step_executions_on_state"
42
+ t.index ["workflow_id", "created_at"], name: "idx_on_workflow_id_created_at_af16a14fb2"
43
+ t.index ["workflow_id", "state"], name: "index_geneva_drive_step_executions_on_workflow_id_and_state"
44
+ t.index ["workflow_id"], name: "index_geneva_drive_step_executions_on_workflow_id"
45
+ t.index ["workflow_id"], name: "index_geneva_drive_step_executions_one_active", unique: true, where: "((state)::text = ANY ((ARRAY['scheduled'::character varying, 'in_progress'::character varying])::text[]))"
46
+ end
47
+
48
+ create_table "geneva_drive_workflows", force: :cascade do |t|
49
+ t.boolean "allow_multiple", default: false, null: false
50
+ t.datetime "created_at", null: false
51
+ t.string "current_step_name"
52
+ t.bigint "hero_id", null: false
53
+ t.string "hero_type", null: false
54
+ t.string "next_step_name"
55
+ t.datetime "started_at"
56
+ t.string "state", default: "ready", null: false
57
+ t.datetime "transitioned_at"
58
+ t.string "type", null: false
59
+ t.datetime "updated_at", null: false
60
+ t.index ["hero_type", "hero_id"], name: "index_geneva_drive_workflows_on_hero_type_and_hero_id"
61
+ t.index ["state"], name: "index_geneva_drive_workflows_on_state"
62
+ t.index ["type", "hero_type", "hero_id"], name: "index_geneva_drive_workflows_unique_ongoing", unique: true, where: "(((state)::text <> ALL ((ARRAY['finished'::character varying, 'canceled'::character varying])::text[])) AND (allow_multiple = false))"
63
+ t.index ["type"], name: "index_geneva_drive_workflows_on_type"
64
+ end
65
+
66
+ create_table "users", force: :cascade do |t|
67
+ t.boolean "active", default: true
68
+ t.datetime "created_at", null: false
69
+ t.string "email", null: false
70
+ t.string "name"
71
+ t.datetime "updated_at", null: false
72
+ end
73
+
74
+ add_foreign_key "geneva_drive_step_executions", "geneva_drive_workflows", column: "workflow_id", on_delete: :cascade
75
+ end