Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add case-sensitive KV support #54

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ rvm:
- 2.7
script: bundle exec rake
env:
- RAILS_VERSION=6.0.3.1
- RAILS_VERSION=6.0.3.5
- RAILS_VERSION=5.2.0
services:
- mysql
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
source "https://rubygems.org"
gemspec

DEFAULT_RAILS_VERSION = '6.0.3'
DEFAULT_RAILS_VERSION = '6.0.3.5'
ENV['RAILS_VERSION'] ||= DEFAULT_RAILS_VERSION

if ENV['RAILS_VERSION'] == '4.2.10'
Expand Down
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,13 @@ rails generate github:ds:active_record
rails db:migrate
```

If you need to change the name of the table used for storing the key-values, you can configure your table name as such, before running the migration:
If you need to change the name of the table used for storing the key-values, you can set your table name on the command line. Don't forget to configure your table name in the library, too:

```
rails generate github:ds:active_record --table-name new_key_values_table
```

```ruby
GitHub::KV.configure do |config|
config.table_name = "new_key_values_table"
end
Expand Down Expand Up @@ -94,6 +98,18 @@ pp kv.mdel(["foo", "bar"])
# nil
```

Due to MySQL's default collation, `GitHub::KV` keys are case-insensitive. If you require case sensitivity, you can set that when generating the migration and configuring the library:

```
rails generate github:ds:active_record --case-sensitive
```

```ruby
GitHub::KV.configure do |config|
config.case_sensitive = true
end
```

### GitHub::SQL

```ruby
Expand Down
15 changes: 11 additions & 4 deletions lib/generators/github/ds/active_record_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ module Github
module Ds
module Generators
class ActiveRecordGenerator < ::Rails::Generators::Base
class_option :table_name, type: :string, default: ::GitHub::KV.config.table_name
class_option :case_sensitive, type: :boolean, default: false

include ::Rails::Generators::Migration
desc "Generates migration for KV table"

source_paths << File.join(File.dirname(__FILE__), "templates")

def create_migration_file
migration_template "migration.rb", "db/migrate/create_key_values_table.rb", migration_version: migration_version
migration_template "migration.rb", "db/migrate/create_#{table_name}_table.rb", migration_version: migration_version
end

def self.next_migration_number(dirname)
Expand All @@ -23,16 +26,20 @@ def self.migration_version
end
end

def migration_name
"create_#{table_name}_table".camelize
end

def migration_version
self.class.migration_version
end

def self.table_name
":#{GitHub::KV.config.table_name}"
def case_sensitive?
@options["case_sensitive"]
end

def table_name
self.class.table_name
@options["table_name"]
end
end
end
Expand Down
16 changes: 10 additions & 6 deletions lib/generators/github/ds/templates/migration.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
class CreateKeyValuesTable < ActiveRecord::Migration<%= migration_version %>
class <%= migration_name %> < ActiveRecord::Migration<%= migration_version %>
def self.up
create_table <%= table_name %> do |t|
create_table :<%= table_name %> do |t|
t.string :key, :null => false
t.binary :value, :null => false
t.datetime :expires_at, :null => true
t.timestamps :null => false
end

add_index <%= table_name %>, :key, :unique => true
add_index <%= table_name %>, :expires_at
add_index :<%= table_name %>, :key, :unique => true
add_index :<%= table_name %>, :expires_at

change_column <%= table_name %>, :id, "bigint(20) NOT NULL AUTO_INCREMENT"
change_column :<%= table_name %>, :id, "bigint(20) NOT NULL AUTO_INCREMENT"
<%- if case_sensitive? %>
execute "ALTER TABLE <%= table_name %> CHARACTER SET utf8 COLLATE utf8_bin"
change_column :<%= table_name %>, :key, :string, :null => false, :collate => "utf8_bin"
<%- end %>
end

def self.down
drop_table <%= table_name %>
drop_table :<%= table_name %>
end
end
2 changes: 1 addition & 1 deletion lib/github/ds/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module GitHub
module DS
VERSION = "0.5.0"
VERSION = "0.6.0"
end
end
8 changes: 7 additions & 1 deletion lib/github/kv.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def initialize(config: GitHub::KV.config, &conn_block)
@use_local_time = config.use_local_time
@table_name = config.table_name
@conn_block = conn_block
@case_sensitive = config.case_sensitive?
end

def connection
Expand Down Expand Up @@ -129,7 +130,12 @@ def mget(keys)
SELECT `key`, value FROM #{@table_name} WHERE `key` IN :keys AND (`expires_at` IS NULL OR `expires_at` > :now)
SQL

keys.map { |key| kvs[key] }
if @case_sensitive
keys.map { |key| kvs[key] }
else
kvs.keys.each { |key| kvs[key.downcase] = kvs[key] }
keys.map { |key| kvs[key.downcase] }
end
}
end

