Class: GitHub::KV

Inherits:
Object
  • Object
show all
Defined in:
lib/github/kv.rb

Defined Under Namespace

Classes: MissingConnectionError

Constant Summary collapse

MAX_KEY_LENGTH =
255
MAX_VALUE_LENGTH =
65535
KeyLengthError =
Class.new(StandardError)
ValueLengthError =
Class.new(StandardError)
UnavailableError =
Class.new(StandardError)

Instance Method Summary collapse

Constructor Details

#initialize(encapsulated_errors = [SystemCallError], &conn_block) ⇒ KV

Returns a new instance of KV.



54
55
56
57
# File 'lib/github/kv.rb', line 54

def initialize(encapsulated_errors = [SystemCallError], &conn_block)
  @encapsulated_errors = encapsulated_errors
  @conn_block = conn_block
end

Instance Method Details

#connectionObject



59
60
61
# File 'lib/github/kv.rb', line 59

def connection
  @conn_block.try(:call) || (raise MissingConnectionError, "KV must be initialized with a block that returns a connection")
end

#del(key) ⇒ Object

del

String -> nil

Deletes the specified key. Returns nil. Raises on error.

Example:

kv.del("foo")
  # => nil


246
247
248
249
250
# File 'lib/github/kv.rb', line 246

def del(key)
  validate_key(key)

  mdel([key])
end

#exists(key) ⇒ Object

exists

String -> Result<Boolean>

Checks for existence of the specified key.

Example:

kv.exists("foo")
  # => #<Result value: true>

kv.exists("octocat")
  # => #<Result value: false>


168
169
170
171
172
# File 'lib/github/kv.rb', line 168

def exists(key)
  validate_key(key)

  mexists([key]).map { |values| values[0] }
end

#get(key) ⇒ Object

get

String -> Result<String | nil>

Gets the value of the specified key.

Example:

kv.get("foo")
  # => #<Result value: "bar">

kv.get("octocat")
  # => #<Result value: nil>


75
76
77
78
79
# File 'lib/github/kv.rb', line 75

def get(key)
  validate_key(key)

  mget([key]).map { |values| values[0] }
end

#mdel(keys) ⇒ Object

mdel

String -> nil

Deletes the specified keys. Returns nil. Raises on error.

Example:

kv.mdel(["foo", "octocat"])
  # => nil


261
262
263
264
265
266
267
268
269
270
271
# File 'lib/github/kv.rb', line 261

def mdel(keys)
  validate_key_array(keys)

  encapsulate_error do
    GitHub::SQL.run(<<-SQL, :keys => keys, :connection => connection)
      DELETE FROM key_values WHERE `key` IN :keys
    SQL
  end

  nil
end

#mexists(keys) ⇒ Object

mexists
String

-> Result<>

Checks for existence of all specified keys. Booleans will be returned in the same order as keys are specified.

Example:

kv.mexists(["foo", "octocat"])
  # => #<Result value: [true, false]>


184
185
186
187
188
189
190
191
192
193
194
# File 'lib/github/kv.rb', line 184

def mexists(keys)
  validate_key_array(keys)

  Result.new {
    existing_keys = GitHub::SQL.values(<<-SQL, :keys => keys, :connection => connection).to_set
      SELECT `key` FROM key_values WHERE `key` IN :keys AND (`expires_at` IS NULL OR `expires_at` > NOW())
    SQL

    keys.map { |key| existing_keys.include?(key) }
  }
end

#mget(keys) ⇒ Object

mget
String

-> Result<[String | nil]>

Gets the values of all specified keys. Values will be returned in the same order as keys are specified. nil will be returned in place of a String for keys which do not exist.

Example:

kv.mget(["foo", "octocat"])
  # => #<Result value: ["bar", nil]


92
93
94
95
96
97
98
99
100
101
102
# File 'lib/github/kv.rb', line 92

def mget(keys)
  validate_key_array(keys)

  Result.new {
    kvs = GitHub::SQL.results(<<-SQL, :keys => keys, :connection => connection).to_h
      SELECT `key`, value FROM key_values WHERE `key` IN :keys AND (`expires_at` IS NULL OR `expires_at` > NOW())
    SQL

    keys.map { |key| kvs[key] }
  }
end

#mset(kvs, expires: nil) ⇒ Object

mset

{ String => String }, expires: Time? -> nil

Sets the specified hash keys to their associated values, setting them to expire at the specified time. Returns nil. Raises on error.

Example:

kv.mset({ "foo" => "bar", "baz" => "quux" })
  # => nil

kv.mset({ "expires" => "soon" }, expires: 1.hour.from_now)
  # => nil


134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/github/kv.rb', line 134

def mset(kvs, expires: nil)
  validate_key_value_hash(kvs)
  validate_expires(expires) if expires

  rows = kvs.map { |key, value|
    [key, value, GitHub::SQL::NOW, GitHub::SQL::NOW, expires || GitHub::SQL::NULL]
  }

  encapsulate_error do
    GitHub::SQL.run(<<-SQL, :rows => GitHub::SQL::ROWS(rows), :connection => connection)
      INSERT INTO key_values (`key`, value, created_at, updated_at, expires_at)
      VALUES :rows
      ON DUPLICATE KEY UPDATE
        value = VALUES(value),
        updated_at = VALUES(updated_at),
        expires_at = VALUES(expires_at)
    SQL
  end

  nil
end

#set(key, value, expires: nil) ⇒ Object

set

String, String, expires: Time? -> nil

Sets the specified key to the specified value. Returns nil. Raises on error.

Example:

kv.set("foo", "bar")
  # => nil


114
115
116
117
118
119
# File 'lib/github/kv.rb', line 114

def set(key, value, expires: nil)
  validate_key(key)
  validate_value(value)

  mset({ key => value }, expires: expires)
end

#setnx(key, value, expires: nil) ⇒ Object

setnx

String, String, expires: Time? -> Boolean

Sets the specified key to the specified value only if it does not already exist.

Returns true if the key was set, false otherwise. Raises on error.

Example:

kv.setnx("foo", "bar")
  # => false

kv.setnx("octocat", "monalisa")
  # => true

kv.setnx("expires", "soon", expires: 1.hour.from_now)
  # => true


214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/github/kv.rb', line 214

def setnx(key, value, expires: nil)
  validate_key(key)
  validate_value(value)
  validate_expires(expires) if expires

  encapsulate_error {
    # if the key already exists but has expired, prune it first. We could
    # achieve the same thing with the right INSERT ... ON DUPLICATE KEY UPDATE
    # query, but then we would not be able to rely on affected_rows

    GitHub::SQL.run(<<-SQL, :key => key, :connection => connection)
      DELETE FROM key_values WHERE `key` = :key AND expires_at <= NOW()
    SQL

    sql = GitHub::SQL.run(<<-SQL, :key => key, :value => value, :expires => expires || GitHub::SQL::NULL, :connection => connection)
      INSERT IGNORE INTO key_values (`key`, value, created_at, updated_at, expires_at)
      VALUES (:key, :value, NOW(), NOW(), :expires)
    SQL

    sql.affected_rows > 0
  }
end