logical_query_parser 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 8f62acc3ca3efe725b46798c405985136dcd0702
4
- data.tar.gz: 56e9d416089645c652649782a1c3d320a6bedc05
2
+ SHA256:
3
+ metadata.gz: c2a48abdb3c711d5984f63d4ff56d8d492e5c674a306833472435c64e2180d41
4
+ data.tar.gz: 85f5476c26e88e6f0cc5465eb46bbf62a7ac0b2a9bfa84183fd2a93cee0aef9b
5
5
  SHA512:
6
- metadata.gz: 83df4ab258dce60d775a4d0548b4f20e25ac26d0f424fc734ea70cc579a9664ecaeaefcb3c593901629428d8954ce7ac60a3682bc32c523232c0c5de0f804ac1
7
- data.tar.gz: 1ac865f2c09148e70e0da1ba31c964b53ebdf6fc31dbedb5cd602c1b5dceb74f46177ee6bb9442511ffe6679696035e9ffb287e9cf354685c873be1a6b93f69c
6
+ metadata.gz: 95909ee34ce7e60993844d69a96c6ef683a470b44113cd49c4045ceb7f6cfb0d6081731c2e13e7a699c7584f2d4efbfd368cff549291481b8471bb6d594b09ff
7
+ data.tar.gz: 1319eab5cf8dd32f8760cacb5ac0211b8c3d2330e95dc3426325e9bc7f0767b9d477c0c4b75e82061e5126117bf70ffe729252dae3de06dc04e0041b051215d3
data/.gitignore CHANGED
@@ -1,10 +1,7 @@
1
1
  /.bundle/
2
- /.yardoc
3
2
  /.project
4
3
  /Gemfile.lock
5
- /_yardoc/
6
4
  /coverage/