Expand Down
7 changes: 7 additions & 0 deletions lib/github/kv/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@ module GitHub
class KV
class Config
attr_accessor :table_name, :encapsulated_errors, :use_local_time
attr_writer :case_sensitive

def initialize
@table_name = 'key_values'
@encapsulated_errors = [SystemCallError]
@use_local_time = false
@case_sensitive = false
yield self if block_given?
end

def case_sensitive?
@case_sensitive
end
end
end
Expand Down
20 changes: 16 additions & 4 deletions test/generators/github/store/active_record_generator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,28 @@ def self.up
t.timestamps :null => false
end

add_index :#{table_name}, :key, :unique => true
add_index :#{table_name}, :expires_at
add_index :#{table_name}, :key, :unique => true
add_index :#{table_name}, :expires_at

change_column :#{table_name}, :id, "bigint(20) NOT NULL AUTO_INCREMENT"

change_column :#{table_name}, :id, "bigint(20) NOT NULL AUTO_INCREMENT"
end

def self.down
drop_table :#{table_name}
drop_table :#{table_name}
end
end
EOM
end

def test_generate_with_arguments
run_generator %w(--table-name kv_test --case-sensitive)

assert_migration "db/migrate/create_kvtest_table.rb" do |migration|
assert_match "class CreateKvTest <", migration
assert_match "create_table :kvtest do", migration
assert_match %r(ALTER TABLE kvtest.*COLLATE utf8_bin), migration
assert_match %r(change_column.*:collate => "utf8_bin"), migration
end
end
end
28 changes: 28 additions & 0 deletions test/github/kv_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@
class GitHub::KVTest < Minitest::Test
def setup
ActiveRecord::Base.connection.execute("TRUNCATE `key_values`")
ActiveRecord::Base.connection.execute("TRUNCATE `kv_case_sensitive`")
@kv = GitHub::KV.new { ActiveRecord::Base.connection }
case_sensitive = GitHub::KV::Config.new do |cfg|
cfg.case_sensitive = true
cfg.table_name = "kv_case_sensitive"
end
@kv_case_sensitive = GitHub::KV.new(config: case_sensitive) { ActiveRecord::Base.connection }
GitHub::KV.reset
end

Expand Down Expand Up @@ -61,6 +67,28 @@ def test_mget_and_mset
assert_equal ["2", "1"], @kv.mget(["b", "a"]).value!
end

def test_get_and_set_case_insensitive
assert_nil @kv.get("foo").value!

@kv.set "foo", "lowercase"
assert_equal "lowercase", @kv.get("foo").value!
assert_equal "lowercase", @kv.get("FOO").value!

@kv.set "FOO", "uppercase"
assert_equal "uppercase", @kv.get("foo").value!
assert_equal "uppercase", @kv.get("FOO").value!
end

def test_get_and_set_case_insensitive

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def test_get_and_set_case_insensitive
def test_get_and_set_case_sensitive

assert_nil @kv_case_sensitive.get("foo").value!
assert_nil @kv_case_sensitive.get("FOO").value!

@kv_case_sensitive.set "foo", "lowercase"
@kv_case_sensitive.set "FOO", "uppercase"
assert_equal "lowercase", @kv_case_sensitive.get("foo").value!
assert_equal "uppercase", @kv_case_sensitive.get("FOO").value!
end

def test_get_failure
ActiveRecord::Base.connection.stubs(:select_all).raises(Errno::ECONNRESET)

Expand Down
4 changes: 2 additions & 2 deletions test/github/sql_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ class GitHub::SQLTest < Minitest::Test
[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'"],
[true, "1", {"5.2.0" => "TRUE", "6.0.3.1" => "TRUE"}],
[false, "0", {"5.2.0" => "FALSE", "6.0.3.1" => "FALSE"}],
[true, "1", {"5.2.0" => "TRUE", "6.0.3.5" => "TRUE"}],
[false, "0", {"5.2.0" => "FALSE", "6.0.3.5" => "FALSE"}],
[17, "17"],
[1.7, "1.7"],
["corge", "'corge'"],
Expand Down
7 changes: 6 additions & 1 deletion test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
begin
ActiveRecord::Base.establish_connection :with_database
ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS `key_values`")
ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS `kv_case_sensitive`")

# remove db tree if present so we can start fresh
db_path = root_path.join("db")
Expand All @@ -40,11 +41,15 @@
require "rails/generators"
require "generators/github/ds/active_record_generator"
Rails::Generators.invoke "github:ds:active_record"
Rails::Generators.invoke "github:ds:active_record", %w(--table-name kv_case_sensitive --case-sensitive)

# require migration and run it so we have the key values table
require db_path.join("migrate").children.first.to_s
db_path.join("migrate").children.each do |migration|
require migration
end
ActiveRecord::Migration.verbose = false
CreateKeyValuesTable.up
CreateKvCaseSensitiveTable.up
rescue
raise if attempts >= 1
ActiveRecord::Base.establish_connection :without_database
Expand Down