kasket 0.7.8 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|