• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nshkrdotcom / ElixirScope / ad776f28bc28eace8c8deb1567f07a14a362d198

30 May 2025 03:34PM UTC coverage: 57.672% (-0.04%) from 57.709%
ad776f28bc28eace8c8deb1567f07a14a362d198

push

github

NSHkr
update test apps

6217 of 10780 relevant lines covered (57.67%)

3151.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

61.9
/lib/elixir_scope/ast_repository/query_builder.ex
1
defmodule ElixirScope.ASTRepository.QueryBuilder do
2
  @moduledoc """
3
  Advanced query builder for the Enhanced AST Repository.
4

5
  Provides powerful querying capabilities with:
6
  - Complex filters for complexity, patterns, dependencies
7
  - Query optimization using available indexes
8
  - Result caching and performance monitoring
9
  - Support for semantic, structural, performance, and security queries
10

11
  ## Query Types
12

13
  - **Semantic queries**: Find functions similar to a given one
14
  - **Structural queries**: Find specific AST patterns (e.g., GenServer implementations)
15
  - **Performance queries**: Find functions with specific complexity characteristics
16
  - **Security queries**: Identify potential security vulnerabilities
17
  - **Dependency queries**: Find modules using specific functions or patterns
18

19
  ## Performance Targets
20

21
  - Simple queries: <50ms
22
  - Complex queries: <200ms
23
  - Memory usage: <50MB for query execution
24

25
  ## Examples
26

27
      # Complex function query
28
      {:ok, functions} = QueryBuilder.execute_query(repo, %{
29
        select: [:module, :function, :complexity, :performance_profile],
30
        from: :functions,
31
        where: [
32
          {:complexity, :gt, 15},
33
          {:calls, :contains, {Ecto.Repo, :all, 1}},
34
          {:performance_profile, :not_nil}
35
        ],
36
        order_by: {:desc, :complexity},
37
        limit: 20
38
      })
39

40
      # Semantic similarity query
41
      {:ok, similar} = QueryBuilder.execute_query(repo, %{
42
        select: [:module, :function, :similarity_score],
43
        from: :functions,
44
        where: [
45
          {:similar_to, {MyModule, :my_function, 2}},
46
          {:similarity_threshold, 0.8}
47
        ],
48
        order_by: {:desc, :similarity_score}
49
      })
50
  """
51

52
  use GenServer
53
  require Logger
54

55
  alias ElixirScope.ASTRepository.QueryBuilder.{
56
    Types,
57
    Normalizer,
58
    Validator,
59
    Optimizer,
60
    Executor,
61
    Cache
62
  }
63

64
  defstruct [
65
    :cache_pid,
66
    :opts
67
  ]
68

69
  # GenServer API
70

71
  def start_link(opts \\ []) do
72
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
33✔
73
  end
74

75
  def init(opts) do
76
    # Ensure the cache is available - either already started or start it now
77
    ensure_cache_started()
33✔
78

79
    state = %__MODULE__{
33✔
80
      cache_pid: nil,  # We'll use the named process
81
      opts: opts
82
    }
83

84
    Logger.info("QueryBuilder started with caching enabled")
33✔
85
    {:ok, state}
86
  end
87

88
  def handle_call({:execute_query, repo, query_spec}, _from, state) do
89
    start_time = System.monotonic_time(:millisecond)
6✔
90

91
    case execute_query_internal(repo, query_spec) do
6✔
92
      {:ok, result} ->
93
        end_time = System.monotonic_time(:millisecond)
×
94
        execution_time = end_time - start_time
×
95

96
        # Record performance
97
        Cache.record_performance(execution_time)
×
98

99
        # Add metadata to result
100
        result_with_metadata = add_execution_metadata(result, execution_time, query_spec)
×
101

102
        {:reply, {:ok, result_with_metadata}, state}
×
103

104
      error ->
105
        {:reply, error, state}
6✔
106
    end
107
  end
108

109
  def handle_call({:build_query, query_spec}, _from, state) do
110
    case build_query_internal(query_spec) do
26✔
111
      {:ok, query} ->
112
        {:reply, {:ok, query}, state}
16✔
113

114
      error ->
115
        {:reply, error, state}
10✔
116
    end
117
  end
118

119
  def handle_call(:get_cache_stats, _from, state) do
120
    case Cache.get_stats() do
2✔
121
      {:ok, %{cache: cache_stats, performance: _perf_stats}} ->
122
        {:reply, {:ok, cache_stats}, state}
2✔
123
      {:ok, stats} ->
124
        {:reply, {:ok, stats}, state}
×
125
      error ->
126
        {:reply, error, state}
×
127
    end
128
  end
129

130
  def handle_call(:clear_cache, _from, state) do
131
    case Cache.clear() do
34✔
132
      :ok -> {:reply, :ok, state}
32✔
133
      error -> {:reply, error, state}
×
134
    end
135
  end
136

137
  # Public API
138

139
  @doc """
140
  Builds a query structure from keyword options.
141

142
  ## Parameters
143

144
  - `query_spec` - Map or keyword list with query specifications
145

146
  ## Examples
147

148
      iex> QueryBuilder.build_query(%{
149
      ...>   select: [:module, :function, :complexity],
150
      ...>   from: :functions,
151
      ...>   where: [{:complexity, :gt, 10}],
152
      ...>   order_by: {:desc, :complexity},
153
      ...>   limit: 20
154
      ...> })
155
      {:ok, %Types{...}}
156
  """
