search_cop 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +1 -0
- data/Appraisals +6 -0
- data/README.md +1 -1
- data/gemfiles/4.2.gemfile +26 -0
- data/lib/search_cop/query_builder.rb +1 -1
- data/lib/search_cop/search_scope.rb +3 -3
- data/lib/search_cop/version.rb +1 -1
- data/lib/search_cop/visitors/mysql.rb +43 -0
- data/lib/search_cop/visitors/postgres.rb +39 -0
- data/lib/search_cop/visitors/visitor.rb +96 -0
- data/lib/search_cop/visitors.rb +5 -0
- data/lib/search_cop.rb +1 -1
- data/lib/search_cop_grammar/attributes.rb +19 -8
- data/lib/search_cop_grammar/nodes.rb +1 -1
- data/lib/search_cop_grammar.treetop +1 -1
- data/test/test_helper.rb +12 -0
- data/test/visitor_test.rb +101 -0
- metadata +9 -4
- data/lib/search_cop/arel/visitors.rb +0 -223
- data/lib/search_cop/arel.rb +0 -4
data/.travis.yml
CHANGED
data/Appraisals
CHANGED
data/README.md
CHANGED
@@ -97,7 +97,7 @@ end
|
|
97
97
|
|
98
98
|
## How does it work
|
99
99
|
|
100
|
-
SearchCop parses the query and maps it to an SQL Query
|
100
|
+
SearchCop parses the query and maps it to an SQL Query in a database agnostic way.
|
101
101
|
Thus, SearchCop is not bound to a specific RDBMS.
|
102
102
|
|
103
103
|
```ruby
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "activerecord", "~> 4.2.0.beta"
|
6
|
+
gem "search_cop", :path => "../"
|
7
|
+
|
8
|
+
platforms :jruby do
|
9
|
+
gem "activerecord-jdbcmysql-adapter"
|
10
|
+
gem "activerecord-jdbcsqlite3-adapter"
|
11
|
+
gem "activerecord-jdbcpostgresql-adapter"
|
12
|
+
end
|
13
|
+
|
14
|
+
platforms :ruby do
|
15
|
+
gem "sqlite3"
|
16
|
+
gem "mysql2"
|
17
|
+
gem "pg"
|
18
|
+
end
|
19
|
+
|
20
|
+
platforms :rbx do
|
21
|
+
gem "racc"
|
22
|
+
gem "rubysl", "~> 2.0"
|
23
|
+
gem "psych"
|
24
|
+
end
|
25
|
+
|
26
|
+
gemspec :path => "../"
|
@@ -29,7 +29,7 @@ module SearchCop
|
|
29
29
|
def attributes(*args)
|
30
30
|
args.each do |arg|
|
31
31
|
attributes_hash arg.is_a?(Hash) ? arg : { arg => arg }
|
32
|
-
end
|
32
|
+
end
|
33
33
|
end
|
34
34
|
|
35
35
|
def options(key, options = {})
|
@@ -54,8 +54,8 @@ module SearchCop
|
|
54
54
|
table, attribute = column.to_s =~ /\./ ? column.to_s.split(".") : [model.name.tableize, column]
|
55
55
|
|
56
56
|
"#{table}.#{attribute}"
|
57
|
-
end
|
58
|
-
end
|
57
|
+
end
|
58
|
+
end
|
59
59
|
end
|
60
60
|
end
|
61
61
|
end
|
data/lib/search_cop/version.rb
CHANGED
@@ -0,0 +1,43 @@
|
|
1
|
+
|
2
|
+
module SearchCop
|
3
|
+
module Visitors
|
4
|
+
module Mysql
|
5
|
+
class FulltextQuery < Visitor
|
6
|
+
def visit_SearchCopGrammar_Nodes_MatchesFulltextNot(node)
|
7
|
+
node.right.split(/[\s+'"<>()~-]+/).collect { |word| "-#{word}" }.join(" ")
|
8
|
+
end
|
9
|
+
|
10
|
+
def visit_SearchCopGrammar_Nodes_MatchesFulltext(node)
|
11
|
+
words = node.right.split(/[\s+'"<>()~-]+/)
|
12
|
+
|
13
|
+
words.size > 1 ? "\"#{words.join " "}\"" : words.first
|
14
|
+
end
|
15
|
+
|
16
|
+
def visit_SearchCopGrammar_Nodes_And_Fulltext(node)
|
17
|
+
res = node.nodes.collect do |node|
|
18
|
+
if node.is_a?(SearchCopGrammar::Nodes::MatchesFulltextNot)
|
19
|
+
visit node
|
20
|
+
else
|
21
|
+
node.nodes.size > 1 ? "+(#{visit node})" : "+#{visit node}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
res.join " "
|
26
|
+
end
|
27
|
+
|
28
|
+
def visit_SearchCopGrammar_Nodes_Or_Fulltext(node)
|
29
|
+
node.nodes.collect { |node| "(#{visit node})" }.join(" ")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def visit_SearchCopGrammar_Attributes_Collection(node)
|
34
|
+
node.attributes.collect { |attribute| visit attribute }.join(", ")
|
35
|
+
end
|
36
|
+
|
37
|
+
def visit_SearchCopGrammar_Nodes_FulltextExpression(node)
|
38
|
+
"MATCH(#{visit node.collection}) AGAINST(#{visit FulltextQuery.new(connection).visit(node.node)} IN BOOLEAN MODE)"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
|
2
|
+
module SearchCop
|
3
|
+
module Visitors
|
4
|
+
module Postgres
|
5
|
+
class FulltextQuery < Visitor
|
6
|
+
def visit_SearchCopGrammar_Nodes_MatchesFulltextNot(node)
|
7
|
+
"!'#{node.right.gsub /[\s&|!:'"]+/, " "}'"
|
8
|
+
end
|
9
|
+
|
10
|
+
def visit_SearchCopGrammar_Nodes_MatchesFulltext(node)
|
11
|
+
"'#{node.right.gsub /[\s&|!:'"]+/, " "}'"
|
12
|
+
end
|
13
|
+
|
14
|
+
def visit_SearchCopGrammar_Nodes_And_Fulltext(node)
|
15
|
+
node.nodes.collect { |node| "(#{visit node})" }.join(" & ")
|
16
|
+
end
|
17
|
+
|
18
|
+
def visit_SearchCopGrammar_Nodes_Or_Fulltext(node)
|
19
|
+
node.nodes.collect { |node| "(#{visit node})" }.join(" | ")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def visit_SearchCopGrammar_Nodes_Matches(node)
|
24
|
+
"#{visit node.left} ILIKE #{visit node.right}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def visit_SearchCopGrammar_Attributes_Collection(node)
|
28
|
+
node.attributes.collect { |attribute| visit attribute }.join(" || ' ' || ")
|
29
|
+
end
|
30
|
+
|
31
|
+
def visit_SearchCopGrammar_Nodes_FulltextExpression(node)
|
32
|
+
dictionary = node.collection.options[:dictionary] || "simple"
|
33
|
+
|
34
|
+
"to_tsvector(#{visit dictionary}, #{visit node.collection}) @@ to_tsquery(#{visit dictionary}, #{visit FulltextQuery.new(connection).visit(node.node)})"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,96 @@
|
|
1
|
+
|
2
|
+
module SearchCop
|
3
|
+
module Visitors
|
4
|
+
class Visitor
|
5
|
+
attr_accessor :connection
|
6
|
+
|
7
|
+
def initialize(connection)
|
8
|
+
@connection = connection
|
9
|
+
|
10
|
+
extend(SearchCop::Visitors::Mysql) if @connection.class.name =~ /mysql/i
|
11
|
+
extend(SearchCop::Visitors::Postgres) if @connection.class.name =~ /postgres/i
|
12
|
+
end
|
13
|
+
|
14
|
+
def visit(visit_node = node)
|
15
|
+
send "visit_#{visit_node.class.name.gsub /::/, "_"}", visit_node
|
16
|
+
end
|
17
|
+
|
18
|
+
def visit_SearchCopGrammar_Nodes_And(node)
|
19
|
+
"(#{node.nodes.collect { |n| visit n }.join(" AND ")})"
|
20
|
+
end
|
21
|
+
|
22
|
+
def visit_SearchCopGrammar_Nodes_Or(node)
|
23
|
+
"(#{node.nodes.collect { |n| visit n }.join(" OR ")})"
|
24
|
+
end
|
25
|
+
|
26
|
+
def visit_SearchCopGrammar_Nodes_GreaterThan(node)
|
27
|
+
"#{visit node.left} > #{visit node.right}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def visit_SearchCopGrammar_Nodes_GreaterThanOrEqual(node)
|
31
|
+
"#{visit node.left} >= #{visit node.right}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def visit_SearchCopGrammar_Nodes_LessThan(node)
|
35
|
+
"#{visit node.left} < #{visit node.right}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def visit_SearchCopGrammar_Nodes_LessThanOrEqual(node)
|
39
|
+
"#{visit node.left} <= #{visit node.right}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def visit_SearchCopGrammar_Nodes_Equality(node)
|
43
|
+
"#{visit node.left} = #{visit node.right}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def visit_SearchCopGrammar_Nodes_NotEqual(node)
|
47
|
+
"#{visit node.left} != #{visit node.right}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def visit_SearchCopGrammar_Nodes_Matches(node)
|
51
|
+
"#{visit node.left} LIKE #{visit node.right}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def visit_SearchCopGrammar_Nodes_Not(node)
|
55
|
+
"NOT (#{visit node.object})"
|
56
|
+
end
|
57
|
+
|
58
|
+
def quote_table_name(name)
|
59
|
+
connection.quote_table_name name
|
60
|
+
end
|
61
|
+
|
62
|
+
def quote_column_name(name)
|
63
|
+
connection.quote_column_name name
|
64
|
+
end
|
65
|
+
|
66
|
+
def visit_attribute(attribute)
|
67
|
+
"#{quote_table_name attribute.table_alias}.#{quote_column_name attribute.column_name}"
|
68
|
+
end
|
69
|
+
|
70
|
+
alias :visit_SearchCopGrammar_Attributes_String :visit_attribute
|
71
|
+
alias :visit_SearchCopGrammar_Attributes_Text :visit_attribute
|
72
|
+
alias :visit_SearchCopGrammar_Attributes_Float :visit_attribute
|
73
|
+
alias :visit_SearchCopGrammar_Attributes_Integer :visit_attribute
|
74
|
+
alias :visit_SearchCopGrammar_Attributes_Decimal :visit_attribute
|
75
|
+
alias :visit_SearchCopGrammar_Attributes_Datetime :visit_attribute
|
76
|
+
alias :visit_SearchCopGrammar_Attributes_Timestamp :visit_attribute
|
77
|
+
alias :visit_SearchCopGrammar_Attributes_Date :visit_attribute
|
78
|
+
alias :visit_SearchCopGrammar_Attributes_Time :visit_attribute
|
79
|
+
alias :visit_SearchCopGrammar_Attributes_Boolean :visit_attribute
|
80
|
+
|
81
|
+
def quote(value)
|
82
|
+
connection.quote value
|
83
|
+
end
|
84
|
+
|
85
|
+
alias :visit_TrueClass :quote
|
86
|
+
alias :visit_FalseClass :quote
|
87
|
+
alias :visit_String :quote
|
88
|
+
alias :visit_Time :quote
|
89
|
+
alias :visit_Date :quote
|
90
|
+
alias :visit_Float :quote
|
91
|
+
alias :visit_Fixnum :quote
|
92
|
+
alias :visit_Symbol :quote
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
data/lib/search_cop.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
|
2
2
|
require "search_cop/version"
|
3
|
-
require "search_cop/arel"
|
4
3
|
require "search_cop/search_scope"
|
5
4
|
require "search_cop/query_info"
|
6
5
|
require "search_cop/query_builder"
|
7
6
|
require "search_cop/grammar_parser"
|
8
7
|
require "search_cop/hash_parser"
|
8
|
+
require "search_cop/visitors"
|
9
9
|
|
10
10
|
module SearchCop
|
11
11
|
class SpecificationError < StandardError; end
|
@@ -82,16 +82,18 @@ module SearchCopGrammar
|
|
82
82
|
|
83
83
|
raise(SearchCop::UnknownAttribute, "Unknown attribute #{attribute_definition}") unless klass.columns_hash[column]
|
84
84
|
|
85
|
-
Attributes.const_get(klass.columns_hash[column].type.to_s.classify).new(klass
|
85
|
+
Attributes.const_get(klass.columns_hash[column].type.to_s.classify).new(klass, alias_for(table), column, options)
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
89
|
class Base
|
90
|
-
attr_reader :attribute, :options
|
90
|
+
attr_reader :attribute, :table_alias, :column_name, :options
|
91
91
|
|
92
|
-
def initialize(
|
93
|
-
@attribute =
|
92
|
+
def initialize(klass, table_alias, column_name, options = {})
|
93
|
+
@attribute = klass.arel_table.alias(table_alias)[column_name]
|
94
94
|
@klass = klass
|
95
|
+
@table_alias = table_alias
|
96
|
+
@column_name = column_name
|
95
97
|
@options = (options || {})
|
96
98
|
end
|
97
99
|
|
@@ -115,16 +117,16 @@ module SearchCopGrammar
|
|
115
117
|
define_method method do |value|
|
116
118
|
raise(SearchCop::IncompatibleDatatype, "Incompatible datatype for #{value}") unless compatible?(value)
|
117
119
|
|
118
|
-
SearchCopGrammar::Nodes.const_get(class_name).new(
|
120
|
+
SearchCopGrammar::Nodes.const_get(class_name).new(self, map(value))
|
119
121
|
end
|
120
122
|
end
|
121
123
|
|
122
124
|
def method_missing(name, *args, &block)
|
123
|
-
@attribute.send
|
125
|
+
@attribute.send(name, *args, &block)
|
124
126
|
end
|
125
127
|
|
126
128
|
def respond_to?(*args)
|
127
|
-
@attribute.respond_to?
|
129
|
+
super(*args) || @attribute.respond_to?(*args)
|
128
130
|
end
|
129
131
|
end
|
130
132
|
|
@@ -154,9 +156,18 @@ module SearchCopGrammar
|
|
154
156
|
|
155
157
|
false
|
156
158
|
end
|
159
|
+
|
160
|
+
def map(value)
|
161
|
+
value.to_f
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class Integer < Float
|
166
|
+
def map(value)
|
167
|
+
value.to_i
|
168
|
+
end
|
157
169
|
end
|
158
170
|
|
159
|
-
class Integer < Float; end
|
160
171
|
class Decimal < Float; end
|
161
172
|
|
162
173
|
class Datetime < WithoutMatches
|
data/test/test_helper.rb
CHANGED
@@ -146,5 +146,17 @@ class SearchCop::TestCase
|
|
146
146
|
def assert_not_nil(value)
|
147
147
|
assert value
|
148
148
|
end
|
149
|
+
|
150
|
+
def quote_table_name(name)
|
151
|
+
ActiveRecord::Base.connection.quote_table_name name
|
152
|
+
end
|
153
|
+
|
154
|
+
def quote_column_name(name)
|
155
|
+
ActiveRecord::Base.connection.quote_column_name name
|
156
|
+
end
|
157
|
+
|
158
|
+
def quote(object)
|
159
|
+
ActiveRecord::Base.connection.quote object
|
160
|
+
end
|
149
161
|
end
|
150
162
|
|
@@ -0,0 +1,101 @@
|
|
1
|
+
|
2
|
+
require File.expand_path("../test_helper", __FILE__)
|
3
|
+
|
4
|
+
class VisitorTest < SearchCop::TestCase
|
5
|
+
def test_and
|
6
|
+
node = SearchCopGrammar::Attributes::Integer.new(Product, "products", "stock").gt(0).and(SearchCopGrammar::Attributes::Integer.new(Product, "products", "stock").lt(2))
|
7
|
+
|
8
|
+
assert_equal "(#{quote_table_name "products"}.#{quote_column_name "stock"} > 0 AND #{quote_table_name "products"}.#{quote_column_name "stock"} < 2)", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_or
|
12
|
+
node = SearchCopGrammar::Attributes::Integer.new(Product, "products", "stock").gt(0).or(SearchCopGrammar::Attributes::Integer.new(Product, "products", "stock").lt(2))
|
13
|
+
|
14
|
+
assert_equal "(#{quote_table_name "products"}.#{quote_column_name "stock"} > 0 OR #{quote_table_name "products"}.#{quote_column_name "stock"} < 2)", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_greater_than
|
18
|
+
node = SearchCopGrammar::Attributes::Integer.new(Product, "products", "stock").gt(1)
|
19
|
+
|
20
|
+
assert_equal "#{quote_table_name "products"}.#{quote_column_name "stock"} > 1", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_greater_than_or_equal
|
24
|
+
node = SearchCopGrammar::Attributes::Integer.new(Product, "products", "stock").gteq(1)
|
25
|
+
|
26
|
+
assert_equal "#{quote_table_name "products"}.#{quote_column_name "stock"} >= 1", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_less_than
|
30
|
+
node = SearchCopGrammar::Attributes::Integer.new(Product, "products", "stock").lt(1)
|
31
|
+
|
32
|
+
assert_equal "#{quote_table_name "products"}.#{quote_column_name "stock"} < 1", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_less_than_or_equal
|
36
|
+
node = SearchCopGrammar::Attributes::Integer.new(Product, "products", "stock").lteq(1)
|
37
|
+
|
38
|
+
assert_equal "#{quote_table_name "products"}.#{quote_column_name "stock"} <= 1", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_equality
|
42
|
+
node = SearchCopGrammar::Attributes::Integer.new(Product, "products", "stock").eq(1)
|
43
|
+
|
44
|
+
assert_equal "#{quote_table_name "products"}.#{quote_column_name "stock"} = 1", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_not_equal
|
48
|
+
node = SearchCopGrammar::Attributes::Integer.new(Product, "products", "stock").not_eq(1)
|
49
|
+
|
50
|
+
assert_equal "#{quote_table_name "products"}.#{quote_column_name "stock"} != 1", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_matches
|
54
|
+
node = SearchCopGrammar::Attributes::String.new(Product, "products", "notice").matches("Notice")
|
55
|
+
|
56
|
+
assert_equal("#{quote_table_name "products"}.#{quote_column_name "notice"} LIKE #{quote "%Notice%"}", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)) if ENV["DATABASE"] != "postgres"
|
57
|
+
assert_equal("#{quote_table_name "products"}.#{quote_column_name "notice"} ILIKE #{quote "%Notice%"}", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)) if ENV["DATABASE"] == "postgres"
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_not
|
61
|
+
node = SearchCopGrammar::Attributes::Integer.new(Product, "products", "stock").eq(1).not
|
62
|
+
|
63
|
+
assert_equal "NOT (#{quote_table_name "products"}.#{quote_column_name "stock"} = 1)", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_attribute
|
67
|
+
# Already tested
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_quote
|
71
|
+
assert_equal quote("Test"), SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit("Test")
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_fulltext
|
75
|
+
node = SearchCopGrammar::Attributes::Collection.new(SearchCop::QueryInfo.new(Product, Product.search_scopes[:search]), "title").matches("Query").optimize!
|
76
|
+
|
77
|
+
assert_equal("MATCH(`products`.`title`) AGAINST('Query' IN BOOLEAN MODE)", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)) if ENV["DATABASE"] == "mysql"
|
78
|
+
assert_equal("to_tsvector('english', \"products\".\"title\") @@ to_tsquery('english', '''Query''')", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)) if ENV["DATABASE"] == "postgres"
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_fulltext_and
|
82
|
+
query1 = SearchCopGrammar::Attributes::Collection.new(SearchCop::QueryInfo.new(Product, Product.search_scopes[:search]), "title").matches("Query1")
|
83
|
+
query2 = SearchCopGrammar::Attributes::Collection.new(SearchCop::QueryInfo.new(Product, Product.search_scopes[:search]), "title").matches("Query2")
|
84
|
+
|
85
|
+
node = query1.and(query2).optimize!
|
86
|
+
|
87
|
+
assert_equal("(MATCH(`products`.`title`) AGAINST('+Query1 +Query2' IN BOOLEAN MODE))", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)) if ENV["DATABASE"] == "mysql"
|
88
|
+
assert_equal("(to_tsvector('english', \"products\".\"title\") @@ to_tsquery('english', '(''Query1'') & (''Query2'')'))", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)) if ENV["DATABASE"] == "postgres"
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_fulltext_or
|
92
|
+
query1 = SearchCopGrammar::Attributes::Collection.new(SearchCop::QueryInfo.new(Product, Product.search_scopes[:search]), "title").matches("Query1")
|
93
|
+
query2 = SearchCopGrammar::Attributes::Collection.new(SearchCop::QueryInfo.new(Product, Product.search_scopes[:search]), "title").matches("Query2")
|
94
|
+
|
95
|
+
node = query1.or(query2).optimize!
|
96
|
+
|
97
|
+
assert_equal("(MATCH(`products`.`title`) AGAINST('(Query1) (Query2)' IN BOOLEAN MODE))", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)) if ENV["DATABASE"] == "mysql"
|
98
|
+
assert_equal("(to_tsvector('english', \"products\".\"title\") @@ to_tsquery('english', '(''Query1'') | (''Query2'')'))", SearchCop::Visitors::Visitor.new(ActiveRecord::Base.connection).visit(node)) if ENV["DATABASE"] == "postgres"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: search_cop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-12-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: treetop
|
@@ -142,15 +142,18 @@ files:
|
|
142
142
|
- gemfiles/3.2.gemfile
|
143
143
|
- gemfiles/4.0.gemfile
|
144
144
|
- gemfiles/4.1.gemfile
|
145
|
+
- gemfiles/4.2.gemfile
|
145
146
|
- lib/search_cop.rb
|
146
|
-
- lib/search_cop/arel.rb
|
147
|
-
- lib/search_cop/arel/visitors.rb
|
148
147
|
- lib/search_cop/grammar_parser.rb
|
149
148
|
- lib/search_cop/hash_parser.rb
|
150
149
|
- lib/search_cop/query_builder.rb
|
151
150
|
- lib/search_cop/query_info.rb
|
152
151
|
- lib/search_cop/search_scope.rb
|
153
152
|
- lib/search_cop/version.rb
|
153
|
+
- lib/search_cop/visitors.rb
|
154
|
+
- lib/search_cop/visitors/mysql.rb
|
155
|
+
- lib/search_cop/visitors/postgres.rb
|
156
|
+
- lib/search_cop/visitors/visitor.rb
|
154
157
|
- lib/search_cop_grammar.rb
|
155
158
|
- lib/search_cop_grammar.treetop
|
156
159
|
- lib/search_cop_grammar/attributes.rb
|
@@ -172,6 +175,7 @@ files:
|
|
172
175
|
- test/search_cop_test.rb
|
173
176
|
- test/string_test.rb
|
174
177
|
- test/test_helper.rb
|
178
|
+
- test/visitor_test.rb
|
175
179
|
homepage: https://github.com/mrkamel/search_cop
|
176
180
|
licenses:
|
177
181
|
- MIT
|
@@ -215,3 +219,4 @@ test_files:
|
|
215
219
|
- test/search_cop_test.rb
|
216
220
|
- test/string_test.rb
|
217
221
|
- test/test_helper.rb
|
222
|
+
- test/visitor_test.rb
|
@@ -1,223 +0,0 @@
|
|
1
|
-
|
2
|
-
module SearchCop
|
3
|
-
module Arel
|
4
|
-
module Visitors
|
5
|
-
module ToSql
|
6
|
-
if ::Arel::VERSION >= "4.0.1"
|
7
|
-
def visit_SearchCopGrammar_Nodes_And(o, a)
|
8
|
-
visit ::Arel::Nodes::Grouping.new(o.nodes.inject { |res, cur| ::Arel::Nodes::And.new [res, cur] }), a
|
9
|
-
end
|
10
|
-
|
11
|
-
def visit_SearchCopGrammar_Nodes_Or(o, a)
|
12
|
-
visit ::Arel::Nodes::Grouping.new(o.nodes.inject { |res, cur| ::Arel::Nodes::Or.new res, cur }), a
|
13
|
-
end
|
14
|
-
|
15
|
-
def visit_SearchCopGrammar_Nodes_Equality(o, a)
|
16
|
-
visit ::Arel::Nodes::Equality.new(o.left, o.right), a
|
17
|
-
end
|
18
|
-
|
19
|
-
def visit_SearchCopGrammar_Nodes_NotEqual(o, a)
|
20
|
-
visit ::Arel::Nodes::NotEqual.new(o.left, o.right), a
|
21
|
-
end
|
22
|
-
|
23
|
-
def visit_SearchCopGrammar_Nodes_LessThan(o, a)
|
24
|
-
visit ::Arel::Nodes::LessThan.new(o.left, o.right), a
|
25
|
-
end
|
26
|
-
|
27
|
-
def visit_SearchCopGrammar_Nodes_LessThanOrEqual(o, a)
|
28
|
-
visit ::Arel::Nodes::LessThanOrEqual.new(o.left, o.right), a
|
29
|
-
end
|
30
|
-
|
31
|
-
def visit_SearchCopGrammar_Nodes_GreaterThan(o, a)
|
32
|
-
visit ::Arel::Nodes::GreaterThan.new(o.left, o.right), a
|
33
|
-
end
|
34
|
-
|
35
|
-
def visit_SearchCopGrammar_Nodes_GreaterThanOrEqual(o, a)
|
36
|
-
visit ::Arel::Nodes::GreaterThanOrEqual.new(o.left, o.right), a
|
37
|
-
end
|
38
|
-
|
39
|
-
def visit_SearchCopGrammar_Nodes_Not(o, a)
|
40
|
-
visit ::Arel::Nodes::Not.new(o.object), a
|
41
|
-
end
|
42
|
-
|
43
|
-
def visit_SearchCopGrammar_Nodes_Matches(o, a)
|
44
|
-
visit ::Arel::Nodes::Matches.new(o.left, o.right), a
|
45
|
-
end
|
46
|
-
else
|
47
|
-
def visit_SearchCopGrammar_Nodes_And(o)
|
48
|
-
visit ::Arel::Nodes::Grouping.new(o.nodes.inject { |res, cur| ::Arel::Nodes::And.new [res, cur] })
|
49
|
-
end
|
50
|
-
|
51
|
-
def visit_SearchCopGrammar_Nodes_Or(o)
|
52
|
-
visit ::Arel::Nodes::Grouping.new(o.nodes.inject { |res, cur| ::Arel::Nodes::Or.new res, cur })
|
53
|
-
end
|
54
|
-
|
55
|
-
def visit_SearchCopGrammar_Nodes_Equality(o)
|
56
|
-
visit ::Arel::Nodes::Equality.new(o.left, o.right)
|
57
|
-
end
|
58
|
-
|
59
|
-
def visit_SearchCopGrammar_Nodes_NotEqual(o)
|
60
|
-
visit ::Arel::Nodes::NotEqual.new(o.left, o.right)
|
61
|
-
end
|
62
|
-
|
63
|
-
def visit_SearchCopGrammar_Nodes_LessThan(o)
|
64
|
-
visit ::Arel::Nodes::LessThan.new(o.left, o.right)
|
65
|
-
end
|
66
|
-
|
67
|
-
def visit_SearchCopGrammar_Nodes_LessThanOrEqual(o)
|
68
|
-
visit ::Arel::Nodes::LessThanOrEqual.new(o.left, o.right)
|
69
|
-
end
|
70
|
-
|
71
|
-
def visit_SearchCopGrammar_Nodes_GreaterThan(o)
|
72
|
-
visit ::Arel::Nodes::GreaterThan.new(o.left, o.right)
|
73
|
-
end
|
74
|
-
|
75
|
-
def visit_SearchCopGrammar_Nodes_GreaterThanOrEqual(o)
|
76
|
-
visit ::Arel::Nodes::GreaterThanOrEqual.new(o.left, o.right)
|
77
|
-
end
|
78
|
-
|
79
|
-
def visit_SearchCopGrammar_Nodes_Not(o)
|
80
|
-
visit ::Arel::Nodes::Not.new(o.object)
|
81
|
-
end
|
82
|
-
|
83
|
-
def visit_SearchCopGrammar_Nodes_Matches(o)
|
84
|
-
visit ::Arel::Nodes::Matches.new(o.left, o.right)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
module MySQL
|
90
|
-
if ::Arel::VERSION >= "4.0.1"
|
91
|
-
def visit_SearchCopGrammar_Attributes_Collection(o, a)
|
92
|
-
o.attributes.collect { |attribute| visit attribute.attribute, a }.join(", ")
|
93
|
-
end
|
94
|
-
|
95
|
-
def visit_SearchCopGrammar_Nodes_FulltextExpression(o, a)
|
96
|
-
"MATCH(#{visit o.collection, a}) AGAINST(#{visit visit(o.node, a), a} IN BOOLEAN MODE)"
|
97
|
-
end
|
98
|
-
|
99
|
-
def visit_SearchCopGrammar_Nodes_MatchesFulltextNot(o, a)
|
100
|
-
o.right.split(/[\s+'"<>()~-]+/).collect { |word| "-#{word}" }.join(" ")
|
101
|
-
end
|
102
|
-
|
103
|
-
def visit_SearchCopGrammar_Nodes_MatchesFulltext(o, a)
|
104
|
-
words = o.right.split(/[\s+'"<>()~-]+/)
|
105
|
-
|
106
|
-
words.size > 1 ? "\"#{words.join " "}\"" : words.first
|
107
|
-
end
|
108
|
-
|
109
|
-
def visit_SearchCopGrammar_Nodes_And_Fulltext(o, a)
|
110
|
-
res = o.nodes.collect do |node|
|
111
|
-
if node.is_a?(SearchCopGrammar::Nodes::MatchesFulltextNot)
|
112
|
-
visit node, a
|
113
|
-
else
|
114
|
-
node.nodes.size > 1 ? "+(#{visit node, a})" : "+#{visit node, a}"
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
res.join " "
|
119
|
-
end
|
120
|
-
|
121
|
-
def visit_SearchCopGrammar_Nodes_Or_Fulltext(o, a)
|
122
|
-
o.nodes.collect { |node| "(#{visit node, a})" }.join(" ")
|
123
|
-
end
|
124
|
-
else
|
125
|
-
def visit_SearchCopGrammar_Attributes_Collection(o)
|
126
|
-
o.attributes.collect { |attribute| visit attribute.attribute }.join(", ")
|
127
|
-
end
|
128
|
-
|
129
|
-
def visit_SearchCopGrammar_Nodes_FulltextExpression(o)
|
130
|
-
"MATCH(#{visit o.collection}) AGAINST(#{visit visit(o.node)} IN BOOLEAN MODE)"
|
131
|
-
end
|
132
|
-
|
133
|
-
def visit_SearchCopGrammar_Nodes_MatchesFulltextNot(o)
|
134
|
-
o.right.split(/[\s+'"<>()~-]+/).collect { |word| "-#{word}" }.join(" ")
|
135
|
-
end
|
136
|
-
|
137
|
-
def visit_SearchCopGrammar_Nodes_MatchesFulltext(o)
|
138
|
-
words = o.right.split(/[\s+'"<>()~-]+/)
|
139
|
-
|
140
|
-
words.size > 1 ? "\"#{words.join " "}\"" : words.first
|
141
|
-
end
|
142
|
-
|
143
|
-
def visit_SearchCopGrammar_Nodes_And_Fulltext(o)
|
144
|
-
res = o.nodes.collect do |node|
|
145
|
-
if node.is_a?(SearchCopGrammar::Nodes::MatchesFulltextNot)
|
146
|
-
visit node
|
147
|
-
else
|
148
|
-
node.nodes.size > 1 ? "+(#{visit node})" : "+#{visit node}"
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
res.join " "
|
153
|
-
end
|
154
|
-
|
155
|
-
def visit_SearchCopGrammar_Nodes_Or_Fulltext(o)
|
156
|
-
o.nodes.collect { |node| "(#{visit node})" }.join(" ")
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
module PostgreSQL
|
162
|
-
if ::Arel::VERSION >= "4.0.1"
|
163
|
-
def visit_SearchCopGrammar_Attributes_Collection(o, a)
|
164
|
-
o.attributes.collect { |attribute| visit attribute.attribute, a }.join(" || ' ' || ")
|
165
|
-
end
|
166
|
-
|
167
|
-
def visit_SearchCopGrammar_Nodes_FulltextExpression(o, a)
|
168
|
-
dictionary = o.collection.options[:dictionary] || "simple"
|
169
|
-
|
170
|
-
"to_tsvector(#{visit dictionary, a}, #{visit o.collection, a}) @@ to_tsquery(#{visit dictionary, a}, #{visit visit(o.node, a), a})"
|
171
|
-
end
|
172
|
-
|
173
|
-
def visit_SearchCopGrammar_Nodes_MatchesFulltextNot(o, a)
|
174
|
-
"!'#{o.right}'"
|
175
|
-
end
|
176
|
-
|
177
|
-
def visit_SearchCopGrammar_Nodes_MatchesFulltext(o, a)
|
178
|
-
"'#{o.right.gsub /[\s&|!:'"]+/, " "}'"
|
179
|
-
end
|
180
|
-
|
181
|
-
def visit_SearchCopGrammar_Nodes_And_Fulltext(o, a)
|
182
|
-
o.nodes.collect { |node| "(#{visit node, a})" }.join(" & ")
|
183
|
-
end
|
184
|
-
|
185
|
-
def visit_SearchCopGrammar_Nodes_Or_Fulltext(o, a)
|
186
|
-
o.nodes.collect { |node| "(#{visit node, a})" }.join(" | ")
|
187
|
-
end
|
188
|
-
else
|
189
|
-
def visit_SearchCopGrammar_Attributes_Collection(o)
|
190
|
-
o.attributes.collect { |attribute| visit attribute.attribute }.join(" || ' ' || ")
|
191
|
-
end
|
192
|
-
|
193
|
-
def visit_SearchCopGrammar_Nodes_FulltextExpression(o)
|
194
|
-
dictionary = o.collection.options[:dictionary] || "simple"
|
195
|
-
|
196
|
-
"to_tsvector(#{visit dictionary.to_sym}, #{visit o.collection}) @@ to_tsquery(#{visit dictionary.to_sym}, #{visit visit(o.node)})" # to_sym fixes a 3.2 + postgres bug
|
197
|
-
end
|
198
|
-
|
199
|
-
def visit_SearchCopGrammar_Nodes_MatchesFulltextNot(o)
|
200
|
-
"!'#{o.right}'"
|
201
|
-
end
|
202
|
-
|
203
|
-
def visit_SearchCopGrammar_Nodes_MatchesFulltext(o)
|
204
|
-
"'#{o.right.gsub /[\s&|!:'"]+/, " "}'"
|
205
|
-
end
|
206
|
-
|
207
|
-
def visit_SearchCopGrammar_Nodes_And_Fulltext(o)
|
208
|
-
o.nodes.collect { |node| "(#{visit node})" }.join(" & ")
|
209
|
-
end
|
210
|
-
|
211
|
-
def visit_SearchCopGrammar_Nodes_Or_Fulltext(o)
|
212
|
-
o.nodes.collect { |node| "(#{visit node})" }.join(" | ")
|
213
|
-
end
|
214
|
-
end
|
215
|
-
end
|
216
|
-
end
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
Arel::Visitors::PostgreSQL.send :include, SearchCop::Arel::Visitors::PostgreSQL
|
221
|
-
Arel::Visitors::MySQL.send :include, SearchCop::Arel::Visitors::MySQL
|
222
|
-
Arel::Visitors::ToSql.send :include, SearchCop::Arel::Visitors::ToSql
|
223
|
-
|
data/lib/search_cop/arel.rb
DELETED