search_cop 1.0.2 → 1.0.3
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/.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