157
  @spec build_query(map() | keyword()) :: {:ok, Types.query_t()} | {:error, term()}
158
  def build_query(query_spec) do
159
    GenServer.call(__MODULE__, {:build_query, query_spec})
26✔
160
  end
161

162
  @doc """
163
  Executes a query against the Enhanced Repository with optimization.
164

165
  ## Parameters
166

167
  - `repo` - The Enhanced Repository process
168
  - `query_spec` - Query specification map or QueryBuilder struct
169

170
  ## Returns
171

172
  - `{:ok, query_result()}` - Successful execution with results and metadata
173
  - `{:error, term()}` - Error during execution
174
  """
175
  @spec execute_query(pid() | atom(), map() | Types.query_t()) :: {:ok, Types.query_result()} | {:error, term()}
176
  def execute_query(repo, query_spec) do
177
    GenServer.call(__MODULE__, {:execute_query, repo, query_spec}, 30_000)
6✔
178
  end
179

180
  @doc """
181
  Gets cache statistics for monitoring performance.
182
  """
183
  @spec get_cache_stats() :: {:ok, map()}
184
  def get_cache_stats() do
185
    GenServer.call(__MODULE__, :get_cache_stats)
2✔
186
  end
187

188
  @doc """
189
  Clears the query cache.
190
  """
191
  @spec clear_cache() :: :ok
192
  def clear_cache() do
193
    GenServer.call(__MODULE__, :clear_cache)
34✔
194
  end
195

196
  # Public functions for testing and direct access
197

198
  @doc false
199
  def evaluate_condition(item, condition) do
200
    Executor.evaluate_condition(item, condition)
34✔
201
  end
202

203
  @doc false
204
  def apply_ordering(data, order_spec) do
205
    Executor.apply_ordering(data, order_spec)
×
206
  end
207

208
  @doc false
209
  def apply_limit_offset(data, limit, offset) do
210
    Executor.apply_limit_offset(data, limit, offset)
3✔
211
  end
212

213
  @doc false
214
  def apply_select(data, fields) do
215
    Executor.apply_select(data, fields)
2✔
216
  end
217

218
  # Private Implementation
219

220
  defp ensure_cache_started() do
221
    case Process.whereis(Cache) do
33✔
222
      nil ->
223
        # Cache not started, try to start it
224
        case Cache.start_link() do
31✔
225
          {:ok, _pid} -> :ok
31✔
226
          {:error, {:already_started, _pid}} -> :ok
×
227
          error -> error
×
228
        end
229
      _pid ->
2✔
230
        # Cache already running
231
        :ok
232
    end
233
  end
234

235
  defp execute_query_internal(repo, query_spec) do
236
    with {:ok, query} <- Normalizer.normalize_query(query_spec),
6✔
237
         {:ok, validated_query} <- Validator.validate_query(query),
6✔
238
         {:ok, optimized_query} <- Optimizer.optimize_query(validated_query),
6✔
239
         {:ok, result} <- execute_optimized_query(repo, optimized_query) do
6✔
240
      {:ok, result}
241
    else
242
      error -> error
6✔
243
    end
244
  end
245

246
  defp build_query_internal(query_spec) do
247
    with {:ok, query} <- Normalizer.normalize_query(query_spec),
26✔
248
         {:ok, validated_query} <- Validator.validate_query(query),
23✔
249
         {:ok, optimized_query} <- Optimizer.optimize_query(validated_query) do
16✔
250
      {:ok, optimized_query}
251
    else
252
      error -> error
10✔
253
    end
254
  end
255

256
  defp execute_optimized_query(repo, %Types{} = query) do
257
    # Check cache first if we have a cache key
258
    case query.cache_key do
6✔
259
      nil ->
260
        # No cache key, execute directly
261
        execute_against_repo(repo, query)
×
262

263
      cache_key ->
264
        case Cache.get(cache_key) do
6✔
265
          {:hit, cached_result} ->
×
266
            {:ok, Map.put(cached_result, :cache_hit, true)}
267

268
          :miss ->
269
            case execute_against_repo(repo, query) do
6✔
270
              {:ok, result} ->
271
                # Cache the result
272
                Cache.put(cache_key, result)
×
273
                {:ok, Map.put(result, :cache_hit, false)}
274

275
              error -> error
6✔
276
            end
277
        end
278
    end
279
  end
280

281
  defp execute_against_repo(repo, %Types{} = query) do
282
    Executor.execute_query(repo, query)
6✔
283
  end
284

285
  defp add_execution_metadata(result, execution_time, query_spec) do
286
    # Determine performance score based on execution time
287
    performance_score = cond do
×
288
      execution_time <= Types.simple_query_threshold() -> :excellent
×
289
      execution_time <= Types.complex_query_threshold() -> :good
×
290
      execution_time <= Types.complex_query_threshold() * 2 -> :fair
×
291
      true -> :poor
×
292
    end
293

294
    # Extract optimization hints and estimated cost from query if it's a Types struct
295
    {optimization_hints, estimated_cost} = case query_spec do
×
296
      %Types{optimization_hints: hints, estimated_cost: cost} -> {hints || [], cost}
×
297
      _ -> {[], nil}
×
298
    end
299

300
    metadata = %{
×
301
      execution_time_ms: execution_time,
302
      cache_hit: Map.get(result, :cache_hit, false),
303
      optimization_applied: optimization_hints,
304
      performance_score: performance_score,
305
      estimated_cost: estimated_cost
306
    }
307

308
    Map.put(result, :metadata, metadata)
×
309
  end
310
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc