kasket 0.7.8 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +0 -1
- data/Rakefile +2 -0
- data/lib/kasket.rb +2 -2
- data/lib/kasket/configuration_mixin.rb +14 -10
- data/lib/kasket/query_parser.rb +13 -4
- data/lib/kasket/read_mixin.rb +32 -7
- data/test/find_some_test.rb +3 -12
- data/test/helper.rb +1 -0
- data/test/parser_test.rb +17 -5
- metadata +19 -4
data/.gitignore
CHANGED
data/Rakefile
CHANGED
data/lib/kasket.rb
CHANGED
@@ -30,17 +30,21 @@ module Kasket
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def kasket_key_for(attribute_value_pairs)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
33
|
+
if attribute_value_pairs.size == 1 && attribute_value_pairs[0][0] == :id && attribute_value_pairs[0][1].is_a?(Array)
|
34
|
+
attribute_value_pairs[0][1].map {|id| kasket_key_for_id(id)}
|
35
|
+
else
|
36
|
+
key = attribute_value_pairs.map do |attribute, value|
|
37
|
+
column = columns_hash[attribute.to_s]
|
38
|
+
value = nil if value.blank?
|
39
|
+
attribute.to_s + '=' + connection.quote(column.type_cast(value), column)
|
40
|
+
end.join('/')
|
41
|
+
|
42
|
+
if key.size > (250 - kasket_key_prefix.size) || key =~ /\s/
|
43
|
+
key = Digest::MD5.hexdigest(key)
|
44
|
+
end
|
45
|
+
|
46
|
+
kasket_key_prefix + key
|
41
47
|
end
|
42
|
-
|
43
|
-
kasket_key_prefix + key
|
44
48
|
end
|
45
49
|
|
46
50
|
def kasket_key_for_id(id)
|
data/lib/kasket/query_parser.rb
CHANGED
@@ -12,7 +12,7 @@ module Kasket
|
|
12
12
|
@model_class = model_class
|
13
13
|
@supported_query_pattern = /^select \* from (?:`|")#{@model_class.table_name}(?:`|") where \((.*)\)(|\s+limit 1)\s*$/i
|
14
14
|
@table_and_column_pattern = /(?:(?:`|")?#{@model_class.table_name}(?:`|")?\.)?(?:`|")?(\w+)(?:`|")?/ # Matches: `users`.id, `users`.`id`, users.id, id
|
15
|
-
@key_eq_value_pattern = /^[\(\s]*#{@table_and_column_pattern}\s
|
15
|
+
@key_eq_value_pattern = /^[\(\s]*#{@table_and_column_pattern}\s+(=|IN)\s+#{VALUE}[\)\s]*$/ # Matches: KEY = VALUE, (KEY = VALUE), ()(KEY = VALUE))
|
16
16
|
end
|
17
17
|
|
18
18
|
def parse(sql)
|
@@ -39,10 +39,19 @@ module Kasket
|
|
39
39
|
def parse_condition(conditions = '', *values)
|
40
40
|
values = values.dup
|
41
41
|
conditions.split(AND).inject([]) do |pairs, condition|
|
42
|
-
matched, column_name, sql_value = *(@key_eq_value_pattern.match(condition))
|
42
|
+
matched, column_name, operator, sql_value = *(@key_eq_value_pattern.match(condition))
|
43
43
|
if matched
|
44
|
-
|
45
|
-
|
44
|
+
if operator == 'IN'
|
45
|
+
if column_name == 'id'
|
46
|
+
values = sql_value[1..(-2)].split(',').map(&:strip)
|
47
|
+
pairs << [column_name.to_sym, values]
|
48
|
+
else
|
49
|
+
return nil
|
50
|
+
end
|
51
|
+
else
|
52
|
+
value = sql_value == '?' ? values.shift : sql_value
|
53
|
+
pairs << [column_name.to_sym, value.gsub(/''|\\'/, "'")]
|
54
|
+
end
|
46
55
|
else
|
47
56
|
return nil
|
48
57
|
end
|
data/lib/kasket/read_mixin.rb
CHANGED
@@ -8,20 +8,44 @@ module Kasket
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def find_by_sql_with_kasket(sql)
|
11
|
-
|
12
|
-
query = kasket_parser.parse(sql) if use_kasket?
|
11
|
+
query = kasket_parser.parse(sanitize_sql(sql)) if use_kasket?
|
13
12
|
if query && has_kasket_index_on?(query[:index])
|
14
|
-
|
15
|
-
|
16
|
-
Array.wrap(value).collect { |record| instantiate(record.dup) }
|
13
|
+
if query[:key].is_a?(Array)
|
14
|
+
find_by_sql_with_kasket_on_id_array(sql, query)
|
17
15
|
else
|
18
|
-
|
16
|
+
if value = Rails.cache.read(query[:key])
|
17
|
+
Array.wrap(value).collect { |record| instantiate(record.dup) }
|
18
|
+
else
|
19
|
+
store_in_kasket(query[:key], find_by_sql_without_kasket(sql))
|
20
|
+
end
|
19
21
|
end
|
20
22
|
else
|
21
23
|
find_by_sql_without_kasket(sql)
|
22
24
|
end
|
23
25
|
end
|
24
26
|
|
27
|
+
def find_by_sql_with_kasket_on_id_array(sql, query)
|
28
|
+
key_value_map = Rails.cache.read_multi(*query[:key])
|
29
|
+
missing_ids = []
|
30
|
+
|
31
|
+
key_value_map.each do |key, value|
|
32
|
+
if value.nil?
|
33
|
+
missing_ids << key.split('=').last.to_i
|
34
|
+
else
|
35
|
+
key_value_map[key] = instantiate(value.dup)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
without_kasket do
|
40
|
+
find(missing_ids).each do |instance|
|
41
|
+
instance.store_in_kasket
|
42
|
+
key_value_map[instance.kasket_key] = instance
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
key_value_map.values
|
47
|
+
end
|
48
|
+
|
25
49
|
protected
|
26
50
|
|
27
51
|
def store_in_kasket(key, records)
|
@@ -33,7 +57,8 @@ module Kasket
|
|
33
57
|
Rails.cache.write(key, record.instance_variable_get(:@attributes).dup)
|
34
58
|
key
|
35
59
|
end
|
36
|
-
|
60
|
+
|
61
|
+
Rails.cache.write(key, keys) if key.is_a?(String)
|
37
62
|
end
|
38
63
|
records
|
39
64
|
end
|
data/test/find_some_test.rb
CHANGED
@@ -3,7 +3,7 @@ require File.dirname(__FILE__) + '/helper'
|
|
3
3
|
class FindSomeTest < ActiveSupport::TestCase
|
4
4
|
fixtures :blogs, :posts
|
5
5
|
|
6
|
-
|
6
|
+
should "cache find(id, id) calls" do
|
7
7
|
post1 = Post.first
|
8
8
|
post2 = Post.last
|
9
9
|
|
@@ -18,14 +18,14 @@ class FindSomeTest < ActiveSupport::TestCase
|
|
18
18
|
Post.find(post1.id, post2.id)
|
19
19
|
end
|
20
20
|
|
21
|
-
|
21
|
+
should "only lookup the records that are not in the cache" do
|
22
22
|
post1 = Post.first
|
23
23
|
post2 = Post.last
|
24
24
|
assert_equal(post1, Post.find(post1.id))
|
25
25
|
assert(Rails.cache.read(post1.kasket_key))
|
26
26
|
assert_nil(Rails.cache.read(post2.kasket_key))
|
27
27
|
|
28
|
-
Post.expects(:find_by_sql_without_kasket).with("SELECT * FROM \
|
28
|
+
Post.expects(:find_by_sql_without_kasket).with("SELECT * FROM \"posts\" WHERE (\"posts\".\"id\" = #{post2.id}) ").returns([post2])
|
29
29
|
found_posts = Post.find(post1.id, post2.id)
|
30
30
|
assert_equal([post1, post2].map(&:id).sort, found_posts.map(&:id).sort)
|
31
31
|
|
@@ -33,13 +33,4 @@ class FindSomeTest < ActiveSupport::TestCase
|
|
33
33
|
found_posts = Post.find(post1.id, post2.id)
|
34
34
|
assert_equal([post1, post2].map(&:id).sort, found_posts.map(&:id).sort)
|
35
35
|
end
|
36
|
-
|
37
|
-
should_eventually "cache on index other than primary key" do
|
38
|
-
blog = blogs(:a_blog)
|
39
|
-
posts = Post.find_all_by_blog_id(blog.id)
|
40
|
-
|
41
|
-
Post.expects(:find_by_sql_without_kasket).never
|
42
|
-
|
43
|
-
assert_equal(posts, Post.find_all_by_blog_id(blog.id))
|
44
|
-
end
|
45
36
|
end
|
data/test/helper.rb
CHANGED
data/test/parser_test.rb
CHANGED
@@ -25,6 +25,16 @@ class ParserTest < ActiveSupport::TestCase
|
|
25
25
|
assert @parser.parse('SELECT * FROM `posts` WHERE (`posts`.`id` = 2) LIMIT 1')
|
26
26
|
end
|
27
27
|
|
28
|
+
should "support IN queries on id" do
|
29
|
+
parsed_query = @parser.parse('SELECT * FROM `posts` WHERE (`posts`.`id` IN (1,2,3))')
|
30
|
+
assert(parsed_query)
|
31
|
+
assert_equal([[:id, ['1', '2', '3']]], parsed_query[:attributes])
|
32
|
+
end
|
33
|
+
|
34
|
+
should "not support IN queries on other attributes" do
|
35
|
+
assert(!@parser.parse('SELECT * FROM `posts` WHERE (`posts`.`hest` IN (1,2,3))'))
|
36
|
+
end
|
37
|
+
|
28
38
|
should "support vaguely formatted queries" do
|
29
39
|
assert @parser.parse('SELECT * FROM "posts" WHERE (title = red AND blog_id = big)')
|
30
40
|
end
|
@@ -66,11 +76,6 @@ class ParserTest < ActiveSupport::TestCase
|
|
66
76
|
should "include the OR operator" do
|
67
77
|
assert !@parser.parse('SELECT * FROM `posts` WHERE (title = red OR blog_id = big) LIMIT 2')
|
68
78
|
end
|
69
|
-
|
70
|
-
should "include the IN operator" do
|
71
|
-
assert !@parser.parse('SELECT * FROM `posts` WHERE (id IN (1,2,3))')
|
72
|
-
end
|
73
|
-
|
74
79
|
end
|
75
80
|
|
76
81
|
context "key generation" do
|
@@ -84,6 +89,13 @@ class ParserTest < ActiveSupport::TestCase
|
|
84
89
|
assert_match(/id=1\/title='title'$/, @parser.parse("SELECT * FROM `posts` WHERE (id = 1 AND title = 'title')")[:key])
|
85
90
|
end
|
86
91
|
|
92
|
+
should "generate multiple keys on IN queries" do
|
93
|
+
keys = @parser.parse('SELECT * FROM `posts` WHERE (id IN (1,2))')[:key]
|
94
|
+
assert_instance_of(Array, keys)
|
95
|
+
assert_match(/id=1$/, keys[0])
|
96
|
+
assert_match(/id=2$/, keys[1])
|
97
|
+
end
|
98
|
+
|
87
99
|
context "when limit 1" do
|
88
100
|
should "add /first to the key if the index does not include id" do
|
89
101
|
assert_match(/title='a'\/first$/, @parser.parse("SELECT * FROM `posts` WHERE (title = 'a') LIMIT 1")[:key])
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kasket
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 63
|
4
5
|
prerelease: false
|
5
6
|
segments:
|
6
7
|
- 0
|
7
|
-
- 7
|
8
8
|
- 8
|
9
|
-
|
9
|
+
- 0
|
10
|
+
version: 0.8.0
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Mick Staugaard
|
@@ -15,16 +16,18 @@ autorequire:
|
|
15
16
|
bindir: bin
|
16
17
|
cert_chain: []
|
17
18
|
|
18
|
-
date: 2010-
|
19
|
+
date: 2010-08-24 00:00:00 -07:00
|
19
20
|
default_executable:
|
20
21
|
dependencies:
|
21
22
|
- !ruby/object:Gem::Dependency
|
22
23
|
name: activerecord
|
23
24
|
prerelease: false
|
24
25
|
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
25
27
|
requirements:
|
26
28
|
- - ">="
|
27
29
|
- !ruby/object:Gem::Version
|
30
|
+
hash: 11
|
28
31
|
segments:
|
29
32
|
- 2
|
30
33
|
- 3
|
@@ -36,9 +39,11 @@ dependencies:
|
|
36
39
|
name: activesupport
|
37
40
|
prerelease: false
|
38
41
|
requirement: &id002 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
39
43
|
requirements:
|
40
44
|
- - ">="
|
41
45
|
- !ruby/object:Gem::Version
|
46
|
+
hash: 11
|
42
47
|
segments:
|
43
48
|
- 2
|
44
49
|
- 3
|
@@ -50,9 +55,11 @@ dependencies:
|
|
50
55
|
name: shoulda
|
51
56
|
prerelease: false
|
52
57
|
requirement: &id003 !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
53
59
|
requirements:
|
54
60
|
- - ">="
|
55
61
|
- !ruby/object:Gem::Version
|
62
|
+
hash: 3
|
56
63
|
segments:
|
57
64
|
- 0
|
58
65
|
version: "0"
|
@@ -62,9 +69,11 @@ dependencies:
|
|
62
69
|
name: mocha
|
63
70
|
prerelease: false
|
64
71
|
requirement: &id004 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
65
73
|
requirements:
|
66
74
|
- - ">="
|
67
75
|
- !ruby/object:Gem::Version
|
76
|
+
hash: 3
|
68
77
|
segments:
|
69
78
|
- 0
|
70
79
|
version: "0"
|
@@ -74,9 +83,11 @@ dependencies:
|
|
74
83
|
name: temping
|
75
84
|
prerelease: false
|
76
85
|
requirement: &id005 !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
77
87
|
requirements:
|
78
88
|
- - ">="
|
79
89
|
- !ruby/object:Gem::Version
|
90
|
+
hash: 27
|
80
91
|
segments:
|
81
92
|
- 1
|
82
93
|
- 3
|
@@ -129,23 +140,27 @@ rdoc_options:
|
|
129
140
|
require_paths:
|
130
141
|
- lib
|
131
142
|
required_ruby_version: !ruby/object:Gem::Requirement
|
143
|
+
none: false
|
132
144
|
requirements:
|
133
145
|
- - ">="
|
134
146
|
- !ruby/object:Gem::Version
|
147
|
+
hash: 3
|
135
148
|
segments:
|
136
149
|
- 0
|
137
150
|
version: "0"
|
138
151
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
152
|
+
none: false
|
139
153
|
requirements:
|
140
154
|
- - ">="
|
141
155
|
- !ruby/object:Gem::Version
|
156
|
+
hash: 3
|
142
157
|
segments:
|
143
158
|
- 0
|
144
159
|
version: "0"
|
145
160
|
requirements: []
|
146
161
|
|
147
162
|
rubyforge_project:
|
148
|
-
rubygems_version: 1.3.
|
163
|
+
rubygems_version: 1.3.7
|
149
164
|
signing_key:
|
150
165
|
specification_version: 3
|
151
166
|
summary: A write back caching layer on active record
|