Module: RailsAgents::DatabaseQuery

Defined in:
lib/rails_agents/database_query.rb

Overview

Read-only database query helpers for Tool Bridge. Supports ActiveRecord SQL dialects and Mongoid collection finds.

Constant Summary collapse

FORBIDDEN_SQL =
/\b(INSERT|UPDATE|DELETE|DROP|ALTER|TRUNCATE|CREATE|GRANT|REVOKE|COPY|EXECUTE|CALL|MERGE|REPLACE)\b/i

Class Method Summary collapse

Class Method Details

.apply_row_limit(sql, adapter) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/rails_agents/database_query.rb', line 56

def apply_row_limit(sql, adapter)
  return sql if sql.match?(/\bLIMIT\b/i) || sql.match?(/\bFETCH\s+FIRST\b/i) || sql.match?(/\bTOP\s+\d+/i)

  case adapter.to_s
  when "sqlserver"
    # Best-effort: wrap as subquery when TOP absent
    "SELECT TOP 50 * FROM (#{sql}) AS rails_agents_q"
  when "oracle"
    "SELECT * FROM (#{sql}) rails_agents_q WHERE ROWNUM <= 50"
  else
    # postgresql, mysql, sqlite, trilogy, etc.
    "#{sql} LIMIT 50"
  end
end

.mongo_query(collection:, filter: nil, limit: 50, projection: nil) ⇒ Object

Raises:

  • (ArgumentError)


34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/rails_agents/database_query.rb', line 34

def mongo_query(collection:, filter: nil, limit: 50, projection: nil)
  raise ArgumentError, "Mongoid is not available" unless defined?(Mongoid) && Mongoid.respond_to?(:default_client)

  name = collection.to_s.strip
  raise ArgumentError, "collection is required" if name.empty?
  raise ArgumentError, "Invalid collection name" unless name.match?(/\A[A-Za-z0-9_.-]+\z/)

  lim = [[limit.to_i, 1].max, 50].min
  criteria = filter.is_a?(Hash) ? filter : {}
  coll = Mongoid.default_client[name]
  cursor = coll.find(criteria)
  cursor = cursor.sort(projection) if projection.is_a?(Hash) && !projection.empty?
  docs = cursor.limit(lim).to_a.map { |doc| stringify_bson(doc) }
  {
    ok: true,
    engine: "mongoid",
    collection: name,
    row_count: docs.length,
    rows: docs
  }
end

.sql_query(sql, adapter: nil) ⇒ Object

Raises:

  • (ArgumentError)


11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/rails_agents/database_query.rb', line 11

def sql_query(sql, adapter: nil)
  raise ArgumentError, "ActiveRecord is not available" unless defined?(ActiveRecord::Base)

  text = sql.to_s.strip
  raise ArgumentError, "sql is required" if text.empty?

  normalized = text.sub(/;\s*\z/, "")
  unless normalized.match?(/\A\s*(WITH|SELECT)\b/im)
    raise ArgumentError, "Only read-only SELECT/WITH queries are allowed"
  end
  raise ArgumentError, "Write or DDL statements are not allowed" if normalized.match?(FORBIDDEN_SQL)

  limited = apply_row_limit(normalized, adapter || DatabaseDiscovery.primary_adapter)
  rows = ActiveRecord::Base.connection.exec_query(limited).to_a
  {
    ok: true,
    engine: "active_record",
    adapter: adapter || DatabaseDiscovery.primary_adapter,
    row_count: rows.length,
    rows: rows.first(50)
  }
end

.stringify_bson(value) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/rails_agents/database_query.rb', line 71

def stringify_bson(value)
  case value
  when Hash
    value.transform_keys(&:to_s).transform_values { |v| stringify_bson(v) }
  when Array
    value.map { |v| stringify_bson(v) }
  else
    if defined?(BSON::ObjectId) && value.is_a?(BSON::ObjectId)
      value.to_s
    else
      value
    end
  end
rescue StandardError
  value.inspect
end