Class: WorkerPlugins::AddQuery

Inherits:
ApplicationService show all
Defined in:
app/services/worker_plugins/add_query.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from ApplicationService

#db_now_value, #mysql?, #postgres?, #quote, #quote_column, #quote_table, #sqlite?

Constructor Details

#initialize(query:, workplace:) ⇒ AddQuery

Returns a new instance of AddQuery.



4
5
6
7
8
# File 'app/services/worker_plugins/add_query.rb', line 4

def initialize(query:, workplace:)
  @query = query
    .except(:order) # This fixes crashes in Postgres
  @workplace = workplace
end

Instance Attribute Details

#queryObject (readonly)

Returns the value of attribute query.



2
3
4
# File 'app/services/worker_plugins/add_query.rb', line 2

def query
  @query
end

#workplaceObject (readonly)

Returns the value of attribute workplace.



2
3
4
# File 'app/services/worker_plugins/add_query.rb', line 2

def workplace
  @workplace
end

Instance Method Details

#add_query_to_workplaceObject



14
15
16
# File 'app/services/worker_plugins/add_query.rb', line 14

def add_query_to_workplace
  WorkerPlugins::WorkplaceLink.connection.exec_update(sql, "WorkerPlugins::AddQuery INSERT", [])
end

#conflict_clauseObject



135
136
137
138
139
# File 'app/services/worker_plugins/add_query.rb', line 135

def conflict_clause
  return "" unless postgres?

  "ON CONFLICT (workplace_id, resource_type, resource_id) DO NOTHING"
end


58
59
60
61
62
63
64
65
66
67
# File 'app/services/worker_plugins/add_query.rb', line 58

def existing_workplace_link_exists_sql
  resource_id_column = "#{quote_table(WorkerPlugins::WorkplaceLink.table_name)}.#{quote_column(:resource_id)}"

  workplace
    .workplace_links
    .where(resource_type: model_class.name)
    .where("#{resource_id_column} = #{model_primary_key_cast_for_resource_id}")
    .select(1)
    .to_sql
end

#ids_added_alreadyObject



24
25
26
27
28
29
30
# File 'app/services/worker_plugins/add_query.rb', line 24

def ids_added_already
  WorkerPlugins::SelectColumnWithTypeCast.execute!(
    column_name_to_select: :resource_id,
    column_to_compare_with: model_class.column_for_attribute(:id),
    query: ids_added_already_query
  )
end

#ids_added_already_queryObject



18
19
20
21
22
# File 'app/services/worker_plugins/add_query.rb', line 18

def ids_added_already_query
  workplace
    .workplace_links
    .where(resource_type: model_class.name)
end

#insert_clauseObject



125
126
127
128
129
130
131
132
133
# File 'app/services/worker_plugins/add_query.rb', line 125

def insert_clause
  if mysql?
    "INSERT IGNORE"
  elsif sqlite?
    "INSERT OR IGNORE"
  else
    "INSERT"
  end
end

#model_classObject



32
33
34
# File 'app/services/worker_plugins/add_query.rb', line 32

def model_class
  @model_class ||= query.klass
end

#model_primary_key_cast_for_resource_idObject



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'app/services/worker_plugins/add_query.rb', line 69

def model_primary_key_cast_for_resource_id
  primary_key_column = "#{quote_table(model_class.table_name)}.#{quote_column(model_class.primary_key)}"

  # MySQL and SQLite do implicit conversion when comparing integer/uuid/string
  # primary keys to the `resource_id` VARCHAR column. Postgres is strict about
  # types and needs an explicit cast.
  return primary_key_column unless postgres?

  primary_key_type = model_class.column_for_attribute(model_class.primary_key).type
  resource_id_type = WorkerPlugins::WorkplaceLink.column_for_attribute(:resource_id).type

  return primary_key_column if primary_key_type == resource_id_type

  "CAST(#{primary_key_column} AS VARCHAR)"
end

#performObject



10
11
12
# File 'app/services/worker_plugins/add_query.rb', line 10

def perform
  succeed!(affected_count: add_query_to_workplace)
end

#primary_keyObject



36
37
38
# File 'app/services/worker_plugins/add_query.rb', line 36

def primary_key
  @primary_key ||= model_class.primary_key
end

#resources_to_addObject



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'app/services/worker_plugins/add_query.rb', line 40

def resources_to_add
  # The unique index on `(workplace_id, resource_type, resource_id)` lets us
  # skip the `WHERE NOT EXISTS` anti-join for the common unbounded query —
  # duplicates are rejected on INSERT by the dialect-specific conflict
  # clause in #sql. `.distinct` still handles same-row duplicates produced
  # by joins in the caller's query (e.g. `User.joins(:tasks)`).
  #
  # When the caller scopes with `.limit` / `.offset`, we keep the anti-join
  # so already-linked rows are filtered *before* the window is applied;
  # otherwise `Task.limit(100)` could insert fewer than 100 new rows when
  # some of those 100 are already linked.
  @resources_to_add ||= if query.limit_value || query.offset_value
    query.distinct.where("NOT EXISTS (#{existing_workplace_link_exists_sql})")
  else
    query.distinct
  end
end

#select_sqlObject



85
86
87
88
89
90
91
92
93
94
95
# File 'app/services/worker_plugins/add_query.rb', line 85

def select_sql
  @select_sql ||= resources_to_add
    .select("
      #{db_now_value},
      #{quote(resources_to_add.klass.name)},
      #{quote_table(resources_to_add.klass.table_name)}.#{quote_column(primary_key)},
      #{db_now_value},
      #{select_workplace_id_sql}
    ")
    .to_sql
end

#select_workplace_id_sqlObject



97
98
99
100
101
102
103
104
105
# File 'app/services/worker_plugins/add_query.rb', line 97

def select_workplace_id_sql
  workplace_id_column = WorkerPlugins::WorkplaceLink.columns.find { |column| column.name == "workplace_id" }

  if workplace_id_column.type == :uuid
    "CAST(#{quote(workplace.id)} AS UUID)"
  else
    quote(workplace.id)
  end
end

#sqlObject



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'app/services/worker_plugins/add_query.rb', line 107

def sql
  @sql ||= "
    #{insert_clause} INTO
      worker_plugins_workplace_links

    (
      created_at,
      resource_type,
      resource_id,
      updated_at,
      workplace_id
    )

    #{select_sql}
    #{conflict_clause}
  "
end