ductwork 1.2.0 → 1.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/lib/ductwork/context.rb +1 -1
- data/lib/ductwork/database_clock.rb +5 -7
- data/lib/ductwork/execution_claim.rb +1 -1
- data/lib/ductwork/migration_helper.rb +96 -12
- data/lib/ductwork/models/record.rb +4 -0
- data/lib/ductwork/models/run.rb +1 -1
- data/lib/ductwork/processes/process_supervisor.rb +19 -13
- data/lib/ductwork/version.rb +1 -1
- data/lib/generators/ductwork/update/update_generator.rb +107 -107
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 94c5c2250e4a96fee3aaeac379ec1ee58ac85e6aa5c2f0ccffc67c9e66d4499d
|
|
4
|
+
data.tar.gz: ac6aac4135803b6d0712db4b4491202b64280a506a3101baba6563f549e5ee9d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d7305c2c9a26693cfa92dce562016420aafe4d745cb3efb4450b5192a8c036d7db5d96023b71979bd15c817059c2f8f2933f974a441066ec891cb2132d802c99
|
|
7
|
+
data.tar.gz: 54dfdadfcafe3e1e4a214f5d11599e87bc3b114387702b426aad5c146ec7ca2042c42e0fa001cd0d598e11b515a342f8f01d5ac3b96a4f6b982d6579f93b50a7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Ductwork Changelog
|
|
2
2
|
|
|
3
|
+
## [1.3.0]
|
|
4
|
+
|
|
5
|
+
- feat: add more `MigrationHelper` methods for swapping and updating the availability claim index
|
|
6
|
+
- fix: resolve the database adapter from the configuration (`Ductwork::Record.adapter`) instead of `Ductwork::Record.connection.adapter_name`, and take the database clock's reading through `connection_pool.with_connection` - `ActiveRecord::Base#connection` is soft deprecated and takes a *sticky* lease, so `ConnectionPool#with_connection` skips its own checkin (it releases only `unless lease.sticky`) and every advancer or worker thread that touched the claim hot path pinned a pool slot for its lifetime; the Rails executor checked those back in per unit of work so long-running processes did not leak, but a host app setting `config.active_record.permanent_connection_checkout = :disallowed` would have raised, and `:deprecated` emitted a warning per claim
|
|
7
|
+
- fix: `ExecutionClaim` now selects row locking on Oracle as it always intended - the predicate tests `/oracle_enhanced/i` but was matched against `connection.adapter_name.downcase`, which renders as `oracleenhanced` with no underscore, so Oracle silently fell through to optimistic locking; it is matched against the configured adapter (`oracle_enhanced`) now. Oracle is not covered by CI
|
|
8
|
+
- fix: make `Ductwork::Processes::ProcessSupervisor#shutdown` idempotent and serialize it on a mutex - `run` calls `shutdown` itself once its loop sees the running context go down, so a caller that stops the supervisor from another thread raced the supervisor's own call and both ran `reap_own_process_record!` at the same time
|
|
9
|
+
|
|
3
10
|
## [1.2.0]
|
|
4
11
|
|
|
5
12
|
- feat: add `add_availability_claim_index` and `remove_availability_claim_index` to `Ductwork::MigrationHelper`, which own the adapter-specific shape and the name of the availability claim index
|
data/lib/ductwork/context.rb
CHANGED
|
@@ -26,7 +26,7 @@ module Ductwork
|
|
|
26
26
|
first_set_at: Time.current,
|
|
27
27
|
last_set_at: Time.current,
|
|
28
28
|
}
|
|
29
|
-
opts = if Ductwork::Tuple.
|
|
29
|
+
opts = if Ductwork::Tuple.adapter.match?(/mysql|trilogy/i)
|
|
30
30
|
{}
|
|
31
31
|
else
|
|
32
32
|
{ unique_by: %i[run_id key] }
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Ductwork
|
|
4
|
-
# NOTE: these are SQL fragments that resolve against the database server's
|
|
5
|
-
# clock instead of the calling Ruby process's clock. use them in `WHERE`
|
|
6
|
-
# clauses that compare between a stored timestamp and "now" so that NTP
|
|
7
|
-
# drift between hosts cannot make healthy work look stale (or vice versa)
|
|
8
4
|
class DatabaseClock
|
|
9
5
|
def self.ago_sql(column, interval)
|
|
10
6
|
new(column).ago_sql(interval)
|
|
@@ -15,7 +11,7 @@ module Ductwork
|
|
|
15
11
|
end
|
|
16
12
|
|
|
17
13
|
def self.now
|
|
18
|
-
adapter = Ductwork::Record.
|
|
14
|
+
adapter = Ductwork::Record.adapter
|
|
19
15
|
sql =
|
|
20
16
|
case adapter
|
|
21
17
|
when /postgresql|cockroach/i
|
|
@@ -29,7 +25,9 @@ module Ductwork
|
|
|
29
25
|
else
|
|
30
26
|
raise NotImplementedError, "Database clock does not support adapter #{adapter}"
|
|
31
27
|
end
|
|
32
|
-
raw = Ductwork::Record.
|
|
28
|
+
raw = Ductwork::Record.connection_pool.with_connection do |conn|
|
|
29
|
+
conn.select_value(sql)
|
|
30
|
+
end
|
|
33
31
|
|
|
34
32
|
case raw
|
|
35
33
|
when ::Time, ::DateTime
|
|
@@ -41,7 +39,7 @@ module Ductwork
|
|
|
41
39
|
end
|
|
42
40
|
|
|
43
41
|
def initialize(column)
|
|
44
|
-
@adapter = Ductwork::Record.
|
|
42
|
+
@adapter = Ductwork::Record.adapter
|
|
45
43
|
@column = column
|
|
46
44
|
end
|
|
47
45
|
|
|
@@ -2,29 +2,113 @@
|
|
|
2
2
|
|
|
3
3
|
module Ductwork
|
|
4
4
|
module MigrationHelper
|
|
5
|
+
class OpenTransactionError < StandardError; end
|
|
6
|
+
|
|
5
7
|
AVAILABILITY_CLAIM_INDEX_NAME = "index_ductwork_availabilities_on_claim_latest"
|
|
8
|
+
AVAILABILITY_CLAIM_INDEX_NAME_V2 = "index_ductwork_availabilities_on_claim_latest_v2"
|
|
9
|
+
AVAILABILITY_CLAIM_INDEX_NAMES = [
|
|
10
|
+
AVAILABILITY_CLAIM_INDEX_NAME,
|
|
11
|
+
AVAILABILITY_CLAIM_INDEX_NAME_V2,
|
|
12
|
+
].freeze
|
|
6
13
|
|
|
7
|
-
def add_availability_claim_index(
|
|
14
|
+
def add_availability_claim_index(
|
|
15
|
+
extra_columns: [],
|
|
16
|
+
where_extra: nil,
|
|
17
|
+
name: AVAILABILITY_CLAIM_INDEX_NAME,
|
|
18
|
+
online: false
|
|
19
|
+
)
|
|
8
20
|
if mysql?
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
name: AVAILABILITY_CLAIM_INDEX_NAME
|
|
21
|
+
columns = [:pipeline_klass, :completed_at, *extra_columns, :started_at]
|
|
22
|
+
options = { name: }
|
|
12
23
|
else
|
|
13
|
-
|
|
24
|
+
columns = %i[pipeline_klass started_at]
|
|
25
|
+
options = {
|
|
26
|
+
name: name,
|
|
27
|
+
where: ["completed_at IS NULL", where_extra].compact.join(" AND "),
|
|
28
|
+
}
|
|
29
|
+
end
|
|
14
30
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
31
|
+
if online
|
|
32
|
+
add_index_online :ductwork_availabilities, columns, **options
|
|
33
|
+
else
|
|
34
|
+
add_index :ductwork_availabilities, columns, **options
|
|
19
35
|
end
|
|
20
36
|
end
|
|
21
37
|
|
|
22
|
-
def remove_availability_claim_index
|
|
23
|
-
remove_index :ductwork_availabilities,
|
|
24
|
-
|
|
38
|
+
def remove_availability_claim_index(name: AVAILABILITY_CLAIM_INDEX_NAME)
|
|
39
|
+
remove_index :ductwork_availabilities, name: name, if_exists: true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def swap_availability_claim_index(to:, extra_columns: [], where_extra: nil)
|
|
43
|
+
unless AVAILABILITY_CLAIM_INDEX_NAMES.include?(to)
|
|
44
|
+
raise ArgumentError, "unrecognized availability claim index name: #{to}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
add_availability_claim_index(
|
|
48
|
+
extra_columns: extra_columns,
|
|
49
|
+
where_extra: where_extra,
|
|
50
|
+
name: to,
|
|
51
|
+
online: true
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
(AVAILABILITY_CLAIM_INDEX_NAMES - [to]).each do |stale|
|
|
55
|
+
remove_index_online :ductwork_availabilities, name: stale
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def add_index_online(table_name, column_names, **options)
|
|
60
|
+
ensure_ddl_transaction_disabled!
|
|
61
|
+
|
|
62
|
+
name = options[:name] || connection.index_name(table_name, column_names)
|
|
63
|
+
|
|
64
|
+
drop_invalid_index(table_name, name)
|
|
65
|
+
|
|
66
|
+
return if connection.index_name_exists?(table_name, name)
|
|
67
|
+
|
|
68
|
+
full_options = options.merge(name:)
|
|
69
|
+
full_options[:algorithm] = :concurrently if postgresql?
|
|
70
|
+
|
|
71
|
+
add_index table_name, column_names, **full_options
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def remove_index_online(table_name, name:)
|
|
75
|
+
ensure_ddl_transaction_disabled!
|
|
76
|
+
|
|
77
|
+
return unless connection.index_name_exists?(table_name, name)
|
|
78
|
+
|
|
79
|
+
options = { name: name, if_exists: true }
|
|
80
|
+
options[:algorithm] = :concurrently if postgresql?
|
|
81
|
+
|
|
82
|
+
remove_index table_name, **options
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def drop_invalid_index(table_name, index_name)
|
|
86
|
+
return unless postgresql?
|
|
87
|
+
|
|
88
|
+
invalid = connection.select_value(<<~SQL.squish)
|
|
89
|
+
SELECT 1 FROM pg_index
|
|
90
|
+
WHERE indexrelid = to_regclass(#{connection.quote(index_name)})
|
|
91
|
+
AND NOT indisvalid
|
|
92
|
+
SQL
|
|
93
|
+
|
|
94
|
+
return if invalid.blank?
|
|
95
|
+
|
|
96
|
+
remove_index table_name,
|
|
97
|
+
name: index_name,
|
|
98
|
+
algorithm: :concurrently,
|
|
25
99
|
if_exists: true
|
|
26
100
|
end
|
|
27
101
|
|
|
102
|
+
def ensure_ddl_transaction_disabled!
|
|
103
|
+
return unless postgresql?
|
|
104
|
+
return unless connection.transaction_open?
|
|
105
|
+
|
|
106
|
+
raise OpenTransactionError, <<~MESSAGE.squish
|
|
107
|
+
online index changes cannot run inside a transaction on PostgreSQL;
|
|
108
|
+
declare `disable_ddl_transaction!` in the migration
|
|
109
|
+
MESSAGE
|
|
110
|
+
end
|
|
111
|
+
|
|
28
112
|
def create_ductwork_table(table_name, &block)
|
|
29
113
|
if postgresql?
|
|
30
114
|
create_table table_name, id: :uuid, &block
|
data/lib/ductwork/models/run.rb
CHANGED
|
@@ -118,7 +118,7 @@ module Ductwork
|
|
|
118
118
|
# relies on the advancer retrying a deadlock victim; SQLite has no row-level
|
|
119
119
|
# locks, so `lock!` is a no-op there and the deadlock cannot occur.
|
|
120
120
|
def lock_for_terminal_resolution!
|
|
121
|
-
if Ductwork::Record.
|
|
121
|
+
if Ductwork::Record.adapter.match?(/postgresql|cockroach/)
|
|
122
122
|
lock!("FOR NO KEY UPDATE")
|
|
123
123
|
else
|
|
124
124
|
lock!
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
module Ductwork
|
|
4
4
|
module Processes
|
|
5
5
|
class ProcessSupervisor # rubocop:todo Metrics/ClassLength
|
|
6
|
-
# Upper bound on how long sig_kill_process will block the supervisor
|
|
7
|
-
# loop waiting for a KILL'd child to be reaped (see sig_kill_process).
|
|
8
6
|
KILL_REAP_TIMEOUT = 5 # seconds
|
|
9
7
|
KILL_REAP_INTERVAL = 0.1
|
|
10
8
|
|
|
@@ -13,6 +11,8 @@ module Ductwork
|
|
|
13
11
|
def initialize
|
|
14
12
|
@running_context = Ductwork::RunningContext.new
|
|
15
13
|
@workers = []
|
|
14
|
+
@shutdown_mutex = Mutex.new
|
|
15
|
+
@shutdown_complete = false
|
|
16
16
|
|
|
17
17
|
run_hooks_for(:start)
|
|
18
18
|
|
|
@@ -53,22 +53,28 @@ module Ductwork
|
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def shutdown
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
shutdown_mutex.synchronize do
|
|
57
|
+
next if shutdown_complete
|
|
58
|
+
|
|
59
|
+
running_context.shutdown!
|
|
60
|
+
Ductwork.logger.debug(
|
|
61
|
+
msg: "Beginning shutdown",
|
|
62
|
+
role: :process_supervisor
|
|
63
|
+
)
|
|
61
64
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
terminate_gracefully
|
|
66
|
+
wait_for_workers_to_exit
|
|
67
|
+
terminate_immediately
|
|
68
|
+
reap_own_process_record!
|
|
69
|
+
run_hooks_for(:stop)
|
|
70
|
+
|
|
71
|
+
@shutdown_complete = true
|
|
72
|
+
end
|
|
67
73
|
end
|
|
68
74
|
|
|
69
75
|
private
|
|
70
76
|
|
|
71
|
-
attr_reader :running_context
|
|
77
|
+
attr_reader :running_context, :shutdown_mutex, :shutdown_complete
|
|
72
78
|
|
|
73
79
|
def adopt_or_create_process!
|
|
74
80
|
Ductwork.wrap_with_app_executor do
|
data/lib/ductwork/version.rb
CHANGED
|
@@ -10,113 +10,113 @@ module Ductwork
|
|
|
10
10
|
source_root File.expand_path("templates", __dir__)
|
|
11
11
|
|
|
12
12
|
def create_files
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
13
|
+
Ductwork::Record.connection_pool.with_connection do |connection|
|
|
14
|
+
if Ductwork::Availability.column_names.exclude?("pipeline_klass")
|
|
15
|
+
migration_template "db/denormalize_pipeline_klass_on_availabilities.rb",
|
|
16
|
+
"db/migrate/denormalize_pipeline_klass_on_availabilities.rb"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
if Ductwork::Pipeline.column_for_attribute("id").type != :uuid
|
|
20
|
+
migration_template "db/migrate_tables_to_uuid_primary_key.rb",
|
|
21
|
+
"db/migrate/migrate_tables_to_uuid_primary_key.rb"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
if !connection.table_exists?(:ductwork_branches)
|
|
25
|
+
migration_template "db/create_ductwork_branches.rb",
|
|
26
|
+
"db/migrate/create_ductwork_branches.rb"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
if !connection.table_exists?(:ductwork_branch_links)
|
|
30
|
+
migration_template "db/create_ductwork_branch_links.rb",
|
|
31
|
+
"db/migrate/create_ductwork_branch_links.rb"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
if Ductwork::Step.column_names.exclude?("branch_id")
|
|
35
|
+
migration_template "db/associate_steps_to_branches.rb",
|
|
36
|
+
"db/migrate/associate_steps_to_branches.rb"
|
|
37
|
+
migration_template "db/backfill_branch_ids_on_steps.rb",
|
|
38
|
+
"db/migrate/backfill_branch_ids_on_steps.rb"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
if !connection.table_exists?(:ductwork_transitions)
|
|
42
|
+
migration_template "db/create_ductwork_transitions.rb",
|
|
43
|
+
"db/migrate/create_ductwork_transitions.rb"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
if !connection.table_exists?(:ductwork_advancements)
|
|
47
|
+
migration_template "db/create_ductwork_advancements.rb",
|
|
48
|
+
"db/migrate/create_ductwork_advancements.rb"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
if Ductwork::Execution.column_names.include?("process_id")
|
|
52
|
+
migration_template "db/update_process_associations.rb",
|
|
53
|
+
"db/migrate/update_process_associations.rb"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
if connection.table_exists?(:ductwork_runs)
|
|
57
|
+
migration_template "db/rename_runs_to_attempts.rb",
|
|
58
|
+
"db/migrate/rename_runs_to_attempts.rb"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
if !connection.table_exists?(:ductwork_runs)
|
|
62
|
+
migration_template "db/create_ductwork_runs.rb",
|
|
63
|
+
"db/migrate/create_ductwork_runs.rb"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
if Ductwork::Branch.column_names.include?("pipeline_id")
|
|
67
|
+
migration_template "db/associate_branches_to_runs.rb",
|
|
68
|
+
"db/migrate/associate_branches_to_runs.rb"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
if Ductwork::Step.column_names.include?("pipeline_id")
|
|
72
|
+
migration_template "db/associate_steps_to_runs.rb",
|
|
73
|
+
"db/migrate/associate_steps_to_runs.rb"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
if Ductwork::Tuple.column_names.include?("pipeline_id")
|
|
77
|
+
migration_template "db/associate_tuples_to_runs.rb",
|
|
78
|
+
"db/migrate/associate_tuples_to_runs.rb"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if Ductwork::Execution.column_names.exclude?("crash_count")
|
|
82
|
+
migration_template "db/add_crash_count_to_ductwork_executions.rb",
|
|
83
|
+
"db/migrate/add_crash_count_to_ductwork_executions.rb"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
if Ductwork::Advancement.column_names.exclude?("crash_count")
|
|
87
|
+
migration_template "db/add_crash_count_to_ductwork_advancements.rb",
|
|
88
|
+
"db/migrate/add_crash_count_to_ductwork_advancements.rb"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
if Ductwork::Execution.column_names.exclude?("process_id")
|
|
92
|
+
migration_template "db/add_process_id_to_ductwork_executions.rb",
|
|
93
|
+
"db/migrate/add_process_id_to_ductwork_executions.rb"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
if Ductwork::Process.column_names.exclude?("role")
|
|
97
|
+
migration_template "db/add_role_to_ductwork_processes.rb",
|
|
98
|
+
"db/migrate/add_role_to_ductwork_processes.rb"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
if !connection.index_exists?(:ductwork_results, %i[result_type created_at])
|
|
102
|
+
migration_template "db/add_indexes_to_ductwork_results.rb",
|
|
103
|
+
"db/migrate/add_indexes_to_ductwork_results.rb"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
if !connection.index_exists?(:ductwork_runs, :started_at)
|
|
107
|
+
migration_template "db/add_indexes_to_ductwork_runs.rb",
|
|
108
|
+
"db/migrate/add_indexes_to_ductwork_runs.rb"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
if !connection.index_exists?(:ductwork_runs, %i[pipeline_id started_at])
|
|
112
|
+
migration_template "db/add_pipeline_started_index_to_ductwork_runs.rb",
|
|
113
|
+
"db/migrate/add_pipeline_started_index_to_ductwork_runs.rb"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
if !connection.index_exists?(:ductwork_transitions, name: "index_ductwork_transitions_on_latest_open")
|
|
117
|
+
migration_template "db/add_indexes_to_ductwork_transitions.rb",
|
|
118
|
+
"db/migrate/add_indexes_to_ductwork_transitions.rb"
|
|
119
|
+
end
|
|
120
120
|
end
|
|
121
121
|
end
|
|
122
122
|
end
|