Class: CodeToQuery::Query

Inherits:
Object
  • Object
show all
Defined in:
lib/code_to_query/query.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(sql:, params:, bind_spec:, intent:, allow_tables:, config:) ⇒ Query

Returns a new instance of Query.



12
13
14
15
16
17
18
19
20
21
22
# File 'lib/code_to_query/query.rb', line 12

def initialize(sql:, params:, bind_spec:, intent:, allow_tables:, config:)
  @sql = sql
  @params = params || {}
  @bind_spec = bind_spec || []
  @intent = intent || {}
  @allow_tables = allow_tables
  @config = config
  @safety_checked = false
  @safety_result = nil
  @metrics = extract_metrics_from_intent(@intent)
end

Instance Attribute Details

#intentObject (readonly)

Returns the value of attribute intent.



10
11
12
# File 'lib/code_to_query/query.rb', line 10

def intent
  @intent
end

#metricsObject (readonly)

Returns the value of attribute metrics.



10
11
12
# File 'lib/code_to_query/query.rb', line 10

def metrics
  @metrics
end

#paramsObject (readonly)

Returns the value of attribute params.



10
11
12
# File 'lib/code_to_query/query.rb', line 10

def params
  @params
end

#sqlObject (readonly)

Returns the value of attribute sql.



10
11
12
# File 'lib/code_to_query/query.rb', line 10

def sql
  @sql
end

Instance Method Details

#bindsObject



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
# File 'lib/code_to_query/query.rb', line 24

def binds
  return [] unless defined?(ActiveRecord::Base)

  connection = if @config.readonly_role && ActiveRecord.respond_to?(:connected_to)
                 ActiveRecord::Base.connected_to(role: @config.readonly_role) do
                   ActiveRecord::Base.connection
                 end
               else
                 ActiveRecord::Base.connection
               end

  @bind_spec.map do |bind_info|
    key = bind_info[:key]
    column_name = bind_info[:column]

    # Get parameter value (check both string and symbol keys)
    value = @params[key.to_s] || @params[key.to_sym]

    # Determine the correct ActiveRecord type
    type = infer_column_type(connection, @intent['table'], column_name, bind_info[:cast])

    ActiveRecord::Relation::QueryAttribute.new(column_name.to_s, value, type)
  end
rescue StandardError => e
  @config.logger.warn("[code_to_query] Failed to build binds: #{e.message}")
  []
end

#explainObject



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
# File 'lib/code_to_query/query.rb', line 59

def explain
  return 'EXPLAIN unavailable (ActiveRecord not loaded)' unless defined?(ActiveRecord::Base)

  explain_sql = case @config.adapter
                when :postgres, :postgresql
                  "EXPLAIN (ANALYZE false, VERBOSE false, BUFFERS false) #{@sql}"
                when :mysql
                  "EXPLAIN #{@sql}"
                when :sqlite
                  "EXPLAIN QUERY PLAN #{@sql}"
                else
                  "EXPLAIN #{@sql}"
                end

  result = if @config.readonly_role && ActiveRecord.respond_to?(:connected_to)
             ActiveRecord::Base.connected_to(role: @config.readonly_role) do
               ActiveRecord::Base.connection.execute(explain_sql)
             end
           else
             ActiveRecord::Base.connection.execute(explain_sql)
           end

  format_explain_result(result)
rescue StandardError => e
  "EXPLAIN failed: #{e.message}"
end

#relationable?Boolean

Returns:

  • (Boolean)


120
121
122
123
124
125
# File 'lib/code_to_query/query.rb', line 120

def relationable?
  return false unless defined?(ActiveRecord::Base)
  return false unless @intent['type'] == 'select'

  !!infer_model_for_table(@intent['table'])
end

#runObject



134
135
136
# File 'lib/code_to_query/query.rb', line 134

def run
  Runner.new(@config).run(sql: @sql, binds: binds)
end

#safe?Boolean

Returns:

  • (Boolean)


52
53
54
55
56
57
# File 'lib/code_to_query/query.rb', line 52

def safe?
  return @safety_result if @safety_checked

  @safety_checked = true
  @safety_result = perform_safety_checks
end

#to_active_recordObject



116
117
118
# File 'lib/code_to_query/query.rb', line 116

def to_active_record
  to_relation
end

#to_relationObject



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
# File 'lib/code_to_query/query.rb', line 86

def to_relation
  return nil unless defined?(ActiveRecord::Base)
  return nil unless @intent['type'] == 'select'

  table_name = @intent['table']
  model = infer_model_for_table(table_name)
  return nil unless model

  scope = model.all

  # Apply WHERE conditions
  Array(@intent['filters']).each do |filter|
    scope = apply_filter_to_scope(scope, filter)
  end

  # Apply ORDER BY
  Array(@intent['order']).each do |order_spec|
    column = order_spec['column']
    direction = order_spec['dir']&.downcase == 'asc' ? :asc : :desc
    scope = scope.order(column => direction)
  end

  # Apply LIMIT (intelligent based on query type)
  limit = determine_appropriate_limit
  scope.limit(limit) if limit
rescue StandardError => e
  @config.logger.warn("[code_to_query] Failed to build relation: #{e.message}")
  nil
end

#to_relation!Object



127
128
129
130
131
132
# File 'lib/code_to_query/query.rb', line 127

def to_relation!
  rel = to_relation
  return rel if rel

  raise CodeToQuery::NotRelationConvertibleError, 'Query cannot be expressed as ActiveRecord::Relation'
end