Class: ClaudeMemory::Core::FactQueryBuilder

Inherits:
Object
  • Object
show all
Defined in:
lib/claude_memory/core/fact_query_builder.rb

Overview

Query construction logic for fact-related database queries Builds Sequel datasets with appropriate joins and selects Follows Functional Core pattern - pure query building, no execution

Class Method Summary collapse

Class Method Details

.batch_find_facts(store, fact_ids) ⇒ Hash

Build dataset for batch finding facts with entity joins

Parameters:

  • store (SQLiteStore)

    Database store

  • fact_ids (Array<Integer>)

    Fact IDs to load

Returns:

  • (Hash)

    Hash of fact_id => fact_row



13
14
15
16
17
18
19
20
21
# File 'lib/claude_memory/core/fact_query_builder.rb', line 13

def self.batch_find_facts(store, fact_ids)
  return {} if fact_ids.empty?

  results = build_facts_dataset(store)
    .where(Sequel[:facts][:id] => fact_ids)
    .all

  results.each_with_object({}) { |row, hash| hash[row[:id]] = row }
end

.batch_find_receipts(store, fact_ids, include_raw_text: false) ⇒ Hash

Build dataset for batch finding receipts (provenance) with content_items join

Parameters:

  • store (SQLiteStore)

    Database store

  • fact_ids (Array<Integer>)

    Fact IDs to find receipts for

  • include_raw_text (Boolean) (defaults to: false)

    Include raw_text for snippet extraction

Returns:

  • (Hash)

    Hash of fact_id => [receipt_rows]



28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/claude_memory/core/fact_query_builder.rb', line 28

def self.batch_find_receipts(store, fact_ids, include_raw_text: false)
  return {} if fact_ids.empty?

  results = build_receipts_dataset(store, include_raw_text: include_raw_text)
    .where(Sequel[:provenance][:fact_id] => fact_ids)
    .all

  results.group_by { |row| row[:fact_id] }.tap do |grouped|
    # Ensure all requested fact_ids have an entry (empty array if no receipts)
    fact_ids.each { |id| grouped[id] ||= [] }
  end
end

.build_facts_dataset(store) ⇒ Sequel::Dataset

Build standard facts dataset with entity join and all necessary columns

Parameters:

  • store (SQLiteStore)

    Database store

Returns:

  • (Sequel::Dataset)

    Configured dataset



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/claude_memory/core/fact_query_builder.rb', line 131

def self.build_facts_dataset(store)
  store.facts
    .left_join(:entities, id: :subject_entity_id)
    .select(
      Sequel[:facts][:id],
      Sequel[:facts][:docid],
      Sequel[:facts][:predicate],
      Sequel[:facts][:object_literal],
      Sequel[:facts][:status],
      Sequel[:facts][:confidence],
      Sequel[:facts][:valid_from],
      Sequel[:facts][:valid_to],
      Sequel[:facts][:created_at],
      Sequel[:entities][:canonical_name].as(:subject_name),
      Sequel[:facts][:scope],
      Sequel[:facts][:project_path]
    )
end

.build_receipts_dataset(store, include_raw_text: false) ⇒ Sequel::Dataset

Build standard receipts dataset with content_items join

Parameters:

  • store (SQLiteStore)

    Database store

  • include_raw_text (Boolean) (defaults to: false)

    Include raw_text for snippet extraction

Returns:

  • (Sequel::Dataset)

    Configured dataset



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/claude_memory/core/fact_query_builder.rb', line 154

def self.build_receipts_dataset(store, include_raw_text: false)
  columns = [
    Sequel[:provenance][:id],
    Sequel[:provenance][:fact_id],
    Sequel[:provenance][:quote],
    Sequel[:provenance][:strength],
    Sequel[:provenance][:line_start],
    Sequel[:provenance][:line_end],
    Sequel[:content_items][:session_id],
    Sequel[:content_items][:occurred_at]
  ]

  columns << Sequel[:content_items][:raw_text] if include_raw_text

  store.provenance
    .left_join(:content_items, id: :content_item_id)
    .select(*columns)
end

.fetch_changes(store, since, limit) ⇒ Array<Hash>

Find facts created since a given timestamp

Parameters:

  • store (SQLiteStore)

    Database store

  • since (Time, String)

    Timestamp

  • limit (Integer)

    Maximum results

Returns:

  • (Array<Hash>)

    Fact rows



