command_search 0.10.0 → 0.11.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.
- checksums.yaml +4 -4
- data/lib/command_search/backends/postgres.rb +94 -0
- data/lib/command_search/normalizer.rb +13 -7
- data/lib/command_search.rb +18 -7
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6ab70b519f399ddeeba1a8862085f8017d327b60a2cbfddaa4682363e56d2f38
|
|
4
|
+
data.tar.gz: 204fc638f22b3ffddef67f3065c551885c9b116b4fcadffb118f52d4d7ccffb2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 15beca3aa277baf873b57ff539b46c7dc966e357801ed69ed3818b17d89e021a6ead6bc233e119fd227bfc62615e2e0078e954cd92b9a32b216e3414cf6313e4
|
|
7
|
+
data.tar.gz: 34da12042bd8b24d8b83bdceb08454a8ec72517cd3cfca155072cc286d0b54c683bfa16fc65f8f579759ea1a8279576c989fbd7599fdd818e0a0d45b2a7cd646
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
module CommandSearch
|
|
2
|
+
module Postgres
|
|
3
|
+
module_function
|
|
4
|
+
|
|
5
|
+
def quote_string(str)
|
|
6
|
+
# activerecord/lib/active_record/connection_adapters/abstract/quoting.rb:62
|
|
7
|
+
str.gsub!('\\', '\&\&')
|
|
8
|
+
str.gsub!("'", "''")
|
|
9
|
+
Regexp.escape(str)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def build_quoted_regex(input)
|
|
13
|
+
str = quote_string(input)
|
|
14
|
+
if str[/(^\W)|(\W$)/]
|
|
15
|
+
head_border = '(^|\s|[^:+\w])'
|
|
16
|
+
tail_border = '($|\s|[^:+\w])'
|
|
17
|
+
return head_border + str + tail_border
|
|
18
|
+
end
|
|
19
|
+
'\m' + str + '\y'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def command_search(node)
|
|
23
|
+
field_node = node[:value].first
|
|
24
|
+
field = field_node[:value]
|
|
25
|
+
search_node = node[:value].last
|
|
26
|
+
val = search_node[:value]
|
|
27
|
+
type = search_node[:type]
|
|
28
|
+
return '0 = 1' if field == '__CommandSearch_dummy_key__'
|
|
29
|
+
if type == Boolean || type == :existence
|
|
30
|
+
false_val = "'f'"
|
|
31
|
+
false_val = 0 if field_node[:field_type] == Numeric
|
|
32
|
+
if val
|
|
33
|
+
return "NOT ((#{field} = #{false_val}) OR (#{field} IS NULL))"
|
|
34
|
+
end
|
|
35
|
+
return "((#{field} = #{false_val}) OR (#{field} IS NULL))"
|
|
36
|
+
end
|
|
37
|
+
if type == Time
|
|
38
|
+
return '0 = 1' unless val
|
|
39
|
+
return "(#{field} >= '#{val[0]}') AND (#{field} < '#{val[1]}') AND (#{field} IS NOT NULL)"
|
|
40
|
+
end
|
|
41
|
+
if type == :quote
|
|
42
|
+
op = '~'
|
|
43
|
+
val = "'#{build_quoted_regex(val)}'"
|
|
44
|
+
elsif type == :str
|
|
45
|
+
op = '~*'
|
|
46
|
+
val = "'#{quote_string(val)}'"
|
|
47
|
+
elsif type == :number
|
|
48
|
+
op = '='
|
|
49
|
+
end
|
|
50
|
+
"(#{field} #{op} #{val}) AND (#{field} IS NOT NULL)"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def compare_search(node)
|
|
54
|
+
field_node = node[:value].first
|
|
55
|
+
field = field_node[:value]
|
|
56
|
+
search_node = node[:value].last
|
|
57
|
+
val = search_node[:value]
|
|
58
|
+
type = search_node[:type]
|
|
59
|
+
op = node[:nest_op]
|
|
60
|
+
if node[:compare_across_fields]
|
|
61
|
+
"(#{field} #{op} #{val}) AND (#{field} IS NOT NULL) AND (#{val} IS NOT NULL)"
|
|
62
|
+
elsif type == Time && val
|
|
63
|
+
"(#{field} #{op} '#{val}') AND (#{field} IS NOT NULL)"
|
|
64
|
+
elsif val.is_a?(Numeric) || val == val.to_i.to_s || val == val.to_f.to_s
|
|
65
|
+
"(#{field} #{op} #{val}) AND (#{field} IS NOT NULL)"
|
|
66
|
+
else
|
|
67
|
+
'0 = 1'
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def build_query(ast)
|
|
72
|
+
out = []
|
|
73
|
+
ast = [ast] unless ast.is_a?(Array)
|
|
74
|
+
ast.each do |node|
|
|
75
|
+
type = node[:type]
|
|
76
|
+
if type == :colon
|
|
77
|
+
out.push(command_search(node))
|
|
78
|
+
elsif type == :compare
|
|
79
|
+
out.push(compare_search(node))
|
|
80
|
+
elsif type == :and
|
|
81
|
+
out.push(build_query(node[:value]))
|
|
82
|
+
elsif type == :or
|
|
83
|
+
clauses = node[:value].map { |x| build_query(x) }
|
|
84
|
+
clause = clauses.join(' OR ')
|
|
85
|
+
out.push("(#{clause})")
|
|
86
|
+
elsif type == :not
|
|
87
|
+
clause = build_query(node[:value])
|
|
88
|
+
out.push("NOT (#{clause})")
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
out.join(' AND ')
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -96,22 +96,28 @@ module CommandSearch
|
|
|
96
96
|
{ type: :or, value: new_val }
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
-
def type_cast!(node, fields)
|
|
99
|
+
def type_cast!(node, fields, cast_all)
|
|
100
100
|
(key_node, search_node) = node[:value]
|
|
101
101
|
key = key_node[:value]
|
|
102
102
|
field = fields[key.to_sym] || fields[key.to_s]
|
|
103
103
|
return unless field
|
|
104
104
|
type = field.is_a?(Class) ? field : field[:type]
|
|
105
|
+
type = Numeric if type == Integer
|
|
106
|
+
key_node[:field_type] = type
|
|
105
107
|
cast_bool!(field, search_node)
|
|
106
108
|
return cast_time!(node) if [Time, Date, DateTime].include?(type)
|
|
107
|
-
return cast_numeric!(search_node) if
|
|
108
|
-
|
|
109
|
+
return cast_numeric!(search_node) if Numeric == type
|
|
110
|
+
if cast_all
|
|
111
|
+
cast_regex!(search_node)
|
|
112
|
+
else
|
|
113
|
+
search_node[:type] = :str if search_node[:type] == :number
|
|
114
|
+
end
|
|
109
115
|
end
|
|
110
116
|
|
|
111
|
-
def normalize!(ast, fields)
|
|
117
|
+
def normalize!(ast, fields, cast_all = true)
|
|
112
118
|
ast.map! do |node|
|
|
113
119
|
if node[:type] == :and || node[:type] == :or || node[:type] == :not
|
|
114
|
-
normalize!(node[:value], fields)
|
|
120
|
+
normalize!(node[:value], fields, cast_all)
|
|
115
121
|
next node
|
|
116
122
|
end
|
|
117
123
|
if node[:type] == :colon || node[:type] == :compare
|
|
@@ -127,9 +133,9 @@ module CommandSearch
|
|
|
127
133
|
node = split_general_fields(node, fields)
|
|
128
134
|
end
|
|
129
135
|
if node[:type] == :or
|
|
130
|
-
node[:value].each { |x| type_cast!(x, fields) }
|
|
136
|
+
node[:value].each { |x| type_cast!(x, fields, cast_all) }
|
|
131
137
|
else
|
|
132
|
-
type_cast!(node, fields)
|
|
138
|
+
type_cast!(node, fields, cast_all)
|
|
133
139
|
end
|
|
134
140
|
node
|
|
135
141
|
end
|
data/lib/command_search.rb
CHANGED
|
@@ -6,28 +6,39 @@ load(__dir__ + '/command_search/optimizer.rb')
|
|
|
6
6
|
|
|
7
7
|
load(__dir__ + '/command_search/backends/memory.rb')
|
|
8
8
|
load(__dir__ + '/command_search/backends/mongoer.rb')
|
|
9
|
+
load(__dir__ + '/command_search/backends/postgres.rb')
|
|
9
10
|
|
|
10
11
|
class Boolean; end
|
|
11
12
|
|
|
12
13
|
module CommandSearch
|
|
13
14
|
module_function
|
|
14
15
|
|
|
15
|
-
def
|
|
16
|
+
def build(type, query, options)
|
|
16
17
|
aliases = options[:aliases] || {}
|
|
17
18
|
fields = options[:fields] || {}
|
|
18
|
-
|
|
19
19
|
aliased_query = Aliaser.alias(query, aliases)
|
|
20
20
|
ast = Lexer.lex(aliased_query)
|
|
21
|
-
|
|
22
21
|
Parser.parse!(ast)
|
|
23
22
|
Optimizer.optimize!(ast)
|
|
23
|
+
if type == :postgres
|
|
24
|
+
Normalizer.normalize!(ast, fields, false)
|
|
25
|
+
return Postgres.build_query(ast)
|
|
26
|
+
end
|
|
24
27
|
Normalizer.normalize!(ast, fields)
|
|
28
|
+
return Mongoer.build_query(ast) if type == :mongo
|
|
29
|
+
ast
|
|
30
|
+
end
|
|
25
31
|
|
|
32
|
+
def search(source, query, options)
|
|
26
33
|
if source.respond_to?(:mongo_client) && source.queryable
|
|
27
|
-
|
|
28
|
-
return source.where(
|
|
34
|
+
ast = CommandSearch.build(:mongo, query, options)
|
|
35
|
+
return source.where(ast)
|
|
29
36
|
end
|
|
30
|
-
|
|
31
|
-
|
|
37
|
+
if source.respond_to?(:postgresql_connection)
|
|
38
|
+
ast = CommandSearch.build(:postgres, query, options)
|
|
39
|
+
return source.where(ast)
|
|
40
|
+
end
|
|
41
|
+
ast = CommandSearch.build(:other, query, options)
|
|
42
|
+
source.select { |x| CommandSearch::Memory.check(x, ast) }
|
|
32
43
|
end
|
|
33
44
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: command_search
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- zumbalogy
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2019-
|
|
11
|
+
date: 2019-12-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: chronic
|
|
@@ -34,6 +34,7 @@ files:
|
|
|
34
34
|
- lib/command_search/aliaser.rb
|
|
35
35
|
- lib/command_search/backends/memory.rb
|
|
36
36
|
- lib/command_search/backends/mongoer.rb
|
|
37
|
+
- lib/command_search/backends/postgres.rb
|
|
37
38
|
- lib/command_search/lexer.rb
|
|
38
39
|
- lib/command_search/normalizer.rb
|
|
39
40
|
- lib/command_search/optimizer.rb
|
|
@@ -57,7 +58,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
57
58
|
- !ruby/object:Gem::Version
|
|
58
59
|
version: '0'
|
|
59
60
|
requirements: []
|
|
60
|
-
rubygems_version: 3.0.
|
|
61
|
+
rubygems_version: 3.0.3
|
|
61
62
|
signing_key:
|
|
62
63
|
specification_version: 4
|
|
63
64
|
summary: Let users query collections with ease.
|