Class: KBS::Blackboard::Persistence::RedisStore

Inherits:
Store
  • Object
show all
Defined in:
lib/kbs/blackboard/persistence/redis_store.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(url: 'redis://localhost:6379/0', session_id: nil, redis: nil) ⇒ RedisStore



14
15
16
17
18
# File 'lib/kbs/blackboard/persistence/redis_store.rb', line 14

def initialize(url: 'redis://localhost:6379/0', session_id: nil, redis: nil)
  @session_id = session_id || SecureRandom.uuid
  @redis = redis || Redis.new(url: url)
  @transaction_depth = 0
end

Instance Attribute Details

#redisObject (readonly)

Returns the value of attribute redis.



12
13
14
# File 'lib/kbs/blackboard/persistence/redis_store.rb', line 12

def redis
  @redis
end

#session_idObject (readonly)

Returns the value of attribute session_id.



12
13
14
# File 'lib/kbs/blackboard/persistence/redis_store.rb', line 12

def session_id
  @session_id
end

Instance Method Details

#add_fact(uuid, type, attributes) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/kbs/blackboard/persistence/redis_store.rb', line 20

def add_fact(uuid, type, attributes)
  attributes_json = JSON.generate(attributes)
  timestamp = Time.now.to_f

  @redis.multi do |pipeline|
    pipeline.hset("fact:#{uuid}", {
      'uuid' => uuid,
      'type' => type.to_s,
      'attributes' => attributes_json,
      'session_id' => @session_id,
      'created_at' => timestamp,
      'updated_at' => timestamp,
      'retracted' => '0'
    })

    # Indexes
    pipeline.sadd('facts:active', uuid)
    pipeline.sadd("facts:type:#{type}", uuid)
    pipeline.sadd("facts:session:#{@session_id}", uuid) if @session_id
    pipeline.sadd('facts:all', uuid)
  end
end

#clear_session(session_id) ⇒ Object



134
135
136
137
138
139
140
141
142
# File 'lib/kbs/blackboard/persistence/redis_store.rb', line 134

def clear_session(session_id)
  uuids = @redis.smembers("facts:session:#{session_id}")

  uuids.each do |uuid|
    remove_fact(uuid)
  end

  @redis.del("facts:session:#{session_id}")
end

#closeObject



195
196
197
# File 'lib/kbs/blackboard/persistence/redis_store.rb', line 195

def close
  @redis.close if @redis
end

#connectionObject

Redis-specific helper to get connection for MessageQueue/AuditLog



200
201
202
# File 'lib/kbs/blackboard/persistence/redis_store.rb', line 200

def connection
  @redis
end

#get_fact(uuid) ⇒ Object



74
75
76
77
78
79
80
81
82
83
# File 'lib/kbs/blackboard/persistence/redis_store.rb', line 74

def get_fact(uuid)
  fact_data = @redis.hgetall("fact:#{uuid}")
  return nil if fact_data.empty? || fact_data['retracted'] == '1'

  {
    uuid: fact_data['uuid'],
    type: fact_data['type'].to_sym,
    attributes: JSON.parse(fact_data['attributes'], symbolize_names: true)
  }
end

#get_facts(type = nil, pattern = {}) ⇒ Object



85
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
# File 'lib/kbs/blackboard/persistence/redis_store.rb', line 85

def get_facts(type = nil, pattern = {})
  # Get UUIDs from appropriate index
  uuids = if type
    @redis.sinter('facts:active', "facts:type:#{type}")
  else
    @redis.smembers('facts:active')
  end

  # Fetch and filter facts
  facts = []
  uuids.each do |uuid|
    fact_data = @redis.hgetall("fact:#{uuid}")
    next if fact_data.empty? || fact_data['retracted'] == '1'

    attributes = JSON.parse(fact_data['attributes'], symbolize_names: true)

    if matches_pattern?(attributes, pattern)
      facts << {
        uuid: fact_data['uuid'],
        type: fact_data['type'].to_sym,
        attributes: attributes
      }
    end
  end

  facts
