1
2
3
4
5
6
7
8
9
10
11
12
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
120
121
122
123
124
125
126
127
128
129
130
131
132
|
# frozen_string_literal: true
class ApplicationRecord < ActiveRecord::Base
include DatabaseReflection
include Transactions
include LegacyBulkInsert
include CrossDatabaseModification
include SensitiveSerializableHash
self.abstract_class = true
# We should avoid using pluck https://docs.gitlab.com/ee/development/sql.html#plucking-ids
# but, if we are going to use it, let's try and limit the number of records
MAX_PLUCK = 1_000
alias_method :reset, :reload
def self.without_order
reorder(nil)
end
def self.id_in(ids)
where(id: ids)
end
def self.primary_key_in(values)
where(primary_key => values)
end
def self.iid_in(iids)
where(iid: iids)
end
def self.id_not_in(ids)
where.not(id: ids)
end
def self.pluck_primary_key
where(nil).pluck(primary_key)
end
def self.safe_ensure_unique(retries: 0)
transaction(requires_new: true) do # rubocop:disable Performance/ActiveRecordSubtransactions
yield
end
rescue ActiveRecord::RecordNotUnique
if retries > 0
retries -= 1
retry
end
false
end
def self.safe_find_or_create_by!(*args, &block)
safe_find_or_create_by(*args, &block).tap do |record|
raise ActiveRecord::RecordNotFound unless record.present?
record.validate! unless record.persisted?
end
end
# Start a new transaction with a shorter-than-usual statement timeout. This is
# currently one third of the default 15-second timeout with a 500ms buffer
# to allow callers gracefully handling the errors to still complete within
# the 5s target duration of a low urgency request.
def self.with_fast_read_statement_timeout(timeout_ms = 4500)
::Gitlab::Database::LoadBalancing::Session.current.fallback_to_replicas_for_ambiguous_queries do
transaction(requires_new: true) do # rubocop:disable Performance/ActiveRecordSubtransactions
connection.exec_query("SET LOCAL statement_timeout = #{timeout_ms}")
yield
end
end
end
def self.safe_find_or_create_by(*args, &block)
record = find_by(*args)
return record if record.present?
# We need to use `all.create` to make this implementation follow `find_or_create_by` which delegates this in
# https://github.com/rails/rails/blob/v6.1.3.2/activerecord/lib/active_record/querying.rb#L22
#
# When calling this method on an association, just calling `self.create` would call `ActiveRecord::Persistence.create`
# and that skips some code that adds the newly created record to the association.
transaction(requires_new: true) { all.create(*args, &block) } # rubocop:disable Performance/ActiveRecordSubtransactions
rescue ActiveRecord::RecordNotUnique
find_by(*args)
end
def create_or_load_association(association_name)
association(association_name).create unless association(association_name).loaded?
rescue ActiveRecord::RecordNotUnique, PG::UniqueViolation
association(association_name).reader
end
def self.underscore
Gitlab::SafeRequestStore.fetch("model:#{self}:underscore") { to_s.underscore }
end
def self.where_exists(query)
where('EXISTS (?)', query.select(1))
end
def self.where_not_exists(query)
where('NOT EXISTS (?)', query.select(1))
end
def self.declarative_enum(enum_mod)
enum(enum_mod.key => enum_mod.values)
end
def self.cached_column_list
column_names.map { |column_name| arel_table[column_name] }
end
def self.default_select_columns
if ignored_columns.any?
cached_column_list
else
arel_table[Arel.star]
end
end
def readable_by?(user)
Ability.allowed?(user, "read_#{to_ability_name}".to_sym, self)
end
def to_ability_name
model_name.element
end
end
|