Class: Spurline::Session::Store::Postgres

Inherits:
Base
  • Object
show all
Defined in:
lib/spurline/session/store/postgres.rb

Overview

PostgreSQL-backed session store. Persists sessions across process restarts. Thread-safe via a single connection guarded by a Mutex.

Constant Summary collapse

TABLE_NAME =
"spurline_sessions"

Instance Method Summary collapse

Constructor Details

#initialize(url: Spurline.config.session_store_postgres_url, serializer: Spurline::Session::Serializer.new) ⇒ Postgres

Returns a new instance of Postgres.



13
14
15
16
17
18
19
20
# File 'lib/spurline/session/store/postgres.rb', line 13

def initialize(url: Spurline.config.session_store_postgres_url, serializer: Spurline::Session::Serializer.new)
  @url = url
  @serializer = serializer
  @mutex = Mutex.new
  @connection = nil
  require_pg!
  ensure_url!
end

Instance Method Details

#clear!Object



78
79
80
81
82
# File 'lib/spurline/session/store/postgres.rb', line 78

def clear!
  @mutex.synchronize do
    connection.exec("DELETE FROM #{TABLE_NAME}")
  end
end

#closeObject



90
91
92
93
94
95
# File 'lib/spurline/session/store/postgres.rb', line 90

def close
  @mutex.synchronize do
    @connection&.close
    @connection = nil
  end
end

#delete(id) ⇒ Object



59
60
61
62
63
# File 'lib/spurline/session/store/postgres.rb', line 59

def delete(id)
  @mutex.synchronize do
    connection.exec_params("DELETE FROM #{TABLE_NAME} WHERE id = $1", [id])
  end
end

#exists?(id) ⇒ Boolean

Returns:

  • (Boolean)


65
66
67
68
69
70
# File 'lib/spurline/session/store/postgres.rb', line 65

def exists?(id)
  @mutex.synchronize do
    result = connection.exec_params("SELECT 1 FROM #{TABLE_NAME} WHERE id = $1 LIMIT 1", [id])
    result.ntuples.positive?
  end
end

#idsObject



84
85
86
87
88
# File 'lib/spurline/session/store/postgres.rb', line 84

def ids
  @mutex.synchronize do
    connection.exec("SELECT id FROM #{TABLE_NAME} ORDER BY id").map { |row| row.fetch("id") }
  end
end

#load(id) ⇒ Object

ASYNC-READY: Keep read path isolated for future async driver swap.



45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/spurline/session/store/postgres.rb', line 45

def load(id)
  row = @mutex.synchronize do
    result = connection.exec_params(
      "SELECT data::text AS data FROM #{TABLE_NAME} WHERE id = $1 LIMIT 1",
      [id]
    )
    result.ntuples.positive? ? result[0] : nil
  end
  return nil unless row

  payload = row.fetch("data")
  @serializer.from_json(payload, store: self)
end

#save(session) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/spurline/session/store/postgres.rb', line 22

def save(session)
  now = Time.now.utc.iso8601(6)
  payload = @serializer.to_json(session)

  @mutex.synchronize do
    connection.exec_params(
      "        INSERT INTO \#{TABLE_NAME} (id, state, agent_class, created_at, updated_at, data)\n        VALUES ($1, $2, $3, COALESCE((SELECT created_at FROM \#{TABLE_NAME} WHERE id = $1), $4), $5, $6::jsonb)\n        ON CONFLICT (id) DO UPDATE SET\n          state = EXCLUDED.state,\n          agent_class = EXCLUDED.agent_class,\n          updated_at = EXCLUDED.updated_at,\n          data = EXCLUDED.data\n      SQL\n      [session.id, session.state.to_s, session.agent_class, now, now, payload]\n    )\n  end\n\n  session\nend\n",

#sizeObject



72
73
74
75
76
# File 'lib/spurline/session/store/postgres.rb', line 72

def size
  @mutex.synchronize do
    connection.exec("SELECT COUNT(*) FROM #{TABLE_NAME}")[0]["count"].to_i
  end
end