end

#query_facts(conditions = nil, params = []) ⇒ Object



113
114
115
116
117
118
# File 'lib/kbs/blackboard/persistence/redis_store.rb', line 113

def query_facts(conditions = nil, params = [])
  # Redis doesn't support SQL queries
  # For complex queries, use get_facts with pattern matching
  # or implement custom Redis Lua scripts
  get_facts
end

#register_knowledge_source(name, description: nil, topics: []) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/kbs/blackboard/persistence/redis_store.rb', line 120

def register_knowledge_source(name, description: nil, topics: [])
  topics_json = JSON.generate(topics)

  @redis.hset("ks:#{name}", {
    'name' => name,
    'description' => description,
    'topics' => topics_json,
    'active' => '1',
    'registered_at' => Time.now.to_f
  })

  @redis.sadd('knowledge_sources:active', name)
end

#remove_fact(uuid) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/kbs/blackboard/persistence/redis_store.rb', line 43

def remove_fact(uuid)
  fact_data = @redis.hgetall("fact:#{uuid}")
  return nil if fact_data.empty? || fact_data['retracted'] == '1'

  type = fact_data['type'].to_sym
  attributes = JSON.parse(fact_data['attributes'], symbolize_names: true)

  @redis.multi do |pipeline|
    pipeline.hset("fact:#{uuid}", 'retracted', '1')
    pipeline.hset("fact:#{uuid}", 'retracted_at', Time.now.to_f)
    pipeline.srem('facts:active', uuid)
    pipeline.srem("facts:type:#{type}", uuid)
  end

  { type: type, attributes: attributes }
end

#statsObject



168
169
170
171
172
173
174
175
176
177
178
# File 'lib/kbs/blackboard/persistence/redis_store.rb', line 168

def stats
  active_count = @redis.scard('facts:active')
  total_count = @redis.scard('facts:all')
  ks_count = @redis.scard('knowledge_sources:active')

  {
    total_facts: total_count,
    active_facts: active_count,
    knowledge_sources: ks_count
  }
end

#transaction(&block) ⇒ Object



180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/kbs/blackboard/persistence/redis_store.rb', line 180

def transaction(&block)
  @transaction_depth += 1
  begin
    if @transaction_depth == 1
      # Redis MULTI/EXEC happens in individual operations
      # This provides a consistent interface
      yield
    else
      yield
    end
  ensure
    @transaction_depth -= 1
  end
end

#update_fact(uuid, attributes) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/kbs/blackboard/persistence/redis_store.rb', line 60

def update_fact(uuid, attributes)
  fact_data = @redis.hgetall("fact:#{uuid}")
  return nil if fact_data.empty? || fact_data['retracted'] == '1'

  attributes_json = JSON.generate(attributes)

  @redis.multi do |pipeline|
    pipeline.hset("fact:#{uuid}", 'attributes', attributes_json)
    pipeline.hset("fact:#{uuid}", 'updated_at', Time.now.to_f)
  end

  fact_data['type'].to_sym
end

#vacuumObject



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/kbs/blackboard/persistence/redis_store.rb', line 144

def vacuum
  # Remove retracted facts from Redis to free memory
  all_uuids = @redis.smembers('facts:all')

  all_uuids.each do |uuid|
    fact_data = @redis.hgetall("fact:#{uuid}")
    if fact_data['retracted'] == '1'
      # Calculate if fact is old enough to remove (e.g., > 30 days)
      retracted_at = fact_data['retracted_at'].to_f
      if Time.now.to_f - retracted_at > (30 * 24 * 60 * 60)
        type = fact_data['type']
        session_id = fact_data['session_id']

        @redis.multi do |pipeline|
          pipeline.del("fact:#{uuid}")
          pipeline.srem('facts:all', uuid)
          pipeline.srem("facts:type:#{type}", uuid)
          pipeline.srem("facts:session:#{session_id}", uuid) if session_id
        end
      end
    end
  end
end