Module: Motor::Queries::RunQuery
- Defined in:
- lib/motor/queries/run_query.rb
Defined Under Namespace
Classes: QueryResult
Constant Summary collapse
- DEFAULT_LIMIT =
100_000
- CTE_NAME =
'__query__'
- WITH_STATEMENT_START =
"WITH #{CTE_NAME} AS ("
- WITH_STATEMENT_TEMPLATE =
<<~SQL #{WITH_STATEMENT_START}%<sql_body>s ) SQL
- STATEMENT_VARIABLE_REGEXP =
/\$\d+/.freeze
- PG_ERROR_REGEXP =
/\APG.+ERROR:/.freeze
- RESERVED_VARIABLES =
%w[current_user_id current_user_email].freeze
Class Method Summary collapse
- .build_columns_hash(result) ⇒ Hash
- .build_cte_select_sql(connection, limit, filters) ⇒ String
- .build_error_message(exception) ⇒ String
- .build_filters_arel(filters) ⇒ Arel::Nodes?
- .build_statement_attributes(variables) ⇒ Array<ActiveRecord::Relation::QueryAttribute>
- .call(query, variables_hash: nil, limit: nil, filters: nil) ⇒ Motor::Queries::RunQuery::QueryResult
- .call!(query, variables_hash: nil, limit: nil, filters: nil) ⇒ Motor::Queries::RunQuery::QueryResult
- .connection_class ⇒ Object
- .execute_query(query, limit, variables_hash, filters) ⇒ ActiveRecord::Result
- .fetch_column_type_from_value(value) ⇒ String
- .merge_variable_default_values(variable_configs, variables_hash) ⇒ Hash
- .normalize_statement_for_sql(statement) ⇒ Array
- .prepare_sql_statement(connection, query, limit, variables_hash, filters) ⇒ Array
Class Method Details
.build_columns_hash(result) ⇒ Hash
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 |
# File 'lib/motor/queries/run_query.rb', line 85 def build_columns_hash(result) result.columns.map.with_index do |column_name, index| column_type_class = result.column_types[column_name] column_type = ActiveRecordUtils::Types.find_name_for_type(column_type_class) if column_type_class column_type ||= begin not_nil_value = result.rows.reduce(nil) do |acc, row| column = row[index] break column unless column.nil? acc end fetch_column_type_from_value(not_nil_value) end { name: column_name, display_name: column_name.humanize, column_type: column_type, is_array: column_type.class.to_s == 'ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array' } end end |
.build_cte_select_sql(connection, limit, filters) ⇒ String
153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/motor/queries/run_query.rb', line 153 def build_cte_select_sql(connection, limit, filters) table = Arel::Table.new(CTE_NAME) arel_filters = build_filters_arel(filters) expresion = table.project(table[Arel.star]) expresion = expresion.where(arel_filters) if arel_filters.present? expresion.take(limit.to_i) unless connection.class.name.include?('SQLServerAdapter') expresion.to_sql.delete('"') end |
.build_error_message(exception) ⇒ String
53 54 55 |
# File 'lib/motor/queries/run_query.rb', line 53 def (exception) exception..sub(WITH_STATEMENT_START, '').sub(PG_ERROR_REGEXP, '').strip.upcase_first end |
.build_filters_arel(filters) ⇒ Arel::Nodes?
168 169 170 171 172 173 174 175 176 |
# File 'lib/motor/queries/run_query.rb', line 168 def build_filters_arel(filters) return nil if filters.blank? table = Arel::Table.new(CTE_NAME) arel_filters = filters.map { |key, value| table[key].in(value) } arel_filters[1..].reduce(arel_filters.first) { |acc, arel| acc.and(arel) } end |
.build_statement_attributes(variables) ⇒ Array<ActiveRecord::Relation::QueryAttribute>
180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/motor/queries/run_query.rb', line 180 def build_statement_attributes(variables) variables.map do |variable_name, value| [value].flatten.map do |val| ActiveRecord::Relation::QueryAttribute.new( variable_name, val, ActiveRecord::Type::Value.new ) end end.flatten end |
.call(query, variables_hash: nil, limit: nil, filters: nil) ⇒ Motor::Queries::RunQuery::QueryResult
45 46 47 48 49 |
# File 'lib/motor/queries/run_query.rb', line 45 def call(query, variables_hash: nil, limit: nil, filters: nil) call!(query, variables_hash: variables_hash, limit: limit, filters: filters) rescue ActiveRecord::StatementInvalid => e QueryResult.new(error: (e)) end |
.call!(query, variables_hash: nil, limit: nil, filters: nil) ⇒ Motor::Queries::RunQuery::QueryResult
31 32 33 34 35 36 37 38 39 |
# File 'lib/motor/queries/run_query.rb', line 31 def call!(query, variables_hash: nil, limit: nil, filters: nil) variables_hash ||= {} limit ||= DEFAULT_LIMIT filters ||= {} result = execute_query(query, limit, variables_hash, filters) QueryResult.new(data: result.rows, columns: build_columns_hash(result)) end |
.connection_class ⇒ Object
213 214 215 |
# File 'lib/motor/queries/run_query.rb', line 213 def connection_class defined?(ResourceRecord) ? ResourceRecord : ActiveRecord::Base end |
.execute_query(query, limit, variables_hash, filters) ⇒ ActiveRecord::Result
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/motor/queries/run_query.rb', line 62 def execute_query(query, limit, variables_hash, filters) result = nil statement = prepare_sql_statement(connection_class.connection, query, limit, variables_hash, filters) connection_class.transaction do result = case connection_class.connection.class.name when 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter' PostgresqlExecQuery.call(connection_class.connection, statement) else statement = normalize_statement_for_sql(statement) connection_class.connection.exec_query(*statement) end raise ActiveRecord::Rollback end result end |
.fetch_column_type_from_value(value) ⇒ String
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/motor/queries/run_query.rb', line 115 def fetch_column_type_from_value(value) case value when Integer 'integer' when Float 'float' when Time 'datetime' when Date 'date' when TrueClass, FalseClass 'boolean' else 'string' end end |
.merge_variable_default_values(variable_configs, variables_hash) ⇒ Hash
205 206 207 208 209 210 211 |
# File 'lib/motor/queries/run_query.rb', line 205 def merge_variable_default_values(variable_configs, variables_hash) variable_configs.each_with_object(variables_hash.slice(*RESERVED_VARIABLES)) do |variable, acc| next if RESERVED_VARIABLES.include?(variable[:name]) acc[variable[:name]] ||= variables_hash[variable[:name]] || variable[:default_value] end end |
.normalize_statement_for_sql(statement) ⇒ Array
194 195 196 197 198 199 200 |
# File 'lib/motor/queries/run_query.rb', line 194 def normalize_statement_for_sql(statement) sql, _, attributes = statement sql = ActiveRecord::Base.sanitize_sql([sql.gsub(STATEMENT_VARIABLE_REGEXP, '?'), attributes.map(&:value)]) [sql, 'SQL', attributes] end |
.prepare_sql_statement(connection, query, limit, variables_hash, filters) ⇒ Array
138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/motor/queries/run_query.rb', line 138 def prepare_sql_statement(connection, query, limit, variables_hash, filters) variables = merge_variable_default_values(query.preferences.fetch(:variables, []), variables_hash) sql, query_variables = RenderSqlTemplate.call(query.sql_body, variables) cte_sql = format(WITH_STATEMENT_TEMPLATE, sql_body: sql.strip.delete_suffix(';')) cte_select_sql = build_cte_select_sql(connection, limit, filters) attributes = build_statement_attributes(query_variables) [[cte_sql, cte_select_sql].join, 'SQL', attributes] end |