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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fa471a870d21295459efa58aa18e3777752841dc0cfec3033a6423996ed2d98c
4
- data.tar.gz: 37a0b72e6cb764c90fd14eb44b0a1c096d4e6c6e2756a4715db3e02471aa6889
3
+ metadata.gz: 6ab70b519f399ddeeba1a8862085f8017d327b60a2cbfddaa4682363e56d2f38
4
+ data.tar.gz: 204fc638f22b3ffddef67f3065c551885c9b116b4fcadffb118f52d4d7ccffb2
5
5
  SHA512:
6
- metadata.gz: fe02049b5e6eec1b62ffa6d030a747312b661f4db05befb66f5e3585bf0b5b679f65c10c7ba3348a19de97ecdb0d4f9f19b2e5466b75bd4604658b66021c6fe0
7
- data.tar.gz: a5e0fa90644d5c7dd3346e1b9c7e43ef04c6e652f9f293ac59b468a2667bb388be8614854680612b62cda2812179fd800ee45a9e4229c7d4774760facd2b0b79
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 [Integer, Numeric].include?(type)
108
- cast_regex!(search_node)
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
@@ -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 search(source, query, options)
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
- mongo_query = Mongoer.build_query(ast)
28
- return source.where(mongo_query)
34
+ ast = CommandSearch.build(:mongo, query, options)
35
+ return source.where(ast)
29
36
  end
30
-
31
- source.select { |x| Memory.check(x, ast) }
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.10.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-14 00:00:00.000000000 Z
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.6
61
+ rubygems_version: 3.0.3
61
62
  signing_key:
62
63
  specification_version: 4
63
64
  summary: Let users query collections with ease.