Class: Llmemory::LongTerm::Procedural::Storages::DatabaseStorage

Inherits:
Base
  • Object
show all
Includes:
Crypto::FieldHelpers
Defined in:
lib/llmemory/long_term/procedural/storages/database_storage.rb

Overview

PostgreSQL backend. Each skill is stored as a JSONB data document (plus id/user_id/created_at and a denormalized search_text), mirroring the file-based DatabaseStorage pattern.

Instance Method Summary collapse

Constructor Details

#initialize(database_url: nil, cipher: nil) ⇒ DatabaseStorage

Returns a new instance of DatabaseStorage.



19
20
21
22
23
# File 'lib/llmemory/long_term/procedural/storages/database_storage.rb', line 19

def initialize(database_url: nil, cipher: nil)
  @database_url = database_url || Llmemory.configuration.database_url
  @connection = nil
  @cipher = cipher || Llmemory.build_cipher
end

Instance Method Details

#archive_skills(user_id, ids) ⇒ Object



96
97
98
99
100
101
102
103
104
# File 'lib/llmemory/long_term/procedural/storages/database_storage.rb', line 96

def archive_skills(user_id, ids)
  ensure_tables!
  Array(ids).sum do |id|
    conn.exec_params(
      "UPDATE llmemory_skills SET archived_at = NOW() WHERE user_id = $1 AND id = $2 AND archived_at IS NULL",
      [user_id, id]
    ).cmd_tuples
  end
end

#count_skills(user_id) ⇒ Object



84
85
86
87
# File 'lib/llmemory/long_term/procedural/storages/database_storage.rb', line 84

def count_skills(user_id)
  ensure_tables!
  conn.exec_params("SELECT COUNT(*) AS c FROM llmemory_skills WHERE user_id = $1 AND archived_at IS NULL", [user_id]).first["c"].to_i
end

#delete_skills(user_id, ids) ⇒ Object



89
90
91
92
93
94
# File 'lib/llmemory/long_term/procedural/storages/database_storage.rb', line 89

def delete_skills(user_id, ids)
  ensure_tables!
  Array(ids).sum do |id|
    conn.exec_params("DELETE FROM llmemory_skills WHERE user_id = $1 AND id = $2", [user_id, id]).cmd_tuples
  end
end

#expired_skill_ids(user_id, cutoff:) ⇒ Object



106
107
108
109
110
111
112
# File 'lib/llmemory/long_term/procedural/storages/database_storage.rb', line 106

def expired_skill_ids(user_id, cutoff:)
  ensure_tables!
  conn.exec_params(
    "SELECT id FROM llmemory_skills WHERE user_id = $1 AND archived_at IS NULL AND created_at < $2",
    [user_id, cutoff.iso8601]
  ).map { |r| r["id"] }
end

#find_skills_by_name(user_id, name) ⇒ Object



62
63
64
65
66
67
68
# File 'lib/llmemory/long_term/procedural/storages/database_storage.rb', line 62

def find_skills_by_name(user_id, name)
  ensure_tables!
  conn.exec_params(
    "SELECT data FROM llmemory_skills WHERE user_id = $1 AND archived_at IS NULL AND data->>'name' = $2",
    [user_id, name.to_s]
  ).map { |r| parse_data(r["data"]) }
end

#get_skill(user_id, id) ⇒ Object



39
40
41
42
43
# File 'lib/llmemory/long_term/procedural/storages/database_storage.rb', line 39

def get_skill(user_id, id)
  ensure_tables!
  rows = conn.exec_params("SELECT data FROM llmemory_skills WHERE user_id = $1 AND id = $2", [user_id, id])
  rows.any? ? parse_data(rows.first["data"]) : nil
end

#list_skills(user_id, limit: nil, offset: nil) ⇒ Object



45
46
47
48
49
50
51
# File 'lib/llmemory/long_term/procedural/storages/database_storage.rb', line 45

def list_skills(user_id, limit: nil, offset: nil)
  ensure_tables!
  sql = "SELECT data FROM llmemory_skills WHERE user_id = $1 AND archived_at IS NULL ORDER BY created_at DESC"
  sql += " LIMIT #{limit.to_i}" if limit && limit.to_i.positive?
  sql += " OFFSET #{offset.to_i}" if offset && offset.to_i.positive?
  conn.exec_params(sql, [user_id]).map { |r| parse_data(r["data"]) }
end

#list_usersObject



114
115
116
117
# File 'lib/llmemory/long_term/procedural/storages/database_storage.rb', line 114

def list_users
  ensure_tables!
  conn.exec("SELECT DISTINCT user_id FROM llmemory_skills").map { |r| r["user_id"] }
end

#record_outcome(user_id, skill_id, success:) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/llmemory/long_term/procedural/storages/database_storage.rb', line 70

def record_outcome(user_id, skill_id, success:)
  ensure_tables!
  data = get_skill(user_id, skill_id)
  return nil unless data
  key = success ? :success_count : :failure_count
  data[key] = (data[key] || 0).to_i + 1
  data[:updated_at] = Time.now.utc.iso8601
  conn.exec_params(
    "UPDATE llmemory_skills SET data = $3::jsonb, search_text = $4 WHERE user_id = $1 AND id = $2",
    [user_id, skill_id, store_data(data), enc(searchable_text(data))]
  )
  data
end

#save_skill(user_id, skill) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/llmemory/long_term/procedural/storages/database_storage.rb', line 25

def save_skill(user_id, skill)
  ensure_tables!
  id = skill[:id] || skill["id"] || "skill_#{SecureRandom.hex(8)}"
  data = symbolize(skill).merge(id: id, user_id: user_id)
  data[:created_at] ||= Time.now.utc.iso8601
  conn.exec_params(
    "INSERT INTO llmemory_skills (id, user_id, data, search_text, created_at) " \
    "VALUES ($1, $2, $3::jsonb, $4, $5) " \
    "ON CONFLICT (id) DO UPDATE SET data = $3::jsonb, search_text = $4",
    [id, user_id, store_data(data), enc(searchable_text(data)), created_at_value(data)]
  )
  id
end

#search_skills(user_id, query) ⇒ Object



53
54
55
56
57
58
59
60
# File 'lib/llmemory/long_term/procedural/storages/database_storage.rb', line 53

def search_skills(user_id, query)
  ensure_tables!
  suffix, params = token_filter("search_text", query, 2)
  conn.exec_params(
    "SELECT data FROM llmemory_skills WHERE user_id = $1 AND archived_at IS NULL#{suffix} ORDER BY created_at DESC",
    [user_id, *params]
  ).map { |r| parse_data(r["data"]) }
end