Module: Noiseless::Introspection

Included in:
Adapter
Defined in:
lib/noiseless/introspection.rb,
lib/noiseless/introspection/console.rb,
lib/noiseless/introspection/query_visualizer.rb

Defined Under Namespace

Classes: Console, QueryVisualizer

Instance Method Summary collapse

Instance Method Details

#adapter_capabilitiesObject



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/noiseless/introspection.rb', line 30

def adapter_capabilities
  capabilities = [:async_support]

  # Check for bulk operations
  capabilities << :bulk_operations if respond_to?(:bulk)

  # Check for search operations
  capabilities << :search if respond_to?(:search)

  # Check for index management
  capabilities << :index_management if respond_to?(:create_index)

  # Check for document operations
  capabilities << :document_operations if respond_to?(:index_document)

  # Engine-specific capabilities
  case self.class.name
  when /OpenSearch/
    capabilities += %i[point_in_time_search search_templates] if respond_to?(:point_in_time_search)
  when /Typesense/
    capabilities += %i[typo_tolerance faceted_search] if respond_to?(:faceted_search)
  when /Elasticsearch/
    capabilities += %i[aggregations percolate] if respond_to?(:percolate)
  end

  capabilities.uniq
end

#adapter_infoObject

Runtime adapter and module detection



6
7
8
9
10
11
12
13
14
# File 'lib/noiseless/introspection.rb', line 6

def adapter_info
  {
    adapter_type: self.class.name.split("::").last.underscore.to_sym,
    execution_mode: :async,
    execution_module: detect_execution_module,
    capabilities: adapter_capabilities,
    engine_name: engine_name
  }
end

#build_execution_plan(_ast_node, **_opts) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/noiseless/introspection.rb', line 96

def build_execution_plan(_ast_node, **_opts)
  plan = []

  plan << {
    step: "ast_validation",
    description: "Validate AST structure",
    estimated_cost: "O(n) where n = AST node count"
  }

  plan << {
    step: "query_conversion",
    description: "Convert AST to #{engine_name} query format",
    estimated_cost: "O(n) where n = AST complexity"
  }

  plan << {
    step: "async_task_creation",
    description: "Wrap execution in Async::Task",
    estimated_cost: "O(1)"
  }

  plan << {
    step: "engine_execution",
    description: "Execute query on #{engine_name}",
    estimated_cost: "Variable - depends on query complexity and data size"
  }

  plan << {
    step: "response_processing",
    description: "Convert engine response to Noiseless format",
    estimated_cost: "O(m) where m = result count"
  }

  plan
end

#compatibility_matrixObject

Cross-engine compatibility analysis



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/noiseless/introspection.rb', line 133

def compatibility_matrix
  available_adapters = [
    Noiseless::Adapters::Elasticsearch,
    Noiseless::Adapters::OpenSearch,
    Noiseless::Adapters::Typesense
  ]

  matrix = {}

  available_adapters.each do |adapter_class|
    adapter = adapter_class.new
    adapter_name = adapter.engine_name

    matrix[:"#{adapter_name}_async"] = {
      available: true,
      capabilities: adapter.adapter_capabilities,
      engine_name: adapter.engine_name
    }
  rescue StandardError => e
    fallback_key = adapter_class.name.split("::").last.underscore
    matrix[:"#{fallback_key}_async"] = {
      available: false,
      error: e.message
    }
  end

  matrix
end

#detect_execution_moduleObject



16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/noiseless/introspection.rb', line 16

def detect_execution_module
  execution_modules = singleton_class.included_modules.select do |mod|
    mod.name&.include?("ExecutionModules") ||
      mod.name&.include?("Execution")
  end

  execution_modules.map do |mod|
    {
      name: mod.name,
      async: true
    }
  end
end

#engine_nameObject



58
59
60
61
62
63
64
65
# File 'lib/noiseless/introspection.rb', line 58

def engine_name
  case self.class.name
  when /OpenSearch/ then :opensearch
  when /Elasticsearch/ then :elasticsearch
  when /Typesense/ then :typesense
  else :base
  end
end

#explain_query(ast_node) ⇒ Object

Query execution introspection



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/noiseless/introspection.rb', line 68

def explain_query(ast_node, **)
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)

  explanation = {
    adapter: adapter_info,
    ast: ast_node.to_h,
    engine_query: nil,
    execution_plan: [],
    performance: {}
  }

  # Convert AST to engine query
  conversion_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  engine_query = ast_to_hash(ast_node)
  conversion_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - conversion_start

  explanation[:engine_query] = engine_query
  explanation[:performance][:ast_conversion_ms] = (conversion_time * 1000).round(3)

  # Build execution plan
  explanation[:execution_plan] = build_execution_plan(ast_node, **)

  total_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
  explanation[:performance][:total_explanation_ms] = (total_time * 1000).round(3)

  explanation
end

#profile_query(ast_node, iterations: 100) ⇒ Object

Performance profiling



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/noiseless/introspection.rb', line 163

def profile_query(ast_node, iterations: 100, **)
  results = {
    adapter: adapter_info,
    iterations: iterations,
    measurements: [],
    summary: {}
  }

  iterations.times do |_i|
    measurement = measure_single_execution(ast_node, **)
    results[:measurements] << measurement
  end

  # Calculate summary statistics
  times = results[:measurements].map { |m| m[:total_time_ms] }
  results[:summary] = {
    min_ms: times.min,
    max_ms: times.max,
    avg_ms: (times.sum / times.size).round(3),
    median_ms: times.sort[times.size / 2],
    std_dev_ms: calculate_std_dev(times).round(3)
  }

  results
end