sql_helper.rb 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. class SqlHelper
  3. def self.quote_string(value)
  4. ActiveRecord::Base.connection.quote_string(value)
  5. end
  6. def self.quote_like(...)
  7. ApplicationModel.sanitize_sql_like(...)
  8. end
  9. def initialize(object:, table_name: nil)
  10. @object = object
  11. @table_name = table_name
  12. end
  13. def db_column(column)
  14. "#{ActiveRecord::Base.connection.quote_table_name(@table_name || @object.table_name)}.#{ActiveRecord::Base.connection.quote_column_name(column)}"
  15. end
  16. def json_column?(attribute)
  17. @object.column_for_attribute(attribute).type == :jsonb
  18. end
  19. def json_db_column_with_key(attribute, key)
  20. "#{db_column(attribute)}->>#{ActiveRecord::Base.connection.quote(key)}"
  21. end
  22. def get_param_key(key, params)
  23. sort_by = []
  24. if params[key].present? && params[key].is_a?(String)
  25. params[key] = params[key].split(%r{\s*,\s*})
  26. elsif params[key].blank?
  27. params[key] = []
  28. end
  29. sort_by
  30. end
  31. =begin
  32. This function will check the params for the "sort_by" attribute
  33. and validate its values.
  34. sql_helper = SqlHelper.new(object: Ticket)
  35. sort_by = sql_helper.get_sort_by(params, default)
  36. returns
  37. sort_by = [
  38. 'created_at',
  39. 'updated_at',
  40. ]
  41. =end
  42. def get_sort_by(params, default = nil)
  43. sort_by = get_param_key(:sort_by, params)
  44. # check order
  45. params[:sort_by].each do |value|
  46. # only accept values which are set for the db schema
  47. raise "Found invalid column '#{value}' for sorting." if @object.columns_hash[value].blank?
  48. sort_by.push(value)
  49. end
  50. if sort_by.blank? && default.present?
  51. if default.is_a?(Array)
  52. sort_by = default
  53. else
  54. sort_by.push(default)
  55. end
  56. end
  57. sort_by
  58. end
  59. =begin
  60. This function will check the params for the "order_by" attribute
  61. and validate its values.
  62. sql_helper = SqlHelper.new(object: Ticket)
  63. order_by = sql_helper.get_order_by(params, default)
  64. returns
  65. order_by = [
  66. 'asc',
  67. 'desc',
  68. ]
  69. =end
  70. def get_order_by(params, default = nil)
  71. order_by = get_param_key(:order_by, params)
  72. # check order
  73. params[:order_by].each do |value|
  74. raise "Found invalid order by value #{value}. Please use 'asc' or 'desc'." if !value.match?(%r{\A(asc|desc)\z}i)
  75. order_by.push(value.downcase)
  76. end
  77. if order_by.blank? && default.present?
  78. if default.is_a?(Array)
  79. order_by = default
  80. else
  81. order_by.push(default)
  82. end
  83. end
  84. order_by
  85. end
  86. def set_sql_order_default(sql, default)
  87. if sql.blank? && default.present?
  88. sql.push(db_column(default))
  89. end
  90. sql
  91. end
  92. =begin
  93. This function will use the evaluated values for sort_by and
  94. order_by to generate the ORDER-SELECT sql statement for the sorting
  95. of the result.
  96. sort_by = [ 'created_at', 'updated_at' ]
  97. order_by = [ 'asc', 'desc' ]
  98. default = 'tickets.created_at'
  99. sql_helper = SqlHelper.new(object: Ticket)
  100. sql = sql_helper.get_order_select(sort_by, order_by, default)
  101. returns
  102. sql = 'tickets.created_at, tickets.updated_at'
  103. =end
  104. def get_order_select(sort_by, order_by, default = nil)
  105. sql = []
  106. sort_by.each_with_index do |value, index|
  107. next if value.blank?
  108. next if order_by[index].blank?
  109. sql.push(db_column(value))
  110. end
  111. sql = set_sql_order_default(sql, default)
  112. sql.join(', ')
  113. end
  114. =begin
  115. This function will use the evaluated values for sort_by and
  116. order_by to generate the ORDER- sql statement for the sorting
  117. of the result.
  118. sort_by = [ 'created_at', 'updated_at' ]
  119. order_by = [ 'asc', 'desc' ]
  120. default = 'tickets.created_at DESC'
  121. sql_helper = SqlHelper.new(object: Ticket)
  122. sql = sql_helper.get_order(sort_by, order_by, default)
  123. returns
  124. sql = 'tickets.created_at ASC, tickets.updated_at DESC'
  125. =end
  126. def get_order(sort_by, order_by, default = nil)
  127. sql = []
  128. sort_by.each_with_index do |value, index|
  129. next if value.blank?
  130. next if order_by[index].blank?
  131. sql.push("#{db_column(value)} #{order_by[index]}")
  132. end
  133. sql = set_sql_order_default(sql, default)
  134. sql.join(', ')
  135. end
  136. def containable?(attribute)
  137. ObjectManager::Attribute.for_object(@object).exists?(name: attribute, data_type: %w[multiselect multi_tree_select])
  138. end
  139. def array_contains_all(attribute, value, negated: false)
  140. value = [''] if value.blank?
  141. value = Array(value)
  142. result = if Rails.application.config.db_column_array
  143. "(#{db_column(attribute)} @> ARRAY[#{value.map { |v| "'#{self.class.quote_string(v)}'" }.join(',')}]::varchar[])"
  144. else
  145. "JSON_CONTAINS(#{db_column(attribute)}, '#{self.class.quote_string(value.to_json)}', '$')"
  146. end
  147. negated ? "NOT(#{result})" : "(#{result})"
  148. end
  149. def array_contains_one(attribute, value, negated: false)
  150. value = [''] if value.blank?
  151. value = Array(value)
  152. result = if Rails.application.config.db_column_array
  153. "(#{db_column(attribute)} && ARRAY[#{value.map { |v| "'#{self.class.quote_string(v)}'" }.join(',')}]::varchar[])"
  154. else
  155. value.map { |v| "JSON_CONTAINS(#{db_column(attribute)}, '#{self.class.quote_string(v.to_json)}', '$')" }.join(' OR ')
  156. end
  157. negated ? "NOT(#{result})" : "(#{result})"
  158. end
  159. def regex_match(attribute, negated: false)
  160. operator = if mysql?
  161. negated ? 'NOT REGEXP' : 'REGEXP'
  162. else
  163. negated ? '!~*' : '~*'
  164. end
  165. "#{attribute} #{operator} (?)"
  166. end
  167. private
  168. def mysql?
  169. ActiveRecord::Base.connection_db_config.configuration_hash[:adapter] == 'mysql2'
  170. end
  171. end