7
- /doc/
5
+ /gemfiles/*gemfile.lock
8
6
  /pkg/
9
- /spec/reports/
10
7
  /tmp/
data/.travis.yml CHANGED
@@ -1,12 +1,21 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.3.4
5
- - 2.4.1
4
+ - 2.3
5
+ - 2.4
6
+ - 2.5
7
+ - 2.6
6
8
  gemfile:
7
9
  - gemfiles/rails42.gemfile
8
10
  - gemfiles/rails50.gemfile
9
11
  - gemfiles/rails51.gemfile
10
- before_install:
11
- - gem install bundler -v 1.15.3
12
- script: bundle exec rspec
12
+ - gemfiles/rails52.gemfile
13
+ - gemfiles/rails60.gemfile
14
+ matrix:
15
+ exclude:
16
+ - rvm: 2.3
17
+ gemfile: gemfiles/rails60.gemfile
18
+ - rvm: 2.4
19
+ gemfile: gemfiles/rails60.gemfile
20
+ script:
21
+ - bundle exec rspec
data/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # CHANGELOG
2
+
3
+ ## 0.3.0
4
+
5
+ * Support searching with associations.
6
+ * Change module name.
7
+
8
+ ## 0.2.0
9
+
10
+ * Support operators: "NOT", "&", "|".
11
+
12
+ ## 0.1.0
13
+
14
+ * First release.
data/Gemfile CHANGED
@@ -1,4 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in logical_query_parser.gemspec
4
3
  gemspec
data/README.md CHANGED
@@ -79,36 +79,41 @@ parser.parse('("a a" AND NOT "b b") OR (c AND -d)')
79
79
  * ": represents beginning or end of a quoted word.
80
80
  * Space: represents a boundary between two words.
81
81
 
82
- For more information, see [grammar definition](lib/logical_query.treetop).
82
+ For more information, see [grammar definition](lib/logical_query_parser.treetop).
83
83
 
84
- ### Compile into SQL
84
+ ### Use with ActiveRecord
85
85
 
86
- You can compile a syntax tree into a SQL statement by using activerecord model. For example, the code below
86
+ You can use a syntax tree to compile a SQL statement for activerecord. For example:
87
87
 
88
88
  ```ruby
89
- class Table < ActiveRecord::Base
90
- def build_sql(str = "a AND b")
91
- parser = LogicalQueryParser.new
92
- parser.parse(str).to_sql(model: self, columns: %w(c1 c2))
93
- end
89
+ class Doc < ActiveRecord::Base
94
90
  end
95
- ```
96
-
97
- builds a SQL statement as follows (this example shows a postgresql statement):
98
91
 
99
- ```sql
100
- ("tables"."c1" ILIKE '%a%' OR "tables"."c2" ILIKE '%a%') AND ("tables"."c1" ILIKE '%b%' OR "tables"."c2" ILIKE '%b%')
92
+ LogicalQueryParser.search("a AND b", Doc.all, :c1, :c2).to_sql
93
+ # SELECT "docs".* FROM "docs"
94
+ # WHERE (("docs"."c1" LIKE '%a%' OR "docs"."c2" LIKE '%a%') AND ("docs"."c1" LIKE '%b%' OR "docs"."c2" LIKE '%b%'))
101
95
  ```
102
96
 
103
- ## Development
97
+ Use with associations:
104
98
 
105
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
99
+ ```ruby
100
+ class Doc < ActiveRecord::Base
101
+ has_many :tags
102
+ end
106
103
 
107
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
104
+ class Tag < ActiveRecord::Base
105
+ end
106
+
107
+ LogicalQueryParser.search("a AND b", Doc.all, :c1, :c2, tags: [:c3]).to_sql
108
+ # SELECT "docs".* FROM "docs"
109
+ # INNER JOIN "tags" ON "tags"."doc_id" = "docs"."id"
110
+ # WHERE ((("docs"."c1" LIKE '%a%' OR "docs"."c2" LIKE '%a%') OR "tags"."c3" LIKE '%a%') AND
111
+ # (("docs"."c1" LIKE '%b%' OR "docs"."c2" LIKE '%b%') OR "tags"."c3" LIKE '%b%'))
112
+ ```
108
113
 
109
114
  ## Contributing
110
115
 
111
- Bug reports and pull requests are welcome on GitHub at https://github.com/kanety/logical_query_parser. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
116
+ Bug reports and pull requests are welcome on GitHub at https://github.com/kanety/logical_query_parser.
112
117
 
113
118
  ## License
114
119
 
@@ -1,6 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gem "activerecord", "~> 4.2.9"
4
+ gem "sqlite3", "~> 1.3.6"
4
5
 
5
- # Specify your gem's dependencies in params_keeper_rails.gemspec
6
6
  gemspec path: "../"
@@ -1,6 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gem "activerecord", "~> 5.0.5"
4
+ gem "sqlite3", "~> 1.3.6"
4
5
 
5
- # Specify your gem's dependencies in params_keeper_rails.gemspec
6
6
  gemspec path: "../"
@@ -1,6 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gem "activerecord", "~> 5.1.3"
4
+ gem "sqlite3", "~> 1.3.6"
4
5
 
5
- # Specify your gem's dependencies in params_keeper_rails.gemspec
6
6
  gemspec path: "../"
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "activerecord", "~> 5.2.0"
4
+ gem "sqlite3", "~> 1.3.6"
5
+
6
+ gemspec path: "../"
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "activerecord", "~> 6.0.0"
4
+
5
+ gemspec path: "../"
@@ -1,12 +1,28 @@
1
1
  require 'treetop'
2
+ Treetop.load File.expand_path("../logical_query_parser.treetop", __FILE__)
3
+
2
4
  require 'logical_query_parser/version'
5
+ require 'logical_query_parser/assoc_resolver'
3
6
  require 'logical_query_parser/nodes/base'
4
- require 'logical_query_parser/nodes/active_record' if defined? ActiveRecord::Base
5
-
6
- Treetop.load File.expand_path("../logical_query.treetop", __FILE__)
7
+ require 'logical_query_parser/nodes/active_record' if defined? ::ActiveRecord::Base
7
8
 
8
- class LogicalQueryParser < Treetop::Runtime::CompiledParser
9
+ module LogicalQueryParser
9
10
  class << self
11
+ def new
12
+ LogicalQueryParserParser.new
13
+ end
14
+
15
+ def search(query, relations, *options)
16
+ relations = relations.all if relations.respond_to?(:all)
17
+ assoc = resolve_assocs(relations.klass, options)
18
+ sql = new.parse(query).to_sql(model: relations.klass, columns: assoc.column_mapping)
19
+ relations.joins(assoc.structure).where(sql)
20
+ end
21
+
22
+ def resolve_assocs(klass, options)
23
+ AssocResolver.new(klass).run(options)
24
+ end
25
+
10
26
  def walk_tree(node, &block)
11
27
  yield node
12
28
  unless node.elements.nil?
@@ -1,4 +1,4 @@
1
- grammar LogicalQuery
1
+ grammar LogicalQueryParser
2
2
  rule exp
3
3
  sp* exp:(logic_exp / paren_exp / literal_exp / literal) sp* <ExpNode>
4
4
  end
@@ -0,0 +1,11 @@
1
+ module LogicalQueryParser
2
+ class Assoc
3
+ attr_accessor :column_mapping, :structure
4
+ attr_accessor :current
5
+
6
+ def initialize(attrs = {})
7
+ @column_mapping = {}
8
+ @structure = {}
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,48 @@
1
+ require_relative 'assoc'
2
+
3
+ module LogicalQueryParser
4
+ class AssocResolver
5
+ def initialize(klass)
6
+ @klass = klass
7
+ end
8
+
9
+ def run(*args)
10
+ Assoc.new.tap do |assoc|
11
+ assoc.current = assoc.structure
12
+ resolve_assocs(@klass, args, assoc)
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def wrap_array(options)
19
+ if options.is_a?(Array)
20
+ options.flatten(1)
21
+ else
22
+ [options]
23
+ end
24
+ end
25
+
26
+ def resolve_assocs(klass, options, assoc)
27
+ options = wrap_array(options)
28
+ options.each do |option|
29
+ if option.is_a?(Hash)
30
+ resolve_assocs_for_hash(klass, option, assoc)
31
+ else
32
+ assoc.column_mapping[klass] ||= []
33
+ assoc.column_mapping[klass] << option
34
+ end
35
+ end
36
+ end
37
+
38
+ def resolve_assocs_for_hash(klass, hash, assoc)
39
+ hash.each do |assoc_name, options|
40
+ if (reflection = klass.reflect_on_association(assoc_name))
41
+ assoc.current[assoc_name] = {}
42
+ assoc.current = assoc.current[assoc_name]
43
+ resolve_assocs(reflection.klass, options, assoc)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,81 +1,109 @@
1
- module LogicalQuery
1
+ module LogicalQueryParser
2
2
  module ExpNode
3
- def to_sql(opts = {}, sql = '')
4
- exp.to_sql(opts, sql)
3
+ def to_sql(params = {})
4
+ params[:sql] ||= ''
5
+ exp.to_sql(params)
5
6
  end
6
7
  end
7
8
 
8
9
  module ParenExpNode
9
- def to_sql(opts, sql = '')
10
+ def to_sql(params)
10
11
  if negative.elements.size > 0
11
- negative.elements[0].to_sql(opts, sql)
12
+ negative.elements[0].to_sql(params)
12
13
  end
13
- lparen.to_sql(opts, sql)
14
- exp.to_sql(opts, sql)
15
- rparen.to_sql(opts, sql)
14
+ lparen.to_sql(params)
15
+ exp.to_sql(params)
16
+ rparen.to_sql(params)
16
17
  if rexp.elements.size > 0
17
- sql += ' AND '
18
- rexp.elements[0].to_sql(opts, sql)
18
+ params[:sql] += ' AND '
19
+ rexp.elements[0].to_sql(params)
19
20
  end
20
- sql
21
+ params[:sql]
21
22
  end
22
23
  end
23
24
 
24
25
  module LogicExpNode
25
- def to_sql(opts, sql = '')
26
- lexp.to_sql(opts, sql)
27
- logic.to_sql(opts, sql)
28
- rexp.to_sql(opts, sql)
26
+ def to_sql(params)
27
+ lexp.to_sql(params)
28
+ logic.to_sql(params)
29
+ rexp.to_sql(params)
29
30
  end
30
31
  end
31
32
 
32
33
  module LiteralExpNode
33
- def to_sql(opts, sql = '')
34
- literal.to_sql(opts, sql)
35
- sql << ' AND '
36
- exp.to_sql(opts, sql)
34
+ def to_sql(params)
35
+ literal.to_sql(params)
36
+ params[:sql] << ' AND '
37
+ exp.to_sql(params)
37
38
  end
38
39
  end
39
40
 
40
41
  module LParenNode
41
- def to_sql(opts, sql = '')
42
- sql << '('
42
+ def to_sql(params)
43
+ params[:sql] << '('
43
44
  end
44
45
  end
45
46
 
46
47
  module RParenNode
47
- def to_sql(opts, sql = '')
48
- sql << ')'
48
+ def to_sql(params)
49
+ params[:sql] << ')'
49
50
  end
50
51
  end
51
52
 
52
53
  module AndNode
53
- def to_sql(opts, sql = '')
54
- sql << ' AND '
54
+ def to_sql(params)
55
+ params[:sql] << ' AND '
55
56
  end
56
57
  end
57
58
 
58
59
  module OrNode
59
- def to_sql(opts, sql = '')
60
- sql << ' OR '
60
+ def to_sql(params)
61
+ params[:sql] << ' OR '
61
62
  end
62
63
  end
63
64
 
64
65
  module NotNode
65
- def to_sql(opts, sql = '')
66
- sql << 'NOT '
66
+ def to_sql(params)
67
+ params[:sql] << 'NOT '
67
68
  end
68
69
  end
69
70
 
70
71
  module LiteralNode
71
- def to_sql(opts, sql = '')
72
- operator, logic = negative.elements.size > 0 ? [:does_not_match, :and] : [:matches, :or]
73
- unquoted = LogicalQuery.unquote(word.text_value)
72
+ def to_sql(params)
73
+ operator, logic = operator_and_logic
74
+ text = LogicalQueryParser.unquote(word.text_value)
75
+
76
+ sql = build_arel(params, operator, text).reduce(logic).to_sql
77
+ sql = "(#{sql})" if sql[0] != '(' && sql[-1] != ')'
78
+ params[:sql] << sql
79
+ end
80
+
81
+ private
82
+
83
+ def operator_and_logic
84
+ if negative.elements.size > 0
85
+ return :does_not_match, :and
86
+ else
87
+ return :matches, :or
88
+ end
89
+ end
74
90
 
75
- arel = opts[:model].arel_table
76
- ss = opts[:columns].map { |c| arel[c].send(operator, "%#{unquoted}%") }.reduce(logic).to_sql
77
- ss = "(#{ss})" if ss[0] != '(' && ss[-1] != ')'
78
- sql << ss
91
+ def build_arel(params, operator, text)
92
+ if params[:columns].is_a?(Hash)
93
+ build_arel_from_hash(params[:model], params[:columns], operator, text)
94
+ else
95
+ build_arel_from_columns(params[:model], params[:columns], operator, text)
96
+ end
97
+ end
98
+
99
+ def build_arel_from_columns(klass, columns, operator, text)
100
+ columns.map { |column| klass.arel_table[column].send(operator, "%#{text}%") }
101
+ end
102
+
103
+ def build_arel_from_hash(klass, hash, operator, text)
104
+ hash.flat_map do |klass, columns|
105
+ build_arel_from_columns(klass, columns, operator, text)
106
+ end
79
107
  end
80
108
  end
81
109
 
@@ -1,4 +1,4 @@
1
- module LogicalQuery
1
+ module LogicalQueryParser
2
2
  module ExpNode
3
3
  end
4
4
 
@@ -1,3 +1,3 @@
1
- module LogicalQuery
2
- VERSION = "0.2.0"
1
+ module LogicalQueryParser
2
+ VERSION = "0.3.0"
3
3
  end
@@ -5,7 +5,7 @@ require 'logical_query_parser/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "logical_query_parser"
8
- spec.version = LogicalQuery::VERSION
8
+ spec.version = LogicalQueryParser::VERSION
9
9
  spec.authors = ["Yoshikazu Kaneta"]
10
10
  spec.email = ["kaneta@sitebridge.co.jp"]
11
11
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logical_query_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yoshikazu Kaneta
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-10-07 00:00:00.000000000 Z
11
+ date: 2019-09-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: treetop
@@ -119,6 +119,7 @@ files:
119
119
  - ".gitignore"
120
120
  - ".rspec"
121
121
  - ".travis.yml"
122
+ - CHANGELOG.md
122
123
  - CODE_OF_CONDUCT.md
123
124
  - Gemfile
124
125
  - LICENSE.txt
@@ -129,8 +130,12 @@ files:
129
130
  - gemfiles/rails42.gemfile
130
131
  - gemfiles/rails50.gemfile
131
132
  - gemfiles/rails51.gemfile
132
- - lib/logical_query.treetop
133
+ - gemfiles/rails52.gemfile
134
+ - gemfiles/rails60.gemfile
133
135
  - lib/logical_query_parser.rb
136
+ - lib/logical_query_parser.treetop
137
+ - lib/logical_query_parser/assoc.rb
138
+ - lib/logical_query_parser/assoc_resolver.rb
134
139
  - lib/logical_query_parser/nodes/active_record.rb
135
140
  - lib/logical_query_parser/nodes/base.rb
136
141
  - lib/logical_query_parser/version.rb
@@ -154,8 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
154
159
  - !ruby/object:Gem::Version
155
160
  version: '0'
156
161
  requirements: []
157
- rubyforge_project:
158
- rubygems_version: 2.6.12
162
+ rubygems_version: 3.0.3
159
163
  signing_key:
160
164
  specification_version: 4
161
165
  summary: A parser for a logical query string.