108
109
110
111
112
113
114
115
# File 'lib/claude_memory/core/fact_query_builder.rb', line 108

def self.fetch_changes(store, since, limit)
  store.facts
    .select(:id, :docid, :subject_entity_id, :predicate, :object_literal, :status, :created_at, :scope, :project_path)
    .where { created_at >= since }
    .order(Sequel.desc(:created_at))
    .limit(limit)
    .all
end

.find_conflicts(store, fact_id) ⇒ Array<Hash>

Find conflicts involving the given fact

Parameters:

  • store (SQLiteStore)

    Database store

  • fact_id (Integer)

    Fact ID

Returns:

  • (Array<Hash>)

    Conflict rows



96
97
98
99
100
101
# File 'lib/claude_memory/core/fact_query_builder.rb', line 96

def self.find_conflicts(store, fact_id)
  store.conflicts
    .select(:id, :fact_a_id, :fact_b_id, :status)
    .where(Sequel.or(fact_a_id: fact_id, fact_b_id: fact_id))
    .all
end

.find_fact(store, fact_id) ⇒ Hash?

Find single fact by ID with entity join

Parameters:

  • store (SQLiteStore)

    Database store

  • fact_id (Integer)

    Fact ID

Returns:

  • (Hash, nil)

    Fact row or nil



45
46
47
48
49
# File 'lib/claude_memory/core/fact_query_builder.rb', line 45

def self.find_fact(store, fact_id)
  build_facts_dataset(store)
    .where(Sequel[:facts][:id] => fact_id)
    .first
end

.find_fact_by_docid(store, docid) ⇒ Hash?

Find single fact by docid with entity join

Parameters:

  • store (SQLiteStore)

    Database store

  • docid (String)

    8-character docid

Returns:

  • (Hash, nil)

    Fact row or nil



55
56
57
58
59
# File 'lib/claude_memory/core/fact_query_builder.rb', line 55

def self.find_fact_by_docid(store, docid)
  build_facts_dataset(store)
    .where(Sequel[:facts][:docid] => docid)
    .first
end

.find_provenance_by_content(store, content_id) ⇒ Array<Hash>

Find provenance records for a content item

Parameters:

  • store (SQLiteStore)

    Database store

  • content_id (Integer)

    Content item ID

Returns:

  • (Array<Hash>)

    Provenance rows



121
122
123
124
125
126
# File 'lib/claude_memory/core/fact_query_builder.rb', line 121

def self.find_provenance_by_content(store, content_id)
  store.provenance
    .select(:id, :fact_id, :content_item_id, :quote, :strength)
    .where(content_item_id: content_id)
    .all
end

.find_receipts(store, fact_id, include_raw_text: false) ⇒ Array<Hash>

Find receipts for a single fact

Parameters:

  • store (SQLiteStore)

    Database store

  • fact_id (Integer)

    Fact ID

  • include_raw_text (Boolean) (defaults to: false)

    Include raw_text for snippet extraction

Returns:

  • (Array<Hash>)

    Receipt rows



66
67
68
69
70
# File 'lib/claude_memory/core/fact_query_builder.rb', line 66

def self.find_receipts(store, fact_id, include_raw_text: false)
  build_receipts_dataset(store, include_raw_text: include_raw_text)
    .where(Sequel[:provenance][:fact_id] => fact_id)
    .all
end

.find_superseded_by(store, fact_id) ⇒ Array<Integer>

Find fact IDs that supersede the given fact

Parameters:

  • store (SQLiteStore)

    Database store

  • fact_id (Integer)

    Fact ID

Returns:

  • (Array<Integer>)

    Fact IDs



76
77
78
79
80
# File 'lib/claude_memory/core/fact_query_builder.rb', line 76

def self.find_superseded_by(store, fact_id)
  store.fact_links
    .where(to_fact_id: fact_id, link_type: "supersedes")
    .select_map(:from_fact_id)
end

.find_supersedes(store, fact_id) ⇒ Array<Integer>

Find fact IDs that are superseded by the given fact

Parameters:

  • store (SQLiteStore)

    Database store

  • fact_id (Integer)

    Fact ID

Returns:

  • (Array<Integer>)

    Fact IDs



86
87
88
89
90
# File 'lib/claude_memory/core/fact_query_builder.rb', line 86

def self.find_supersedes(store, fact_id)
  store.fact_links
    .where(from_fact_id: fact_id, link_type: "supersedes")
    .select_map(:to_fact_id)
end