зеркало из https://github.com/github/github-ds.git
Rename from generic github-data to specific github-kv
This commit is contained in:
Родитель
5904050f4a
Коммит
c3fb2faeb3
|
@ -1,7 +1,7 @@
|
|||
## Contributing
|
||||
|
||||
[fork]: https://github.com/github/github-data/fork
|
||||
[pr]: https://github.com/github/github-data/compare
|
||||
[fork]: https://github.com/github/github-kv/fork
|
||||
[pr]: https://github.com/github/github-kv/compare
|
||||
[style]: https://github.com/styleguide/ruby
|
||||
[code-of-conduct]: CODE_OF_CONDUCT.md
|
||||
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -1,6 +1,6 @@
|
|||
source "https://rubygems.org"
|
||||
|
||||
# Specify your gem's dependencies in github-data.gemspec
|
||||
# Specify your gem's dependencies in github-kv.gemspec
|
||||
gemspec
|
||||
|
||||
gem "rails", "~> #{ENV['RAILS_VERSION'] || '5.0.2'}"
|
||||
|
|
37
README.md
37
README.md
|
@ -1,17 +1,16 @@
|
|||
# Github::Data
|
||||
# Github::KV
|
||||
|
||||
GitHub::Data is a few handy classes on top of ActiveRecord for working with SQL.
|
||||
`GitHub::KV` is a key/value data store backed by MySQL, built on top of `GitHub::KV::SQL` and `GitHub::KV::Result`, each of which are useful on their own apart from KV.
|
||||
|
||||
* `GitHub::Data::KV` is a key/value data store backed by MySQL, built on top of `SQL` and `Result`.
|
||||
* `GitHub::Data::SQL` is for building and executing a SQL query. This class uses ActiveRecord's connection class, but provides a better API for bind values and raw data access.
|
||||
* `GitHub::Data::Result` makes it easier to bake in resiliency through the use of a Result object instead of raising exceptions.
|
||||
* `GitHub::KV::SQL` is for building and executing a SQL query. This class uses ActiveRecord's connection class, but provides a better API for bind values and raw data access.
|
||||
* `GitHub::KV::Result` makes it easier to bake in resiliency through the use of a Result object instead of raising exceptions.
|
||||
|
||||
## Installation
|
||||
|
||||
Add this line to your application's Gemfile:
|
||||
|
||||
```ruby
|
||||
gem 'github-data'
|
||||
gem 'github-kv'
|
||||
```
|
||||
|
||||
And then execute:
|
||||
|
@ -20,16 +19,16 @@ And then execute:
|
|||
|
||||
Or install it yourself as:
|
||||
|
||||
$ gem install github-data
|
||||
$ gem install github-kv
|
||||
|
||||
## Usage
|
||||
|
||||
### GitHub::Data::KV
|
||||
### GitHub::KV
|
||||
|
||||
First, you'll need to create the key_values table using the included Rails migration generator.
|
||||
First, you'll need to create the `key_values` table using the included Rails migration generator.
|
||||
|
||||
```
|
||||
rails generate github:data:active_record
|
||||
rails generate github:kv:active_record
|
||||
rails db:migrate
|
||||
```
|
||||
|
||||
|
@ -37,14 +36,14 @@ Once you have the table, KV can do neat things like this:
|
|||
|
||||
```ruby
|
||||
require "pp"
|
||||
require "github/data/kv"
|
||||
require "github/kv"
|
||||
|
||||
# Create new instance using ActiveRecord's default connection.
|
||||
kv = GitHub::Data::KV.new { ActiveRecord::Base.connection }
|
||||
kv = GitHub::KV.new { ActiveRecord::Base.connection }
|
||||
|
||||
# Get a key.
|
||||
pp kv.get("foo")
|
||||
#<GitHub::Data::Result:0x3fd88cd3ea9c value: nil>
|
||||
#<GitHub::KV::Result:0x3fd88cd3ea9c value: nil>
|
||||
|
||||
# Set a key.
|
||||
kv.set("foo", "bar")
|
||||
|
@ -52,23 +51,23 @@ kv.set("foo", "bar")
|
|||
|
||||
# Get the key again.
|
||||
pp kv.get("foo")
|
||||
#<GitHub::Data::Result:0x3fe810d06e4c value: "bar">
|
||||
#<GitHub::KV::Result:0x3fe810d06e4c value: "bar">
|
||||
|
||||
# Get multiple keys at once.
|
||||
pp kv.mget(["foo", "bar"])
|
||||
#<GitHub::Data::Result:0x3fccccd1b57c value: ["bar", nil]>
|
||||
#<GitHub::KV::Result:0x3fccccd1b57c value: ["bar", nil]>
|
||||
|
||||
# Check for existence of a key.
|
||||
pp kv.exists("foo")
|
||||
#<GitHub::Data::Result:0x3fd4ae55ce8c value: true>
|
||||
#<GitHub::KV::Result:0x3fd4ae55ce8c value: true>
|
||||
|
||||
# Check for existence of key that does not exist.
|
||||
pp kv.exists("bar")
|
||||
#<GitHub::Data::Result:0x3fd4ae55c554 value: false>
|
||||
#<GitHub::KV::Result:0x3fd4ae55c554 value: false>
|
||||
|
||||
# Check for existence of multiple keys at once.
|
||||
pp kv.mexists(["foo", "bar"])
|
||||
#<GitHub::Data::Result:0x3ff1e98e18e8 value: [true, false]>
|
||||
#<GitHub::KV::Result:0x3ff1e98e18e8 value: [true, false]>
|
||||
|
||||
# Set a key's value if the key does not already exist.
|
||||
pp kv.setnx("foo", "bar")
|
||||
|
@ -91,7 +90,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|||
|
||||
## Contributing
|
||||
|
||||
Bug reports and pull requests are welcome on GitHub at https://github.com/github/github-data. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
||||
Bug reports and pull requests are welcome on GitHub at https://github.com/github/github-kv. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
||||
|
||||
|
||||
## License
|
||||
|
|
|
@ -9,7 +9,7 @@ require "active_record"
|
|||
|
||||
ActiveRecord::Base.establish_connection({
|
||||
adapter: "mysql2",
|
||||
database: "github_data_test",
|
||||
database: "github_kv_test",
|
||||
})
|
||||
|
||||
ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS `key_values`")
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
require File.expand_path("../example_setup", __FILE__)
|
||||
require "github/data/kv"
|
||||
require "github/kv"
|
||||
|
||||
# Create new instance using ActiveRecord's default connection.
|
||||
kv = GitHub::Data::KV.new { ActiveRecord::Base.connection }
|
||||
kv = GitHub::KV.new { ActiveRecord::Base.connection }
|
||||
|
||||
# Get a key.
|
||||
pp kv.get("foo")
|
||||
#<GitHub::Data::Result:0x3fd88cd3ea9c value: nil>
|
||||
#<GitHub::KV::Result:0x3fd88cd3ea9c value: nil>
|
||||
|
||||
# Set a key.
|
||||
kv.set("foo", "bar")
|
||||
|
@ -14,23 +14,23 @@ kv.set("foo", "bar")
|
|||
|
||||
# Get the key again.
|
||||
pp kv.get("foo")
|
||||
#<GitHub::Data::Result:0x3fe810d06e4c value: "bar">
|
||||
#<GitHub::KV::Result:0x3fe810d06e4c value: "bar">
|
||||
|
||||
# Get multiple keys at once.
|
||||
pp kv.mget(["foo", "bar"])
|
||||
#<GitHub::Data::Result:0x3fccccd1b57c value: ["bar", nil]>
|
||||
#<GitHub::KV::Result:0x3fccccd1b57c value: ["bar", nil]>
|
||||
|
||||
# Check for existence of a key.
|
||||
pp kv.exists("foo")
|
||||
#<GitHub::Data::Result:0x3fd4ae55ce8c value: true>
|
||||
#<GitHub::KV::Result:0x3fd4ae55ce8c value: true>
|
||||
|
||||
# Check for existence of key that does not exist.
|
||||
pp kv.exists("bar")
|
||||
#<GitHub::Data::Result:0x3fd4ae55c554 value: false>
|
||||
#<GitHub::KV::Result:0x3fd4ae55c554 value: false>
|
||||
|
||||
# Check for existence of multiple keys at once.
|
||||
pp kv.mexists(["foo", "bar"])
|
||||
#<GitHub::Data::Result:0x3ff1e98e18e8 value: [true, false]>
|
||||
#<GitHub::KV::Result:0x3ff1e98e18e8 value: [true, false]>
|
||||
|
||||
# Set a key's value if the key does not already exist.
|
||||
pp kv.setnx("foo", "bar")
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
# coding: utf-8
|
||||
lib = File.expand_path('../lib', __FILE__)
|
||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||
require 'github/data/version'
|
||||
require "github/kv/version"
|
||||
|
||||
Gem::Specification.new do |spec|
|
||||
spec.name = "github-data"
|
||||
spec.version = Github::Data::VERSION
|
||||
spec.name = "github-kv"
|
||||
spec.version = Github::KV::VERSION
|
||||
spec.authors = ["GitHub Open Source", "John Nunemaker"]
|
||||
spec.email = ["opensource+github-data@github.com", "nunemaker@gmail.com"]
|
||||
spec.email = ["opensource+github-kv@github.com", "nunemaker@gmail.com"]
|
||||
|
||||
spec.summary = %q{Useful tools for working with SQL data.}
|
||||
spec.description = %q{Useful tools for working with SQL data.}
|
||||
spec.homepage = "https://github.com/github/github-data"
|
||||
spec.summary = %q{A key/value data store backed by MySQL.}
|
||||
spec.description = %q{A key/value data store backed by MySQL.}
|
||||
spec.homepage = "https://github.com/github/github-kv"
|
||||
spec.license = "MIT"
|
||||
|
||||
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
||||
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
||||
if spec.respond_to?(:metadata)
|
||||
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
||||
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
||||
else
|
||||
raise "RubyGems 2.0 or newer is required to protect against " \
|
||||
"public gem pushes."
|
|
@ -1,7 +1,7 @@
|
|||
require 'rails/generators/active_record'
|
||||
|
||||
module Github
|
||||
module Data
|
||||
class KV
|
||||
module Generators
|
||||
class ActiveRecordGenerator < ::Rails::Generators::Base
|
||||
include ::Rails::Generators::Migration
|
|
@ -1,9 +0,0 @@
|
|||
require "github/data/version"
|
||||
require "github/data/result"
|
||||
require "github/data/sql"
|
||||
require "github/data/kv"
|
||||
|
||||
module Github
|
||||
module Data
|
||||
end
|
||||
end
|
|
@ -1,345 +0,0 @@
|
|||
require "github/data/result"
|
||||
require "github/data/sql"
|
||||
|
||||
# GitHub::Data::KV is a key/value data store backed by MySQL (however, the backing
|
||||
# store used should be regarded as an implementation detail).
|
||||
#
|
||||
# Usage tips:
|
||||
#
|
||||
# * Components in key names should be ordered by cardinality, from lowest to
|
||||
# highest. That is, static key components should be at the front of the key
|
||||
# and key components that vary should be at the end of the key in order of
|
||||
# how many potential values they might have.
|
||||
#
|
||||
# For example, if using GitHub::Data::KV to store a user preferences, the key
|
||||
# should be named "user.#{preference_name}.#{user_id}". Notice that the
|
||||
# part of the key that never changes ("user") comes first, followed by
|
||||
# the name of the preference (of which there might be a handful), followed
|
||||
# finally by the user id (of which there are millions).
|
||||
#
|
||||
# This will make it easier to scan for keys later on, which is a necessity
|
||||
# if we ever need to move this data out of GitHub::Data::KV or if we need to
|
||||
# search the keyspace for some reason (for example, if it's a preference
|
||||
# that we're planning to deprecate, putting the preference name near the
|
||||
# beginning of the key name makes it easier to search for all users with
|
||||
# that preference set).
|
||||
#
|
||||
# * All reader methods in GitHub::Data::KV return values wrapped inside a Result
|
||||
# object.
|
||||
#
|
||||
# If any of these methods raise an exception for some reason (for example,
|
||||
# the database is down), they will return a Result value representing this
|
||||
# error rather than raising the exception directly. See lib/github/data/result.rb
|
||||
# for more documentation on GitHub::Data::Result including usage examples.
|
||||
#
|
||||
# When using GitHub::Data::KV, it's important to handle error conditions and not
|
||||
# assume that GitHub::Data::Result objects will always represent success.
|
||||
# Code using GitHub::Data::KV should be able to fail partially if
|
||||
# GitHub::Data::KV is down. How exactly to do this will depend on a
|
||||
# case-by-case basis - it may involve falling back to a default value, or it
|
||||
# might involve showing an error message to the user while still letting the
|
||||
# rest of the page load.
|
||||
#
|
||||
module GitHub
|
||||
module Data
|
||||
class KV
|
||||
MAX_KEY_LENGTH = 255
|
||||
MAX_VALUE_LENGTH = 65535
|
||||
|
||||
KeyLengthError = Class.new(StandardError)
|
||||
ValueLengthError = Class.new(StandardError)
|
||||
UnavailableError = Class.new(StandardError)
|
||||
|
||||
class MissingConnectionError < StandardError; end
|
||||
|
||||
def initialize(encapsulated_errors = [SystemCallError], &conn_block)
|
||||
@encapsulated_errors = encapsulated_errors
|
||||
@conn_block = conn_block
|
||||
end
|
||||
|
||||
def connection
|
||||
@conn_block.try(:call) || (raise MissingConnectionError, "KV must be initialized with a block that returns a connection")
|
||||
end
|
||||
|
||||
# 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>
|
||||
#
|
||||
def get(key)
|
||||
validate_key(key)
|
||||
|
||||
mget([key]).map { |values| values[0] }
|
||||
end
|
||||
|
||||
# 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]
|
||||
#
|
||||
def mget(keys)
|
||||
validate_key_array(keys)
|
||||
|
||||
Result.new {
|
||||
kvs = GitHub::Data::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
|
||||
|
||||
# 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
|
||||
#
|
||||
def set(key, value, expires: nil)
|
||||
validate_key(key)
|
||||
validate_value(value)
|
||||
|
||||
mset({ key => value }, expires: expires)
|
||||
end
|
||||
|
||||
# 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
|
||||
#
|
||||
def mset(kvs, expires: nil)
|
||||
validate_key_value_hash(kvs)
|
||||
validate_expires(expires) if expires
|
||||
|
||||
rows = kvs.map { |key, value|
|
||||
[key, value, GitHub::Data::SQL::NOW, GitHub::Data::SQL::NOW, expires || GitHub::Data::SQL::NULL]
|
||||
}
|
||||
|
||||
encapsulate_error do
|
||||
GitHub::Data::SQL.run(<<-SQL, :rows => GitHub::Data::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
|
||||
|
||||
# exists :: String -> Result<Boolean>
|
||||
#
|
||||
# Checks for existence of the specified key.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# kv.exists("foo")
|
||||
# # => #<Result value: true>
|
||||
#
|
||||
# kv.exists("octocat")
|
||||
# # => #<Result value: false>
|
||||
#
|
||||
def exists(key)
|
||||
validate_key(key)
|
||||
|
||||
mexists([key]).map { |values| values[0] }
|
||||
end
|
||||
|
||||
# mexists :: [String] -> Result<[Boolean]>
|
||||
#
|
||||
# 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]>
|
||||
#
|
||||
def mexists(keys)
|
||||
validate_key_array(keys)
|
||||
|
||||
Result.new {
|
||||
existing_keys = GitHub::Data::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
|
||||
|
||||
# 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
|
||||
#
|
||||
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::Data::SQL.run(<<-SQL, :key => key, :connection => connection)
|
||||
DELETE FROM key_values WHERE `key` = :key AND expires_at <= NOW()
|
||||
SQL
|
||||
|
||||
sql = GitHub::Data::SQL.run(<<-SQL, :key => key, :value => value, :expires => expires || GitHub::Data::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
|
||||
|
||||
# del :: String -> nil
|
||||
#
|
||||
# Deletes the specified key. Returns nil. Raises on error.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# kv.del("foo")
|
||||
# # => nil
|
||||
#
|
||||
def del(key)
|
||||
validate_key(key)
|
||||
|
||||
mdel([key])
|
||||
end
|
||||
|
||||
# mdel :: String -> nil
|
||||
#
|
||||
# Deletes the specified keys. Returns nil. Raises on error.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# kv.mdel(["foo", "octocat"])
|
||||
# # => nil
|
||||
#
|
||||
def mdel(keys)
|
||||
validate_key_array(keys)
|
||||
|
||||
encapsulate_error do
|
||||
GitHub::Data::SQL.run(<<-SQL, :keys => keys, :connection => connection)
|
||||
DELETE FROM key_values WHERE `key` IN :keys
|
||||
SQL
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
def validate_key(key)
|
||||
raise TypeError, "key must be a String in #{self.class.name}, but was #{key.class}" unless key.is_a?(String)
|
||||
|
||||
validate_key_length(key)
|
||||
end
|
||||
|
||||
def validate_value(value)
|
||||
raise TypeError, "value must be a String in #{self.class.name}, but was #{value.class}" unless value.is_a?(String)
|
||||
|
||||
validate_value_length(value)
|
||||
end
|
||||
|
||||
def validate_key_array(keys)
|
||||
unless keys.is_a?(Array)
|
||||
raise TypeError, "keys must be a [String] in #{self.class.name}, but was #{keys.class}"
|
||||
end
|
||||
|
||||
keys.each do |key|
|
||||
unless key.is_a?(String)
|
||||
raise TypeError, "keys must be a [String] in #{self.class.name}, but also saw at least one #{key.class}"
|
||||
end
|
||||
|
||||
validate_key_length(key)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_key_value_hash(kvs)
|
||||
unless kvs.is_a?(Hash)
|
||||
raise TypeError, "kvs must be a {String => String} in #{self.class.name}, but was #{key.class}"
|
||||
end
|
||||
|
||||
kvs.each do |key, value|
|
||||
unless key.is_a?(String)
|
||||
raise TypeError, "kvs must be a {String => String} in #{self.class.name}, but also saw at least one key of type #{key.class}"
|
||||
end
|
||||
|
||||
unless value.is_a?(String)
|
||||
raise TypeError, "kvs must be a {String => String} in #{self.class.name}, but also saw at least one value of type #{value.class}"
|
||||
end
|
||||
|
||||
validate_key_length(key)
|
||||
validate_value_length(value)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_key_length(key)
|
||||
if key.length > MAX_KEY_LENGTH
|
||||
raise KeyLengthError, "key of length #{key.length} exceeds maximum key length of #{MAX_KEY_LENGTH}\n\nkey: #{key.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def validate_value_length(value)
|
||||
if value.length > MAX_VALUE_LENGTH
|
||||
raise ValueLengthError, "value of length #{value.length} exceeds maximum value length of #{MAX_VALUE_LENGTH}"
|
||||
end
|
||||
end
|
||||
|
||||
def validate_expires(expires)
|
||||
unless expires.respond_to?(:to_time)
|
||||
raise TypeError, "expires must be a time of some sort (Time, DateTime, ActiveSupport::TimeWithZone, etc.), but was #{expires.class}"
|
||||
end
|
||||
end
|
||||
|
||||
def encapsulate_error
|
||||
yield
|
||||
rescue *@encapsulated_errors => error
|
||||
raise UnavailableError, "#{error.class}: #{error.message}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,344 @@
|
|||
require "github/kv/version"
|
||||
require "github/kv/result"
|
||||
require "github/kv/sql"
|
||||
|
||||
# GitHub::KV is a key/value data store backed by MySQL (however, the backing
|
||||
# store used should be regarded as an implementation detail).
|
||||
#
|
||||
# Usage tips:
|
||||
#
|
||||
# * Components in key names should be ordered by cardinality, from lowest to
|
||||
# highest. That is, static key components should be at the front of the key
|
||||
# and key components that vary should be at the end of the key in order of
|
||||
# how many potential values they might have.
|
||||
#
|
||||
# For example, if using GitHub::KV to store a user preferences, the key
|
||||
# should be named "user.#{preference_name}.#{user_id}". Notice that the
|
||||
# part of the key that never changes ("user") comes first, followed by
|
||||
# the name of the preference (of which there might be a handful), followed
|
||||
# finally by the user id (of which there are millions).
|
||||
#
|
||||
# This will make it easier to scan for keys later on, which is a necessity
|
||||
# if we ever need to move this data out of GitHub::KV or if we need to
|
||||
# search the keyspace for some reason (for example, if it's a preference
|
||||
# that we're planning to deprecate, putting the preference name near the
|
||||
# beginning of the key name makes it easier to search for all users with
|
||||
# that preference set).
|
||||
#
|
||||
# * All reader methods in GitHub::KV return values wrapped inside a Result
|
||||
# object.
|
||||
#
|
||||
# If any of these methods raise an exception for some reason (for example,
|
||||
# the database is down), they will return a Result value representing this
|
||||
# error rather than raising the exception directly. See lib/github/kv/result.rb
|
||||
# for more documentation on GitHub::KV::Result including usage examples.
|
||||
#
|
||||
# When using GitHub::KV, it's important to handle error conditions and not
|
||||
# assume that GitHub::KV::Result objects will always represent success.
|
||||
# Code using GitHub::KV should be able to fail partially if
|
||||
# GitHub::KV is down. How exactly to do this will depend on a
|
||||
# case-by-case basis - it may involve falling back to a default value, or it
|
||||
# might involve showing an error message to the user while still letting the
|
||||
# rest of the page load.
|
||||
#
|
||||
module GitHub
|
||||
class KV
|
||||
MAX_KEY_LENGTH = 255
|
||||
MAX_VALUE_LENGTH = 65535
|
||||
|
||||
KeyLengthError = Class.new(StandardError)
|
||||
ValueLengthError = Class.new(StandardError)
|
||||
UnavailableError = Class.new(StandardError)
|
||||
|
||||
class MissingConnectionError < StandardError; end
|
||||
|
||||
def initialize(encapsulated_errors = [SystemCallError], &conn_block)
|
||||
@encapsulated_errors = encapsulated_errors
|
||||
@conn_block = conn_block
|
||||
end
|
||||
|
||||
def connection
|
||||
@conn_block.try(:call) || (raise MissingConnectionError, "KV must be initialized with a block that returns a connection")
|
||||
end
|
||||
|
||||
# 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>
|
||||
#
|
||||
def get(key)
|
||||
validate_key(key)
|
||||
|
||||
mget([key]).map { |values| values[0] }
|
||||
end
|
||||
|
||||
# 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]
|
||||
#
|
||||
def mget(keys)
|
||||
validate_key_array(keys)
|
||||
|
||||
Result.new {
|
||||
kvs = GitHub::KV::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
|
||||
|
||||
# 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
|
||||
#
|
||||
def set(key, value, expires: nil)
|
||||
validate_key(key)
|
||||
validate_value(value)
|
||||
|
||||
mset({ key => value }, expires: expires)
|
||||
end
|
||||
|
||||
# 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
|
||||
#
|
||||
def mset(kvs, expires: nil)
|
||||
validate_key_value_hash(kvs)
|
||||
validate_expires(expires) if expires
|
||||
|
||||
rows = kvs.map { |key, value|
|
||||
[key, value, GitHub::KV::SQL::NOW, GitHub::KV::SQL::NOW, expires || GitHub::KV::SQL::NULL]
|
||||
}
|
||||
|
||||
encapsulate_error do
|
||||
GitHub::KV::SQL.run(<<-SQL, :rows => GitHub::KV::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
|
||||
|
||||
# exists :: String -> Result<Boolean>
|
||||
#
|
||||
# Checks for existence of the specified key.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# kv.exists("foo")
|
||||
# # => #<Result value: true>
|
||||
#
|
||||
# kv.exists("octocat")
|
||||
# # => #<Result value: false>
|
||||
#
|
||||
def exists(key)
|
||||
validate_key(key)
|
||||
|
||||
mexists([key]).map { |values| values[0] }
|
||||
end
|
||||
|
||||
# mexists :: [String] -> Result<[Boolean]>
|
||||
#
|
||||
# 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]>
|
||||
#
|
||||
def mexists(keys)
|
||||
validate_key_array(keys)
|
||||
|
||||
Result.new {
|
||||
existing_keys = GitHub::KV::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
|
||||
|
||||
# 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
|
||||
#
|
||||
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::KV::SQL.run(<<-SQL, :key => key, :connection => connection)
|
||||
DELETE FROM key_values WHERE `key` = :key AND expires_at <= NOW()
|
||||
SQL
|
||||
|
||||
sql = GitHub::KV::SQL.run(<<-SQL, :key => key, :value => value, :expires => expires || GitHub::KV::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
|
||||
|
||||
# del :: String -> nil
|
||||
#
|
||||
# Deletes the specified key. Returns nil. Raises on error.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# kv.del("foo")
|
||||
# # => nil
|
||||
#
|
||||
def del(key)
|
||||
validate_key(key)
|
||||
|
||||
mdel([key])
|
||||
end
|
||||
|
||||
# mdel :: String -> nil
|
||||
#
|
||||
# Deletes the specified keys. Returns nil. Raises on error.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# kv.mdel(["foo", "octocat"])
|
||||
# # => nil
|
||||
#
|
||||
def mdel(keys)
|
||||
validate_key_array(keys)
|
||||
|
||||
encapsulate_error do
|
||||
GitHub::KV::SQL.run(<<-SQL, :keys => keys, :connection => connection)
|
||||
DELETE FROM key_values WHERE `key` IN :keys
|
||||
SQL
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
def validate_key(key)
|
||||
raise TypeError, "key must be a String in #{self.class.name}, but was #{key.class}" unless key.is_a?(String)
|
||||
|
||||
validate_key_length(key)
|
||||
end
|
||||
|
||||
def validate_value(value)
|
||||
raise TypeError, "value must be a String in #{self.class.name}, but was #{value.class}" unless value.is_a?(String)
|
||||
|
||||
validate_value_length(value)
|
||||
end
|
||||
|
||||
def validate_key_array(keys)
|
||||
unless keys.is_a?(Array)
|
||||
raise TypeError, "keys must be a [String] in #{self.class.name}, but was #{keys.class}"
|
||||
end
|
||||
|
||||
keys.each do |key|
|
||||
unless key.is_a?(String)
|
||||
raise TypeError, "keys must be a [String] in #{self.class.name}, but also saw at least one #{key.class}"
|
||||
end
|
||||
|
||||
validate_key_length(key)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_key_value_hash(kvs)
|
||||
unless kvs.is_a?(Hash)
|
||||
raise TypeError, "kvs must be a {String => String} in #{self.class.name}, but was #{key.class}"
|
||||
end
|
||||
|
||||
kvs.each do |key, value|
|
||||
unless key.is_a?(String)
|
||||
raise TypeError, "kvs must be a {String => String} in #{self.class.name}, but also saw at least one key of type #{key.class}"
|
||||
end
|
||||
|
||||
unless value.is_a?(String)
|
||||
raise TypeError, "kvs must be a {String => String} in #{self.class.name}, but also saw at least one value of type #{value.class}"
|
||||
end
|
||||
|
||||
validate_key_length(key)
|
||||
validate_value_length(value)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_key_length(key)
|
||||
if key.length > MAX_KEY_LENGTH
|
||||
raise KeyLengthError, "key of length #{key.length} exceeds maximum key length of #{MAX_KEY_LENGTH}\n\nkey: #{key.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def validate_value_length(value)
|
||||
if value.length > MAX_VALUE_LENGTH
|
||||
raise ValueLengthError, "value of length #{value.length} exceeds maximum value length of #{MAX_VALUE_LENGTH}"
|
||||
end
|
||||
end
|
||||
|
||||
def validate_expires(expires)
|
||||
unless expires.respond_to?(:to_time)
|
||||
raise TypeError, "expires must be a time of some sort (Time, DateTime, ActiveSupport::TimeWithZone, etc.), but was #{expires.class}"
|
||||
end
|
||||
end
|
||||
|
||||
def encapsulate_error
|
||||
yield
|
||||
rescue *@encapsulated_errors => error
|
||||
raise UnavailableError, "#{error.class}: #{error.message}"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,18 +1,18 @@
|
|||
module GitHub
|
||||
module Data
|
||||
class KV
|
||||
class Result
|
||||
# Invokes the supplied block and wraps the return value in a
|
||||
# GitHub::Data::Result object.
|
||||
# GitHub::KV::Result object.
|
||||
#
|
||||
# Exceptions raised by the block are caught and also wrapped.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# GitHub::Data::Result.new { 123 }
|
||||
# # => #<GitHub::Data::Result value: 123>
|
||||
# GitHub::KV::Result.new { 123 }
|
||||
# # => #<GitHub::KV::Result value: 123>
|
||||
#
|
||||
# GitHub::Data::Result.new { raise "oops" }
|
||||
# # => #<GitHub::Data::Result error: #<RuntimeError: oops>>
|
||||
# GitHub::KV::Result.new { raise "oops" }
|
||||
# # => #<GitHub::KV::Result error: #<RuntimeError: oops>>
|
||||
#
|
||||
def initialize
|
||||
begin
|
||||
|
@ -25,9 +25,9 @@ module GitHub
|
|||
|
||||
def to_s
|
||||
if ok?
|
||||
"#<GitHub::Data::Result:0x%x value: %s>" % [object_id, @value.inspect]
|
||||
"#<GitHub::KV::Result:0x%x value: %s>" % [object_id, @value.inspect]
|
||||
else
|
||||
"#<GitHub::Data::Result:0x%x error: %s>" % [object_id, @error.inspect]
|
||||
"#<GitHub::KV::Result:0x%x error: %s>" % [object_id, @error.inspect]
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -38,7 +38,7 @@ module GitHub
|
|||
#
|
||||
# If the result represents an error, returns self.
|
||||
#
|
||||
# The block must also return a GitHub::Data::Result object.
|
||||
# The block must also return a GitHub::KV::Result object.
|
||||
# Use #map otherwise.
|
||||
#
|
||||
# Example:
|
||||
|
@ -46,17 +46,17 @@ module GitHub
|
|||
# result = do_something().then { |val|
|
||||
# do_other_thing(val)
|
||||
# }
|
||||
# # => #<GitHub::Data::Result value: ...>
|
||||
# # => #<GitHub::KV::Result value: ...>
|
||||
#
|
||||
# do_something_that_fails().then { |val|
|
||||
# # never invoked
|
||||
# }
|
||||
# # => #<GitHub::Data::Result error: ...>
|
||||
# # => #<GitHub::KV::Result error: ...>
|
||||
#
|
||||
def then
|
||||
if ok?
|
||||
result = yield(@value)
|
||||
raise TypeError, "block invoked in GitHub::Data::Result#then did not return GitHub::Data::Result" unless result.is_a?(Result)
|
||||
raise TypeError, "block invoked in GitHub::KV::Result#then did not return GitHub::KV::Result" unless result.is_a?(Result)
|
||||
result
|
||||
else
|
||||
self
|
||||
|
@ -67,7 +67,7 @@ module GitHub
|
|||
#
|
||||
# If the result represents a value, returns self.
|
||||
#
|
||||
# The block must also return a GitHub::Data::Result object.
|
||||
# The block must also return a GitHub::KV::Result object.
|
||||
# Use #map otherwise.
|
||||
#
|
||||
# Example:
|
||||
|
@ -75,41 +75,41 @@ module GitHub
|
|||
# result = do_something().rescue { |val|
|
||||
# # never invoked
|
||||
# }
|
||||
# # => #<GitHub::Data::Result value: ...>
|
||||
# # => #<GitHub::KV::Result value: ...>
|
||||
#
|
||||
# do_something_that_fails().rescue { |val|
|
||||
# # handle_error(val)
|
||||
# }
|
||||
# # => #<GitHub::Data::Result error: ...>
|
||||
# # => #<GitHub::KV::Result error: ...>
|
||||
#
|
||||
def rescue
|
||||
return self if ok?
|
||||
result = yield(@error)
|
||||
raise TypeError, "block invoked in GitHub::Data::Result#rescue did not return GitHub::Data::Result" unless result.is_a?(Result)
|
||||
raise TypeError, "block invoked in GitHub::KV::Result#rescue did not return GitHub::KV::Result" unless result.is_a?(Result)
|
||||
result
|
||||
end
|
||||
|
||||
# If the result represents a value, invokes the supplied block with that
|
||||
# value and wraps the block's return value in a GitHub::Data::Result.
|
||||
# value and wraps the block's return value in a GitHub::KV::Result.
|
||||
#
|
||||
# If the result represents an error, returns self.
|
||||
#
|
||||
# The block should not return a GitHub::Data::Result object (unless you
|
||||
# truly intend to create a GitHub::Data::Result<GitHub::Data::Result<T>>).
|
||||
# The block should not return a GitHub::KV::Result object (unless you
|
||||
# truly intend to create a GitHub::KV::Result<GitHub::KV::Result<T>>).
|
||||
# Use #then if it does.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# result = do_something()
|
||||
# # => #<GitHub::Data::Result value: 123>
|
||||
# # => #<GitHub::KV::Result value: 123>
|
||||
#
|
||||
# result.map { |val| val * 2 }
|
||||
# # => #<GitHub::Data::Result value: 246>
|
||||
# # => #<GitHub::KV::Result value: 246>
|
||||
#
|
||||
# do_something_that_fails().map { |val|
|
||||
# # never invoked
|
||||
# }
|
||||
# # => #<GitHub::Data::Result error: ...>
|
||||
# # => #<GitHub::KV::Result error: ...>
|
||||
#
|
||||
def map
|
||||
if ok?
|
||||
|
@ -127,20 +127,20 @@ module GitHub
|
|||
# Example:
|
||||
#
|
||||
# result = do_something()
|
||||
# # => #<GitHub::Data::Result value: "foo">
|
||||
# # => #<GitHub::KV::Result value: "foo">
|
||||
#
|
||||
# result.value { "nope" }
|
||||
# # => "foo"
|
||||
#
|
||||
# result = do_something_that_fails()
|
||||
# # => #<GitHub::Data::Result error: ...>
|
||||
# # => #<GitHub::KV::Result error: ...>
|
||||
#
|
||||
# result.value { "nope" }
|
||||
# # => #<GitHub::Data::Result value: "nope">
|
||||
# # => #<GitHub::KV::Result value: "nope">
|
||||
#
|
||||
def value
|
||||
unless block_given?
|
||||
raise ArgumentError, "must provide a block to GitHub::Data::Result#value to be invoked in case of error"
|
||||
raise ArgumentError, "must provide a block to GitHub::KV::Result#value to be invoked in case of error"
|
||||
end
|
||||
|
||||
if ok?
|
||||
|
@ -157,13 +157,13 @@ module GitHub
|
|||
# Example:
|
||||
#
|
||||
# result = do_something()
|
||||
# # => #<GitHub::Data::Result value: "foo">
|
||||
# # => #<GitHub::KV::Result value: "foo">
|
||||
#
|
||||
# result.value!
|
||||
# # => "foo"
|
||||
#
|
||||
# result = do_something_that_fails()
|
||||
# # => #<GitHub::Data::Result error: ...>
|
||||
# # => #<GitHub::KV::Result error: ...>
|
||||
#
|
||||
# result.value!
|
||||
# # !! raises exception
|
||||
|
@ -181,13 +181,13 @@ module GitHub
|
|||
# Example:
|
||||
#
|
||||
# result = do_something()
|
||||
# # => #<GitHub::Data::Result value: "foo">
|
||||
# # => #<GitHub::KV::Result value: "foo">
|
||||
#
|
||||
# result.ok?
|
||||
# # => true
|
||||
#
|
||||
# result = do_something_that_fails()
|
||||
# # => #<GitHub::Data::Result error: ...>
|
||||
# # => #<GitHub::KV::Result error: ...>
|
||||
#
|
||||
# result.ok?
|
||||
# # => false
|
||||
|
@ -201,13 +201,13 @@ module GitHub
|
|||
# If the result represents an error, returns that error.
|
||||
#
|
||||
# result = do_something()
|
||||
# # => #<GitHub::Data::Result value: "foo">
|
||||
# # => #<GitHub::KV::Result value: "foo">
|
||||
#
|
||||
# result.error
|
||||
# # => nil
|
||||
#
|
||||
# result = do_something_that_fails()
|
||||
# # => #<GitHub::Data::Result error: ...>
|
||||
# # => #<GitHub::KV::Result error: ...>
|
||||
#
|
||||
# result.error
|
||||
# # => ...
|
||||
|
@ -216,10 +216,10 @@ module GitHub
|
|||
@error
|
||||
end
|
||||
|
||||
# Create a GitHub::Data::Result with only the error condition set.
|
||||
# Create a GitHub::KV::Result with only the error condition set.
|
||||
#
|
||||
# GitHub::Data::Result.error(e)
|
||||
# # => # <GitHub::Data::Result error: ...>
|
||||
# GitHub::KV::Result.error(e)
|
||||
# # => # <GitHub::KV::Result error: ...>
|
||||
#
|
||||
def self.error(e)
|
||||
result = allocate
|
|
@ -2,14 +2,14 @@ require "active_record"
|
|||
require "active_support/all"
|
||||
|
||||
module GitHub
|
||||
module Data
|
||||
class KV
|
||||
# Public: Build and execute a SQL query, returning results as Arrays. This
|
||||
# class uses ActiveRecord's connection classes, but provides a better API for
|
||||
# bind values and raw data access.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# sql = GitHub::Data::SQL.new(<<-SQL, :parent_ids => parent_ids, :network_id => network_id)
|
||||
# sql = GitHub::KV::SQL.new(<<-SQL, :parent_ids => parent_ids, :network_id => network_id)
|
||||
# SELECT * FROM repositories
|
||||
# WHERE source_id = :network_id AND parent_id IN :parent_ids
|
||||
# SQL
|
||||
|
@ -30,7 +30,7 @@ module GitHub
|
|||
#
|
||||
# * Arrays are escaped as `(item, item, item)`. If you need to insert multiple
|
||||
# rows (Arrays of Arrays), you must specify the bind value using
|
||||
# GitHub::Data::SQL::ROWS(array_of_arrays).
|
||||
# GitHub::KV::SQL::ROWS(array_of_arrays).
|
||||
#
|
||||
class SQL
|
||||
|
||||
|
@ -78,7 +78,7 @@ module GitHub
|
|||
# Used when a column contains binary data which needs to be escaped
|
||||
# to prevent warnings from MySQL
|
||||
def self.BINARY(string)
|
||||
GitHub::Data::SQL.LITERAL(GitHub::Data::SQL.BINARY_LITERAL(string))
|
||||
GitHub::KV::SQL.LITERAL(GitHub::KV::SQL.BINARY_LITERAL(string))
|
||||
end
|
||||
|
||||
# Public: Escape a binary SQL value, yielding a string which can be used as
|
||||
|
@ -158,7 +158,7 @@ module GitHub
|
|||
# aren't available to subsequent adds.
|
||||
#
|
||||
# Returns self.
|
||||
# Raises GitHub::Data::SQL::BadBind for unknown keyword tokens.
|
||||
# Raises GitHub::KV::SQL::BadBind for unknown keyword tokens.
|
||||
def add(sql, extras = nil)
|
||||
return self if sql.blank?
|
||||
|
||||
|
@ -186,7 +186,7 @@ module GitHub
|
|||
# aren't available to subsequent adds.
|
||||
#
|
||||
# Returns self.
|
||||
# Raises GitHub::Data::SQL::BadBind for unknown keyword tokens.
|
||||
# Raises GitHub::KV::SQL::BadBind for unknown keyword tokens.
|
||||
def add_unless_empty(sql, extras = nil)
|
||||
return self if query.empty?
|
||||
add sql, extras
|
||||
|
@ -315,8 +315,8 @@ module GitHub
|
|||
# Public: Execute, ignoring results. This is useful when the results of a
|
||||
# query aren't important, often INSERTs, UPDATEs, or DELETEs.
|
||||
#
|
||||
# sql - An optional SQL string. See GitHub::Data::SQL#add for details.
|
||||
# extras - Optional bind values. See GitHub::Data::SQL#add for details.
|
||||
# sql - An optional SQL string. See GitHub::KV::SQL#add for details.
|
||||
# extras - Optional bind values. See GitHub::KV::SQL#add for details.
|
||||
#
|
||||
# Returns self.
|
||||
def run(sql = nil, extras = nil)
|
||||
|
@ -337,8 +337,8 @@ module GitHub
|
|||
|
||||
# Public: Create and execute a new SQL query, ignoring results.
|
||||
#
|
||||
# sql - A SQL string. See GitHub::Data::SQL#add for details.
|
||||
# bindings - Optional bind values. See GitHub::Data::SQL#add for details.
|
||||
# sql - A SQL string. See GitHub::KV::SQL#add for details.
|
||||
# bindings - Optional bind values. See GitHub::KV::SQL#add for details.
|
||||
#
|
||||
# Returns self.
|
||||
def self.run(sql, bindings = {})
|
||||
|
@ -347,8 +347,8 @@ module GitHub
|
|||
|
||||
# Public: Create and execute a new SQL query, returning its hash_result rows.
|
||||
#
|
||||
# sql - A SQL string. See GitHub::Data::SQL#add for details.
|
||||
# bindings - Optional bind values. See GitHub::Data::SQL#add for details.
|
||||
# sql - A SQL string. See GitHub::KV::SQL#add for details.
|
||||
# bindings - Optional bind values. See GitHub::KV::SQL#add for details.
|
||||
#
|
||||
# Returns an Array of result hashes.
|
||||
def self.hash_results(sql, bindings = {})
|
||||
|
@ -357,8 +357,8 @@ module GitHub
|
|||
|
||||
# Public: Create and execute a new SQL query, returning its result rows.
|
||||
#
|
||||
# sql - A SQL string. See GitHub::Data::SQL#add for details.
|
||||
# bindings - Optional bind values. See GitHub::Data::SQL#add for details.
|
||||
# sql - A SQL string. See GitHub::KV::SQL#add for details.
|
||||
# bindings - Optional bind values. See GitHub::KV::SQL#add for details.
|
||||
#
|
||||
# Returns an Array of result arrays.
|
||||
def self.results(sql, bindings = {})
|
||||
|
@ -368,8 +368,8 @@ module GitHub
|
|||
# Public: Create and execute a new SQL query, returning the value of the
|
||||
# first column of the first result row.
|
||||
#
|
||||
# sql - A SQL string. See GitHub::Data::SQL#add for details.
|
||||
# bindings - Optional bind values. See GitHub::Data::SQL#add for details.
|
||||
# sql - A SQL string. See GitHub::KV::SQL#add for details.
|
||||
# bindings - Optional bind values. See GitHub::KV::SQL#add for details.
|
||||
#
|
||||
# Returns a value or nil.
|
||||
def self.value(sql, bindings = {})
|
||||
|
@ -378,8 +378,8 @@ module GitHub
|
|||
|
||||
# Public: Create and execute a new SQL query, returning its values.
|
||||
#
|
||||
# sql - A SQL string. See GitHub::Data::SQL#add for details.
|
||||
# bindings - Optional bind values. See GitHub::Data::SQL#add for details.
|
||||
# sql - A SQL string. See GitHub::KV::SQL#add for details.
|
||||
# bindings - Optional bind values. See GitHub::KV::SQL#add for details.
|
||||
#
|
||||
# Returns an Array of values.
|
||||
def self.values(sql, bindings = {})
|
|
@ -1,5 +1,5 @@
|
|||
module Github
|
||||
module Data
|
||||
class KV
|
||||
VERSION = "0.1.0"
|
||||
end
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require "bundler/setup"
|
||||
require "github/data"
|
||||
require "github/kv"
|
||||
|
||||
# You can add fixtures and/or initialization code here to make experimenting
|
||||
# with your gem easier. You can also use a different console, if you like.
|
||||
|
|
|
@ -3,10 +3,10 @@ require "rails"
|
|||
require "rails/test_help"
|
||||
require "active_record"
|
||||
require "rails/generators/test_case"
|
||||
require "generators/github/data/active_record_generator"
|
||||
require "generators/github/kv/active_record_generator"
|
||||
|
||||
class GitHubDataActiveRecordGeneratorTest < Rails::Generators::TestCase
|
||||
tests Github::Data::Generators::ActiveRecordGenerator
|
||||
class GithubKVActiveRecordGeneratorTest < Rails::Generators::TestCase
|
||||
tests Github::KV::Generators::ActiveRecordGenerator
|
||||
destination File.expand_path("../../../../tmp", __FILE__)
|
||||
setup :prepare_destination
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
require 'test_helper'
|
||||
|
||||
class GitHub::Data::ResultTest < Minitest::Test
|
||||
def test_to_s
|
||||
assert_match %r{#<GitHub::Data::Result:0x[a-f0-9]+ value: 123>}, GitHub::Data::Result.new { 123 }.to_s
|
||||
|
||||
assert_match %r{#<GitHub::Data::Result:0x[a-f0-9]+ error: #<RuntimeError: nope>>}, GitHub::Data::Result.new { raise "nope" }.to_s
|
||||
end
|
||||
|
||||
def test_then
|
||||
assert_equal 456, GitHub::Data::Result.new { 123 }.then {
|
||||
GitHub::Data::Result.new { 456 }
|
||||
}.value!
|
||||
|
||||
assert GitHub::Data::Result.new { raise "nope" }.then {
|
||||
flunk "should not have invoked then block"
|
||||
}.error
|
||||
|
||||
assert_raises TypeError do
|
||||
GitHub::Data::Result.new {}.then {
|
||||
"not a result"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def test_rescue
|
||||
assert_equal 456, GitHub::Data::Result.new { raise "nope" }.rescue {
|
||||
GitHub::Data::Result.new { 456 }
|
||||
}.value!
|
||||
|
||||
assert_equal 456, GitHub::Data::Result.new { raise "nope" }.rescue { |error|
|
||||
assert_equal "nope", error.message
|
||||
GitHub::Data::Result.new { 456 }
|
||||
}.value!
|
||||
|
||||
assert GitHub::Data::Result.new { 123 }.rescue {
|
||||
flunk "should not have invoked rescue block"
|
||||
}.value!
|
||||
|
||||
assert_raises TypeError do
|
||||
GitHub::Data::Result.new { raise "nope" }.rescue {
|
||||
"not a result"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def test_map
|
||||
assert_equal 456, GitHub::Data::Result.new { 123 }.map {
|
||||
456
|
||||
}.value!
|
||||
|
||||
assert GitHub::Data::Result.new { raise "nope" }.map {
|
||||
flunk "should not have invoked map block"
|
||||
}.error
|
||||
end
|
||||
|
||||
def test_value
|
||||
assert_equal 123, GitHub::Data::Result.new { 123 }.value { 456 }
|
||||
|
||||
assert_equal 456, GitHub::Data::Result.new { raise "nope" }.value { 456 }
|
||||
end
|
||||
|
||||
def test_value!
|
||||
assert_equal 123, GitHub::Data::Result.new { 123 }.value!
|
||||
|
||||
r = GitHub::Data::Result.new { raise "nope" }
|
||||
|
||||
assert_raises RuntimeError do
|
||||
r.value!
|
||||
end
|
||||
end
|
||||
|
||||
def test_ok?
|
||||
assert_predicate GitHub::Data::Result.new { 123 }, :ok?
|
||||
|
||||
refute_predicate GitHub::Data::Result.new { raise "nope" }, :ok?
|
||||
end
|
||||
|
||||
def test_error
|
||||
assert_nil GitHub::Data::Result.new { 123 }.error
|
||||
|
||||
e = StandardError.new("nope")
|
||||
|
||||
assert_equal e, GitHub::Data::Result.new { raise e }.error
|
||||
end
|
||||
end
|
|
@ -1,7 +0,0 @@
|
|||
require 'test_helper'
|
||||
|
||||
class Github::DataTest < Minitest::Test
|
||||
def test_that_it_has_a_version_number
|
||||
refute_nil ::Github::Data::VERSION
|
||||
end
|
||||
end
|
|
@ -0,0 +1,86 @@
|
|||
require 'test_helper'
|
||||
|
||||
class GitHub::KV::ResultTest < Minitest::Test
|
||||
def test_to_s
|
||||
assert_match %r{#<GitHub::KV::Result:0x[a-f0-9]+ value: 123>}, GitHub::KV::Result.new { 123 }.to_s
|
||||
|
||||
assert_match %r{#<GitHub::KV::Result:0x[a-f0-9]+ error: #<RuntimeError: nope>>}, GitHub::KV::Result.new { raise "nope" }.to_s
|
||||
end
|
||||
|
||||
def test_then
|
||||
assert_equal 456, GitHub::KV::Result.new { 123 }.then {
|
||||
GitHub::KV::Result.new { 456 }
|
||||
}.value!
|
||||
|
||||
assert GitHub::KV::Result.new { raise "nope" }.then {
|
||||
flunk "should not have invoked then block"
|
||||
}.error
|
||||
|
||||
assert_raises TypeError do
|
||||
GitHub::KV::Result.new {}.then {
|
||||
"not a result"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def test_rescue
|
||||
assert_equal 456, GitHub::KV::Result.new { raise "nope" }.rescue {
|
||||
GitHub::KV::Result.new { 456 }
|
||||
}.value!
|
||||
|
||||
assert_equal 456, GitHub::KV::Result.new { raise "nope" }.rescue { |error|
|
||||
assert_equal "nope", error.message
|
||||
GitHub::KV::Result.new { 456 }
|
||||
}.value!
|
||||
|
||||
assert GitHub::KV::Result.new { 123 }.rescue {
|
||||
flunk "should not have invoked rescue block"
|
||||
}.value!
|
||||
|
||||
assert_raises TypeError do
|
||||
GitHub::KV::Result.new { raise "nope" }.rescue {
|
||||
"not a result"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def test_map
|
||||
assert_equal 456, GitHub::KV::Result.new { 123 }.map {
|
||||
456
|
||||
}.value!
|
||||
|
||||
assert GitHub::KV::Result.new { raise "nope" }.map {
|
||||
flunk "should not have invoked map block"
|
||||
}.error
|
||||
end
|
||||
|
||||
def test_value
|
||||
assert_equal 123, GitHub::KV::Result.new { 123 }.value { 456 }
|
||||
|
||||
assert_equal 456, GitHub::KV::Result.new { raise "nope" }.value { 456 }
|
||||
end
|
||||
|
||||
def test_value!
|
||||
assert_equal 123, GitHub::KV::Result.new { 123 }.value!
|
||||
|
||||
r = GitHub::KV::Result.new { raise "nope" }
|
||||
|
||||
assert_raises RuntimeError do
|
||||
r.value!
|
||||
end
|
||||
end
|
||||
|
||||
def test_ok?
|
||||
assert_predicate GitHub::KV::Result.new { 123 }, :ok?
|
||||
|
||||
refute_predicate GitHub::KV::Result.new { raise "nope" }, :ok?
|
||||
end
|
||||
|
||||
def test_error
|
||||
assert_nil GitHub::KV::Result.new { 123 }.error
|
||||
|
||||
e = StandardError.new("nope")
|
||||
|
||||
assert_equal e, GitHub::KV::Result.new { raise e }.error
|
||||
end
|
||||
end
|
|
@ -1,13 +1,13 @@
|
|||
require "test_helper"
|
||||
|
||||
class GitHub::Data::SQLTest < Minitest::Test
|
||||
class GitHub::KV::SQLTest < Minitest::Test
|
||||
local_time = Time.utc(1970, 1, 1, 0, 0, 0)
|
||||
|
||||
Timecop.freeze(local_time) do
|
||||
foo = GitHub::Data::SQL::LITERAL "foo"
|
||||
rows = GitHub::Data::SQL::ROWS [[1, 2], [3, 4]]
|
||||
foo = GitHub::KV::SQL::LITERAL "foo"
|
||||
rows = GitHub::KV::SQL::ROWS [[1, 2], [3, 4]]
|
||||
SANITIZE_TESTS = [
|
||||
[GitHub::Data::SQL, "'GitHub::Data::SQL'"],
|
||||
[GitHub::KV::SQL, "'GitHub::KV::SQL'"],
|
||||
[DateTime.now.utc, "'1970-01-01 00:00:00'"],
|
||||
[Time.now.utc, "'1970-01-01 00:00:00'"],
|
||||
[Time.now.utc.to_date, "'1970-01-01'"],
|
||||
|
@ -34,22 +34,22 @@ class GitHub::Data::SQLTest < Minitest::Test
|
|||
|
||||
def test_sanitize
|
||||
SANITIZE_TESTS.each do |input, expected|
|
||||
assert_equal expected, GitHub::Data::SQL.new.sanitize(input),
|
||||
assert_equal expected, GitHub::KV::SQL.new.sanitize(input),
|
||||
"#{input.inspect} sanitizes as #{expected.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def test_sanitize_bad_values
|
||||
BAD_VALUE_TESTS.each do |input|
|
||||
assert_raises GitHub::Data::SQL::BadValue, "#{input.inspect} (#{input.class}) raises BadValue when sanitized" do
|
||||
GitHub::Data::SQL.new.sanitize input
|
||||
assert_raises GitHub::KV::SQL::BadValue, "#{input.inspect} (#{input.class}) raises BadValue when sanitized" do
|
||||
GitHub::KV::SQL.new.sanitize input
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_initialize_with_query
|
||||
str = "query"
|
||||
sql = GitHub::Data::SQL.new str
|
||||
sql = GitHub::KV::SQL.new str
|
||||
|
||||
assert_equal Hash.new, sql.binds
|
||||
assert_equal str, sql.query
|
||||
|
@ -58,7 +58,7 @@ class GitHub::Data::SQLTest < Minitest::Test
|
|||
|
||||
def test_initialize_with_binds
|
||||
binds = { :key => "value" }
|
||||
sql = GitHub::Data::SQL.new binds
|
||||
sql = GitHub::KV::SQL.new binds
|
||||
|
||||
assert_equal "", sql.query
|
||||
assert_equal "value", sql.binds[:key]
|
||||
|
@ -66,49 +66,49 @@ class GitHub::Data::SQLTest < Minitest::Test
|
|||
end
|
||||
|
||||
def test_initialize_with_query_and_binds
|
||||
sql = GitHub::Data::SQL.new "query :key", :key => "value"
|
||||
sql = GitHub::KV::SQL.new "query :key", :key => "value"
|
||||
|
||||
assert_equal "query 'value'", sql.query
|
||||
assert_equal "value", sql.binds[:key]
|
||||
end
|
||||
|
||||
def test_initialize_with_single_character_binds
|
||||
sql = GitHub::Data::SQL.new "query :x", :x => "y"
|
||||
sql = GitHub::KV::SQL.new "query :x", :x => "y"
|
||||
assert_equal "query 'y'", sql.query
|
||||
assert_equal "y", sql.binds[:x]
|
||||
end
|
||||
|
||||
def test_add
|
||||
sql = GitHub::Data::SQL.new
|
||||
sql = GitHub::KV::SQL.new
|
||||
|
||||
sql.add("first").add "second"
|
||||
assert_equal "first second", sql.query
|
||||
end
|
||||
|
||||
def test_add_with_binds
|
||||
sql = GitHub::Data::SQL.new
|
||||
sql = GitHub::KV::SQL.new
|
||||
|
||||
sql.add ":local", :local => "value"
|
||||
assert_equal "'value'", sql.query
|
||||
|
||||
assert_raises GitHub::Data::SQL::BadBind do
|
||||
assert_raises GitHub::KV::SQL::BadBind do
|
||||
sql.add ":local" # the previous value doesn't persist
|
||||
end
|
||||
end
|
||||
|
||||
def test_add_with_leading_and_trailing_whitespace
|
||||
sql = GitHub::Data::SQL.new " query "
|
||||
sql = GitHub::KV::SQL.new " query "
|
||||
assert_equal "query", sql.query
|
||||
end
|
||||
|
||||
def test_add_date
|
||||
now = Time.now.utc
|
||||
sql = GitHub::Data::SQL.new ":now", :now => now
|
||||
sql = GitHub::KV::SQL.new ":now", :now => now
|
||||
assert_equal "'#{now.to_s(:db)}'", sql.query
|
||||
end
|
||||
|
||||
def test_bind
|
||||
sql = GitHub::Data::SQL.new
|
||||
sql = GitHub::KV::SQL.new
|
||||
sql.bind(:first => "firstval").bind(:second => "secondval")
|
||||
|
||||
assert_equal "firstval", sql.binds[:first]
|
||||
|
@ -116,7 +116,7 @@ class GitHub::Data::SQLTest < Minitest::Test
|
|||
end
|
||||
|
||||
def test_initialize_with_connection
|
||||
sql = GitHub::Data::SQL.new :connection => "stub"
|
||||
sql = GitHub::KV::SQL.new :connection => "stub"
|
||||
|
||||
assert_equal "stub", sql.connection
|
||||
assert_nil sql.binds[:connection]
|
||||
|
@ -126,65 +126,65 @@ class GitHub::Data::SQLTest < Minitest::Test
|
|||
first, second = nil
|
||||
|
||||
ActiveRecord::Base.cache do
|
||||
first = GitHub::Data::SQL.new("SELECT RAND()").value
|
||||
second = GitHub::Data::SQL.new("SELECT RAND()").value
|
||||
first = GitHub::KV::SQL.new("SELECT RAND()").value
|
||||
second = GitHub::KV::SQL.new("SELECT RAND()").value
|
||||
end
|
||||
|
||||
assert_in_delta first, second
|
||||
end
|
||||
|
||||
def test_add_unless_empty_adds_to_a_non_empty_query
|
||||
sql = GitHub::Data::SQL.new "non-empty"
|
||||
sql = GitHub::KV::SQL.new "non-empty"
|
||||
sql.add_unless_empty "foo"
|
||||
|
||||
assert_includes sql.query, "foo"
|
||||
end
|
||||
|
||||
def test_add_unless_empty_does_not_add_to_an_empty_query
|
||||
sql = GitHub::Data::SQL.new
|
||||
sql = GitHub::KV::SQL.new
|
||||
sql.add_unless_empty "foo"
|
||||
|
||||
refute_includes sql.query, "foo"
|
||||
end
|
||||
|
||||
def test_literal
|
||||
assert_kind_of GitHub::Data::SQL::Literal, GitHub::Data::SQL::LITERAL("foo")
|
||||
assert_kind_of GitHub::KV::SQL::Literal, GitHub::KV::SQL::LITERAL("foo")
|
||||
end
|
||||
|
||||
def test_rows
|
||||
assert_kind_of GitHub::Data::SQL::Rows, GitHub::Data::SQL::ROWS([[1, 2, 3], [4, 5, 6]])
|
||||
assert_kind_of GitHub::KV::SQL::Rows, GitHub::KV::SQL::ROWS([[1, 2, 3], [4, 5, 6]])
|
||||
end
|
||||
|
||||
def test_rows_raises_if_non_arrays_are_provided
|
||||
assert_raises(ArgumentError) do
|
||||
GitHub::Data::SQL::ROWS([1, 2, 3])
|
||||
GitHub::KV::SQL::ROWS([1, 2, 3])
|
||||
end
|
||||
end
|
||||
|
||||
def test_affected_rows
|
||||
begin
|
||||
GitHub::Data::SQL.run("CREATE TEMPORARY TABLE affected_rows_test (x INT)")
|
||||
GitHub::Data::SQL.run("INSERT INTO affected_rows_test VALUES (1), (2), (3), (4)")
|
||||
GitHub::KV::SQL.run("CREATE TEMPORARY TABLE affected_rows_test (x INT)")
|
||||
GitHub::KV::SQL.run("INSERT INTO affected_rows_test VALUES (1), (2), (3), (4)")
|
||||
|
||||
sql = GitHub::Data::SQL.new("UPDATE affected_rows_test SET x = x + 1")
|
||||
sql = GitHub::KV::SQL.new("UPDATE affected_rows_test SET x = x + 1")
|
||||
sql.run
|
||||
|
||||
assert_equal 4, sql.affected_rows
|
||||
ensure
|
||||
GitHub::Data::SQL.run("DROP TABLE affected_rows_test")
|
||||
GitHub::KV::SQL.run("DROP TABLE affected_rows_test")
|
||||
end
|
||||
end
|
||||
|
||||
def test_affected_rows_even_when_query_generates_warning
|
||||
begin
|
||||
GitHub::Data::SQL.run("CREATE TEMPORARY TABLE affected_rows_test (x INT)")
|
||||
GitHub::Data::SQL.run("INSERT INTO affected_rows_test VALUES (1), (2), (3), (4)")
|
||||
sql = GitHub::Data::SQL.new("UPDATE affected_rows_test SET x = x + 1 WHERE 1 = '1x'")
|
||||
GitHub::KV::SQL.run("CREATE TEMPORARY TABLE affected_rows_test (x INT)")
|
||||
GitHub::KV::SQL.run("INSERT INTO affected_rows_test VALUES (1), (2), (3), (4)")
|
||||
sql = GitHub::KV::SQL.new("UPDATE affected_rows_test SET x = x + 1 WHERE 1 = '1x'")
|
||||
sql.run
|
||||
|
||||
assert_equal 4, sql.affected_rows
|
||||
ensure
|
||||
GitHub::Data::SQL.run("DROP TABLE affected_rows_test")
|
||||
GitHub::KV::SQL.run("DROP TABLE affected_rows_test")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,14 +1,14 @@
|
|||
require "test_helper"
|
||||
|
||||
class GitHub::Data::KVTest < Minitest::Test
|
||||
class GitHub::KVTest < Minitest::Test
|
||||
def setup
|
||||
ActiveRecord::Base.connection.execute("TRUNCATE `key_values`")
|
||||
@kv = GitHub::Data::KV.new { ActiveRecord::Base.connection }
|
||||
@kv = GitHub::KV.new { ActiveRecord::Base.connection }
|
||||
end
|
||||
|
||||
def test_initialize_without_connection
|
||||
kv = GitHub::Data::KV.new
|
||||
assert_raises GitHub::Data::KV::MissingConnectionError do
|
||||
kv = GitHub::KV.new
|
||||
assert_raises GitHub::KV::MissingConnectionError do
|
||||
kv.get("foo").value!
|
||||
end
|
||||
end
|
||||
|
@ -41,7 +41,7 @@ class GitHub::Data::KVTest < Minitest::Test
|
|||
def test_set_failure
|
||||
ActiveRecord::Base.connection.stubs(:insert).raises(Errno::ECONNRESET)
|
||||
|
||||
assert_raises GitHub::Data::KV::UnavailableError do
|
||||
assert_raises GitHub::KV::UnavailableError do
|
||||
@kv.set("foo", "bar")
|
||||
end
|
||||
end
|
||||
|
@ -71,7 +71,7 @@ class GitHub::Data::KVTest < Minitest::Test
|
|||
def test_setnx_failure
|
||||
ActiveRecord::Base.connection.stubs(:delete).raises(Errno::ECONNRESET)
|
||||
|
||||
assert_raises GitHub::Data::KV::UnavailableError do
|
||||
assert_raises GitHub::KV::UnavailableError do
|
||||
@kv.setnx("foo", "bar")
|
||||
end
|
||||
end
|
||||
|
@ -86,7 +86,7 @@ class GitHub::Data::KVTest < Minitest::Test
|
|||
def test_del_failure
|
||||
ActiveRecord::Base.connection.stubs(:delete).raises(Errno::ECONNRESET)
|
||||
|
||||
assert_raises GitHub::Data::KV::UnavailableError do
|
||||
assert_raises GitHub::KV::UnavailableError do
|
||||
@kv.del("foo")
|
||||
end
|
||||
end
|
||||
|
@ -106,7 +106,7 @@ class GitHub::Data::KVTest < Minitest::Test
|
|||
|
||||
@kv.set("foo", "bar", expires: expires)
|
||||
|
||||
assert_equal expires, GitHub::Data::SQL.value(<<-SQL)
|
||||
assert_equal expires, GitHub::KV::SQL.value(<<-SQL)
|
||||
SELECT expires_at FROM key_values WHERE `key` = 'foo'
|
||||
SQL
|
||||
end
|
||||
|
@ -116,7 +116,7 @@ class GitHub::Data::KVTest < Minitest::Test
|
|||
|
||||
@kv.setnx("foo", "bar", expires: expires)
|
||||
|
||||
assert_equal expires, GitHub::Data::SQL.value(<<-SQL)
|
||||
assert_equal expires, GitHub::KV::SQL.value(<<-SQL)
|
||||
SELECT expires_at FROM key_values WHERE `key` = 'foo'
|
||||
SQL
|
||||
end
|
||||
|
@ -145,7 +145,7 @@ class GitHub::Data::KVTest < Minitest::Test
|
|||
@kv.set("foo", "bar", expires: 1.hour.from_now)
|
||||
@kv.set("foo", "bar")
|
||||
|
||||
assert_nil GitHub::Data::SQL.value(<<-SQL)
|
||||
assert_nil GitHub::KV::SQL.value(<<-SQL)
|
||||
SELECT expires_at FROM key_values WHERE `key` = "foo"
|
||||
SQL
|
||||
end
|
||||
|
@ -165,7 +165,7 @@ class GitHub::Data::KVTest < Minitest::Test
|
|||
end
|
||||
|
||||
def test_length_checks_key
|
||||
assert_raises GitHub::Data::KV::KeyLengthError do
|
||||
assert_raises GitHub::KV::KeyLengthError do
|
||||
@kv.get("A" * 256)
|
||||
end
|
||||
end
|
||||
|
@ -177,7 +177,7 @@ class GitHub::Data::KVTest < Minitest::Test
|
|||
end
|
||||
|
||||
def test_length_checks_value
|
||||
assert_raises GitHub::Data::KV::ValueLengthError do
|
||||
assert_raises GitHub::KV::ValueLengthError do
|
||||
@kv.set("foo", "A" * 65536)
|
||||
end
|
||||
end
|
|
@ -1,6 +1,6 @@
|
|||
require "bundler/setup"
|
||||
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
|
||||
require "github/data"
|
||||
require "github/kv"
|
||||
|
||||
require "timecop"
|
||||
require "minitest/autorun"
|
||||
|
|
Загрузка…
Ссылка в новой задаче