solrbee 0.4.0 → 0.5.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
2
  SHA256:
3
- metadata.gz: 11deb067a219ff325c0aa6dfaeb14422674cfd2a30338d14a1f00fa3f2081550
4
- data.tar.gz: b6566f900e8a437feb379dba1e7f8de64e9c5a4b821cc501256ce0ee58da52e1
3
+ metadata.gz: 32b15ba57c92024c22c90c365c673098bbc5031fb14e76ae62ea75868e0d1ec3
4
+ data.tar.gz: 7aa16b385b8ddff45506966c1fbbb9b3e6252cfff2053b4f29377f09b9c4b9a1
5
5
  SHA512:
6
- metadata.gz: aa65e21c6552dc4d4276ecfdc1a4089e69d3625af84d014cb6353183a9e145ed8adf34158b180e2c70d1718d211e35b32fbd545438bd13753d171484b1577010
7
- data.tar.gz: 78fd833f50640f5f875be47160a87642f2b612c90906e3d349e68042f5d267b4b375727babcc8c2512c90006e7e858535e2382ecfc638d7dff333822f20e04c4
6
+ metadata.gz: c20241f1f4ed8e31fee8aa383bcf1a643f3dd94f8a6435d2ba170efe2858b0215472c65434575d62dfe9b40fb3899050c9bf39eb1457443ad29ff744d926f87f
7
+ data.tar.gz: '023180eea1874e7d4c17caa61bf39b5b8d5163b5681b4d0356ee8b41c65de4d9a4b32ddc233645d38b780fc6a4ae8345bd3d808d241e2d108021c38e67f9ccc5'
@@ -1,9 +1,13 @@
1
- require 'rom-http'
2
1
  require 'securerandom'
2
+ require 'rom-http'
3
3
 
4
4
  module ROM
5
5
  module Solr
6
6
 
7
+ ESCAPE_CHARS = Regexp.new('[%s]' % Regexp.escape('+-!(){}[]^"~*?:/'))
8
+ DOUBLE_AMPERSAND = Regexp.new('&&')
9
+ DOUBLE_PIPE = Regexp.new('\|\|')
10
+
7
11
  def self.dataset_class(name)
8
12
  prefix = name.to_s.split(/[_\/]/).map(&:capitalize).join('')
9
13
  const_name = "#{prefix}Dataset"
@@ -20,32 +24,33 @@ module ROM
20
24
  end
21
25
 
22
26
  # Utilities
23
- require_relative 'solr/array'
27
+ require 'rom/solr/array'
28
+ require 'rom/solr/utils'
24
29
 
25
30
  # Handlers
26
- require_relative 'solr/request_handler'
27
- require_relative 'solr/response_handler'
31
+ require 'rom/solr/request_handler'
32
+ require 'rom/solr/response_handler'
28
33
 
29
34
  # Datasets
30
- require_relative 'solr/dataset'
31
- require_relative 'solr/documents_dataset'
32
- require_relative 'solr/schema_info_dataset'
35
+ require 'rom/solr/dataset'
36
+ require 'rom/solr/documents_dataset'
37
+ require 'rom/solr/schema_info_dataset'
33
38
 
34
39
  # Gateway
35
- require_relative 'solr/gateway'
40
+ require 'rom/solr/gateway'
36
41
 
37
42
  # Schemas
38
- require_relative 'solr/schema'
43
+ require 'rom/solr/schema'
39
44
 
40
45
  # Relations
41
- require_relative 'solr/relation'
46
+ require 'rom/solr/relation'
42
47
 
43
48
  # Repositories
44
- require_relative 'solr/repository'
45
- require_relative 'solr/schema_info_repo'
46
- require_relative 'solr/document_repo'
49
+ require 'rom/solr/repository'
50
+ require 'rom/solr/schema_info_repo'
51
+ require 'rom/solr/document_repo'
47
52
 
48
53
  # Commands
49
- require_relative 'solr/commands'
54
+ require 'rom/solr/commands'
50
55
 
51
56
  ROM.register_adapter(:solr, ROM::Solr)
@@ -6,7 +6,7 @@ module ROM
6
6
  relation :documents
7
7
 
8
8
  def execute(docs)
9
- relation.insert(docs)
9
+ relation.insert(docs).response
10
10
  end
11
11
 
12
12
  end
@@ -6,7 +6,7 @@ module ROM
6
6
  relation :documents
7
7
 
8
8
  def execute(docs)
9
- relation.delete(docs)
9
+ relation.delete(docs).response
10
10
  end
11
11
 
12
12
  end
@@ -6,7 +6,7 @@ module ROM
6
6
  relation :documents
7
7
 
8
8
  def execute(docs)
9
- relation.update(docs)
9
+ relation.update(docs).response
10
10
  end
11
11
 
12
12
  end
@@ -2,44 +2,32 @@ module ROM
2
2
  module Solr
3
3
  class Dataset < ROM::HTTP::Dataset
4
4
 
5
- setting :default_response_key, reader: true
6
- setting :default_content_type, reader: true
7
- setting :default_base_path, reader: true
5
+ setting :default_base_path, reader: true
8
6
 
9
7
  configure do |config|
10
8
  config.default_response_handler = ResponseHandler
11
9
  config.default_request_handler = RequestHandler
12
10
  end
13
11
 
14
- option :response_key, default: proc { self.class.default_response_key }
15
- option :request_data, type: Types::String, default: proc { EMPTY_STRING }
16
- option :content_type, type: Types::String, default: proc { self.class.default_content_type }
17
- option :base_path, type: Types::Path, default: proc { self.class.default_base_path || EMPTY_STRING }
12
+ option :request_data, type: Types::String, optional: true
13
+ option :content_type, type: Types::String, optional: true
14
+
15
+ # @override
16
+ option :base_path, type: Types::Path, default: proc { self.class.default_base_path || EMPTY_STRING }
18
17
 
19
18
  # @override Query parameters are valid with POST, too.
20
19
  def uri
21
20
  uri_s = [options[:uri], path].compact.reject(&:empty?).join('/')
22
21
 
23
22
  URI(uri_s).tap do |u|
24
- u.query = param_encoder.call(params) if has_params?
23
+ u.query = param_encoder.call(params) if params?
25
24
  end
26
25
  end
27
26
 
28
- # @override
29
- def each(&block)
30
- return to_enum unless block_given?
31
-
32
- enumerable_data.each(&block)
33
- end
34
-
35
27
  def with_request_data(data)
36
28
  with_options(request_data: data)
37
29
  end
38
30
 
39
- def with_response_key(*path)
40
- with_options(response_key: path)
41
- end
42
-
43
31
  # Copies and makes private superclass #response method
44
32
  alias_method :__response__, :response
45
33
  private :__response__
@@ -49,20 +37,28 @@ module ROM
49
37
  cache.fetch_or_store(:response) { __response__ }
50
38
  end
51
39
 
52
- def has_request_data?
53
- !request_data.nil? && !request_data.empty?
40
+ def response_header(key)
41
+ response.dig(:responseHeader, key)
42
+ end
43
+
44
+ def request_data?
45
+ !request_data.nil?
54
46
  end
55
47
 
56
- def has_params?
48
+ def params?
57
49
  params.any?
58
50
  end
59
51
 
60
- private
52
+ def status
53
+ response_header(:status)
54
+ end
61
55
 
62
- def enumerable_data
63
- Array.wrap(response_key ? response.dig(*response_key) : response)
56
+ def qtime
57
+ response_header(:QTime)
64
58
  end
65
59
 
60
+ private
61
+
66
62
  def cache
67
63
  @cache ||= Concurrent::Map.new
68
64
  end
@@ -1,3 +1,5 @@
1
+ require 'rom/solr/query_builder'
2
+
1
3
  module ROM
2
4
  module Solr
3
5
  class DocumentRepo < Repository[:documents]
@@ -8,24 +10,34 @@ module ROM
8
10
  documents.by_unique_key(id).one!
9
11
  end
10
12
 
11
- def search
12
- documents
13
- end
14
-
15
13
  def all
16
14
  documents.all
17
15
  end
18
16
 
19
- def create(docs)
20
- documents.command(:create_documents).call(docs)
17
+ def create(docs, **opts)
18
+ docs_command(:create_documents, docs, **opts)
19
+ end
20
+
21
+ def delete(docs, **opts)
22
+ docs_command(:delete_documents, docs, **opts)
23
+ end
24
+
25
+ def delete_by_query(query, **opts)
26
+ docs_command(:delete_documents_by_query, query, **opts)
21
27
  end
22
28
 
23
- def delete(docs)
24
- documents.command(:delete_documents).call(docs)
29
+ def update(docs, **opts)
30
+ docs_command(:update_documents, docs, **opts)
25
31
  end
26
32
 
27
- def update(docs)
28
- documents.command(:update_documents).call(docs)
33
+ private
34
+
35
+ def docs_command(command, data, commit: false, commit_within: nil)
36
+ documents
37
+ .commit(commit)
38
+ .commit_within(commit_within)
39
+ .command(command)
40
+ .call(data)
29
41
  end
30
42
 
31
43
  end
@@ -3,8 +3,58 @@ module ROM
3
3
  class DocumentsDataset < Dataset
4
4
 
5
5
  configure do |config|
6
- config.default_response_key = [:response, :docs]
7
- config.default_base_path = 'select'
6
+ config.default_base_path = 'select'
7
+ end
8
+
9
+ # @override
10
+ def each(&block)
11
+ return to_enum unless block_given?
12
+
13
+ docs.each(&block)
14
+ end
15
+
16
+ def docs
17
+ search_response(:docs)
18
+ end
19
+
20
+ def cursor_mark
21
+ params[:cursorMark]
22
+ end
23
+
24
+ def next_cursor_mark
25
+ response[:nextCursorMark]
26
+ end
27
+
28
+ def num_found
29
+ search_response(:numFound)
30
+ end
31
+
32
+ def num_found_exact
33
+ search_response(:numFoundExact)
34
+ end
35
+
36
+ def num_found_exact?
37
+ num_found_exact === true
38
+ end
39
+
40
+ def search_response(key)
41
+ response.dig(:response, key)
42
+ end
43
+
44
+ def partial_results
45
+ response_header(:partialResults)
46
+ end
47
+
48
+ def partial_results?
49
+ partial_results === true
50
+ end
51
+
52
+ def response_params
53
+ response_header(:params)
54
+ end
55
+
56
+ def response_params?
57
+ !response_params.nil? && !response_params.empty?
8
58
  end
9
59
 
10
60
  end
@@ -0,0 +1,21 @@
1
+ require 'delegate'
2
+
3
+ module ROM
4
+ module Solr
5
+ #
6
+ # Wraps a DocumentsDataset to provide pagination with a cursor.
7
+ #
8
+ class DocumentsPaginator < SimpleDelegator
9
+
10
+ def each(&block)
11
+ while true
12
+ super
13
+ break if cursor_mark == next_cursor_mark
14
+ next_page = __getobj__.add_params(cursorMark: next_cursor_mark)
15
+ __setobj__(next_page)
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module Solr
5
+ module Query
6
+ include Utils
7
+
8
+ ANY_VALUE = '[* TO *]'
9
+ OR = ' OR '
10
+ AND = ' AND '
11
+
12
+ # Templates
13
+ DISJUNCTION = '{!lucene q.op=OR df=%{field}}%{value}'
14
+ JOIN = '{!join from=%{from} to=%{to}}%{field}:%{value}'
15
+ NEGATION = '-%{field}:%{value}'
16
+ REGEXP = '%{field}:/%{value}/'
17
+ STANDARD = '%{field}:%{value}'
18
+ TERM = '{!term f=%{field}}%{value}'
19
+
20
+ # Value transformers
21
+ NOOP = ->(v) { v }
22
+ ESCAPE = ->(v) { escape(v) }
23
+ INTEGER = ->(v) { v.to_i }
24
+ BEFORE_DATE = ->(v) { '[* TO %s]' % solr_date(v) }
25
+ AFTER_DATE = ->(v) { '[%s TO NOW]' % solr_date(v) }
26
+ BETWEEN_DATES = ->(a, b) { '[%s TO %s]' % [solr_date(a), solr_date(b)] }
27
+ EXACT_DATE = ->(v) { escape(solr_date(v)) }
28
+
29
+ # Build standard query clause(s) -- i.e., field:value --
30
+ # and disjunction clauses (i.e., when value is an array).
31
+ #
32
+ # @param mapping [Hash] field=>value mapping
33
+ # @return [Array<String>] queries
34
+ def where(mapping)
35
+ render(STANDARD, mapping, ->(v) { Array.wrap(v).join(OR) })
36
+ end
37
+
38
+ # Builds negation clause(s) -- i.e., -field:value
39
+ #
40
+ # @param mapping [Hash] field=>value mapping
41
+ # @return [Array<String>] queries
42
+ def where_not(mapping)
43
+ render(NEGATION, mapping)
44
+ end
45
+
46
+ # Builds query clause(s) to filter where field is present
47
+ # (i.e., has one or more values)
48
+ #
49
+ # @param fields [Array<String>] on or more fields
50
+ # @return [Array<String>] queries
51
+ def exist(*fields)
52
+ mapping = fields.map { |field| {field: field, value: ANY_VALUE} }
53
+
54
+ render(STANDARD, mapping)
55
+ end
56
+
57
+ # Builds query clause(s) to filter where field is NOT present
58
+ # (i.e., no values)
59
+ #
60
+ # @param fields [Array<Symbol, String>] one or more fields
61
+ # @return [Array<String>] queries
62
+ def not_exist(*fields)
63
+ mapping = fields.map { |field| {field: "-#{field}", value: ANY_VALUE} }
64
+
65
+ render(STANDARD, mapping)
66
+ end
67
+
68
+ # Builds a Solr join clause
69
+ #
70
+ # @see https://wiki.apache.org/solr/Join
71
+ # @param from [String]
72
+ # @param to [String]
73
+ # @param field [String]
74
+ # @param value [String]
75
+ # @return [Array<String>] queries
76
+ def join(from:, to:, field:, value:)
77
+ [ JOIN % { from: from, to: to, field: field, value: value } ]
78
+ end
79
+
80
+ # Builds query clause(s) to filter where date field value
81
+ # is earlier than a date/time value.
82
+ #
83
+ # @param mapping [Hash] field=>value mapping
84
+ # Values (coerced to strings) must be parseable by `DateTime.parse`.
85
+ # @return [Array<String>] queries
86
+ def before(mapping)
87
+ render(STANDARD, mapping, BEFORE_DATE)
88
+ end
89
+
90
+ # Builds query clause(s) to filter where date field value
91
+ # is later than a date/time value.
92
+ #
93
+ # @param mapping [Hash] field=>value mapping
94
+ # Values (coerced to strings) must be parseable by `DateTime.parse`.
95
+ # @return [Array<String>] queries
96
+ def after(mapping)
97
+ render(STANDARD, mapping, AFTER_DATE)
98
+ end
99
+
100
+ def between_dates(mapping)
101
+ render(STANDARD, mapping, BETWEEN_DATES)
102
+ end
103
+
104
+ def exact_date(mapping)
105
+ render(STANDARD, mapping, EXACT_DATE)
106
+ end
107
+
108
+ # Builds term query clause(s) to filter where field contains value.
109
+ #
110
+ # @param mapping [Hash] field=>value mapping
111
+ # @return [Array<String>] queries
112
+ def term(mapping)
113
+ render(TERM, mapping)
114
+ end
115
+
116
+ # Builds regular expression query clause(s).
117
+ #
118
+ # @param mapping [Hash] field=>value mapping
119
+ # @return [Array<String>] queries
120
+ def regexp(mapping)
121
+ render(REGEXP, mapping, ESCAPE_SLASHE)
122
+ end
123
+
124
+ private
125
+
126
+ def render(template, mapping, transformer = NOOP)
127
+ mapping.transform_values(&transformer).map do |field, value|
128
+ template % {field: field, value: value}
129
+ end
130
+ end
131
+
132
+ extend self
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,36 @@
1
+ require 'rom/solr/query'
2
+
3
+ module ROM
4
+ module Solr
5
+ class QueryBuilder
6
+ include Utils
7
+
8
+ attr_accessor :queries
9
+
10
+ def initialize(queries = [])
11
+ @queries = queries
12
+ end
13
+
14
+ def to_a
15
+ queries
16
+ end
17
+
18
+ def raw(*queries)
19
+ self.queries += queries
20
+ end
21
+
22
+ def respond_to_missing?(name, include_private = false)
23
+ Query.public_method_defined?(name, false) || super
24
+ end
25
+
26
+ def method_missing(name, *args)
27
+ if Query.respond_to?(name, false)
28
+ self.queries += Query.send(name, *args)
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,34 @@
1
+ require 'delegate'
2
+
3
+ module ROM
4
+ module Solr
5
+ module QueryTemplates
6
+
7
+ #
8
+ # Simple template object with a `#render` method.
9
+ #
10
+ # N.B. Values must be properly escaped and/or
11
+ # formatted for Solr prior to rendering!
12
+ #
13
+ class Template < SimpleDelegator
14
+ # @param data [Hash]
15
+ def render(data)
16
+ __getobj__ % data
17
+ end
18
+ end
19
+
20
+ STANDARD = Template.new('%{field}:%{value}')
21
+
22
+ ABSENT = Template.new('-%{field}:[* TO *]')
23
+ BEFORE_DATE = Template.new('%{field}:[* TO %{value}]')
24
+ BEFORE_DAYS = Template.new('%{field}:[* TO NOW-%{value}DAYS]')
25
+ DISJUNCTION = Template.new('{!lucene q.op=OR df=%{field}}%{value}')
26
+ JOIN = Template.new('{!join from=%{from} to=%{to}}%{field}:%{value}')
27
+ NEGATION = Template.new('-%{field}:%{value}')
28
+ PRESENT = Template.new('%{field}:[* TO *]')
29
+ REGEXP = Template.new('%{field}:/%{value}/')
30
+ TERM = Template.new('{!term f=%{field}}%{value}')
31
+
32
+ end
33
+ end
34
+ end
@@ -1,37 +1,37 @@
1
+ require 'forwardable'
2
+
1
3
  module ROM
2
4
  module Solr
3
5
  class Relation < ROM::HTTP::Relation
6
+ extend Forwardable
7
+
8
+ RESPONSE_WRITERS = %w[ csv geojson javabin json php phps python ruby smile velocity xlsx xml xslt ]
9
+
10
+ def_delegators :dataset, :response, :params
4
11
 
5
12
  adapter :solr
6
13
  auto_struct false
7
14
  auto_map false
8
15
 
9
- forward :with_response_key
10
-
16
+ # Need this?
11
17
  option :output_schema, default: ->{ NOOP_OUTPUT_SCHEMA }
12
18
 
13
- def wt(format)
14
- add_params(wt: Types::Coercible::String[format])
19
+ def wt(writer)
20
+ add_params(wt: Types::Coercible::String.enum(*RESPONSE_WRITERS)[writer])
15
21
  end
16
- alias_method :format, :wt
17
-
18
- def log_params_list(log_params)
19
- lplist = log_params.nil? ? nil : Array.wrap(log_params).join(',')
20
22
 
21
- add_params(logParamsList: lplist)
22
- end
23
- alias_method :log_params, :log_params_list
23
+ def log_params_list(*log_params)
24
+ new_log_params = Types::Array.of(Types::String)[log_params]
24
25
 
25
- def count
26
- to_enum.count
26
+ add_params(logParamsList: new_log_params.join(','))
27
27
  end
28
28
 
29
- def response
30
- dataset.response
29
+ def omit_header(omit = true)
30
+ add_params(omitHeader: Types::Bool[omit])
31
31
  end
32
32
 
33
- def params
34
- dataset.params.dup
33
+ def count
34
+ to_enum.count
35
35
  end
36
36
 
37
37
  end
@@ -1,16 +1,21 @@
1
- require 'rom/solr/select_cursor'
1
+ require 'rom/solr/documents_paginator'
2
+ require 'rom/solr/query_builder'
2
3
 
3
4
  module ROM
4
5
  module Solr
5
6
  class DocumentsRelation < Relation
6
7
 
8
+ def_delegators :dataset, :num_found, :num_found_exact?
9
+
7
10
  schema(:documents) { }
8
11
 
9
12
  # @override
10
13
  def each(&block)
14
+ return super unless cursor?
15
+
11
16
  return to_enum unless block_given?
12
17
 
13
- SelectCursor.new(dataset).each(&block)
18
+ DocumentsPaginator.new(dataset).each(&block)
14
19
  end
15
20
 
16
21
  # @override FIXME: Get from Solr schema unique key
@@ -28,36 +33,74 @@ module ROM
28
33
 
29
34
  # @override
30
35
  def count
31
- num_found_exact ? num_found : super
36
+ num_found_exact? ? num_found : super
37
+ end
38
+
39
+ # Set a cursor on the relation for pagination
40
+ def cursor
41
+ relation = add_params(cursorMark: '*')
42
+
43
+ # Sort must include a sort on unique key (id).
44
+ if /\bid\b/ =~ params[:sort]
45
+ relation
46
+ else
47
+ relation.sort(params[:sort], 'id ASC')
48
+ end
32
49
  end
33
50
 
34
- def num_found
35
- response.dig(:response, :numFound)
51
+ def cursor?
52
+ params.key?(:cursorMark)
36
53
  end
37
54
 
38
- def num_found_exact
39
- response.dig(:response, :numFoundExact)
55
+ def query(&block)
56
+ queries = QueryBuilder.new.instance_eval(&block).to_a
57
+
58
+ q(*queries)
59
+ end
60
+
61
+ def filter(&block)
62
+ queries = QueryBuilder.new.instance_eval(&block).to_a
63
+
64
+ fq(*queries)
40
65
  end
41
66
 
67
+ #
68
+ # Commands
69
+ #
70
+
42
71
  def insert(docs)
72
+ # FIXME: use input schema
43
73
  data = Array.wrap(docs).map do |doc|
44
74
  doc.transform_keys!(&:to_sym)
45
75
  doc[:id] ||= UUID[]
76
+ doc
46
77
  end
47
78
 
48
- update(data)
79
+ with_options(
80
+ base_path: 'update/json/docs',
81
+ content_type: 'application/json',
82
+ request_data: JSON.dump(data)
83
+ )
49
84
  end
50
85
 
51
86
  def update(docs)
87
+ # FIXME: use input schema
88
+ data = Array.wrap(docs)
89
+ .map { |doc| doc.transform_keys(&:to_sym) }
90
+ .select { |doc| doc.key?(:id) }
91
+
52
92
  with_options(
53
93
  base_path: 'update/json/docs',
54
94
  content_type: 'application/json',
55
- request_data: JSON.dump(docs)
95
+ request_data: JSON.dump(data)
56
96
  )
57
97
  end
58
98
 
59
99
  def delete(docs)
60
- ids = Array.wrap(docs).map { |doc| doc.transform_keys(&:to_sym) }.map(&:id)
100
+ # FIXME: use input schema
101
+ ids = Array.wrap(docs)
102
+ .map { |doc| doc.transform_keys(&:to_sym) }
103
+ .map { |doc| doc.fetch(:id) }
61
104
 
62
105
  with_options(
63
106
  base_path: 'update',
@@ -66,93 +109,105 @@ module ROM
66
109
  )
67
110
  end
68
111
 
112
+ def delete_by_query(query)
113
+ with_options(
114
+ base_path: 'update',
115
+ content_type: 'application/json',
116
+ request_data: JSON.dump(delete: {query: query})
117
+ )
118
+ end
119
+
69
120
  #
70
- # Params
121
+ # Common Query Parameters
71
122
  #
72
- def q(query)
73
- add_params(q: Types::String[query])
123
+
124
+ def q(*queries)
125
+ old_queries = Array.wrap(params[:q])
126
+ new_queries = Array.wrap(queries).flatten
127
+
128
+ add_params q: (old_queries + new_queries).join(' ')
74
129
  end
75
- alias_method :query, :q
76
130
 
77
- def fq(*filter)
78
- add_params(fq: filter)
131
+ def fq(*queries)
132
+ old_queries = Array.wrap(params[:fq])
133
+ new_queries = Array.wrap(queries).flatten
134
+
135
+ add_params fq: old_queries + new_queries
79
136
  end
80
- alias_method :filter, :fq
81
137
 
82
138
  def fl(*fields)
83
- add_params(fl: fields.join(','))
139
+ new_fields = Types::Array.of(Types::Coercible::String)[fields]
140
+
141
+ add_params fl: new_fields.join(',')
84
142
  end
143
+
85
144
  alias_method :fields, :fl
86
145
 
87
146
  def cache(enabled = true)
88
- add_params(cache: Types::Bool[enabled])
147
+ add_params cache: Types::Bool[enabled]
89
148
  end
90
149
 
91
150
  def segment_terminate_early(enabled = true)
92
- add_params(segmentTerminateEarly: Types::Bool[enabled])
151
+ add_params segmentTerminateEarly: Types::Bool[enabled]
93
152
  end
94
153
 
95
154
  def time_allowed(millis)
96
- add_params(timeAllowed: Types::Coercible::Integer[millis])
155
+ add_params timeAllowed: Types::Coercible::Integer[millis]
97
156
  end
98
157
 
99
158
  def explain_other(query)
100
- add_params(explainOther: Types::String[query])
101
- end
102
-
103
- def omit_header(omit = true)
104
- add_params(omitHeader: Types::Bool[omit])
159
+ add_params explainOther: Types::String[query]
105
160
  end
106
161
 
107
162
  def start(offset)
108
- add_params(start: Types::Coercible::Integer[offset])
163
+ add_params start: Types::Coercible::Integer[offset]
109
164
  end
110
165
 
111
166
  def sort(*criteria)
112
- add_params(sort: criteria.join(','))
167
+ new_sort = Types::Array.of(Types::String)[criteria]
168
+
169
+ add_params sort: new_sort.join(',')
113
170
  end
114
171
 
115
172
  def rows(num)
116
- add_params(rows: Types::Coercible::Integer[num])
173
+ add_params rows: Types::Coercible::Integer[num]
117
174
  end
175
+
118
176
  alias_method :limit, :rows
119
177
 
120
178
  def def_type(value)
121
- add_params(defType: Types::Coercible::String[value])
179
+ add_params defType: Types::Coercible::String[value]
122
180
  end
123
181
 
124
182
  def debug(setting)
125
- type = Types::Coercible::String
126
- .enum('query', 'timing', 'results', 'all', 'true')
127
-
128
- add_params(debug: type[setting])
183
+ add_params debug: Types::Coercible::String.enum('query', 'timing', 'results', 'all', 'true')[setting]
129
184
  end
130
185
 
131
186
  def echo_params(setting)
132
- type = Types::Coercible::String.enum('explicit', 'all', 'none')
133
- add_params(echoParams: type[setting])
187
+ add_params echoParams: Types::Coercible::String.enum('explicit', 'all', 'none')[setting]
134
188
  end
135
189
 
136
190
  def min_exact_count(num)
137
- add_params(minExactCount: Types::Coercible::Integer[num])
191
+ add_params minExactCount: Types::Coercible::Integer[num]
138
192
  end
139
193
 
194
+ #
195
+ # Commit parameters
196
+ #
140
197
  def commit(value = true)
141
- add_params(commit: Types::Bool[value])
198
+ add_params commit: Types::Bool[value]
142
199
  end
143
200
 
144
201
  def commit_within(millis)
145
- return self if millis.nil?
146
-
147
- add_params(commitWithin: Types::Coercible::Integer[millis])
202
+ add_params commitWithin: Types::Coercible::Integer.optional[millis]
148
203
  end
149
204
 
150
205
  def overwrite(value = true)
151
- add_params(overwrite: Types::Bool[value])
206
+ add_params overwrite: Types::Bool[value]
152
207
  end
153
208
 
154
209
  def expunge_deletes(value = true)
155
- add_params(expungeDeletes: Types::Bool[value])
210
+ add_params expungeDeletes: Types::Bool[value]
156
211
  end
157
212
 
158
213
  end
@@ -15,84 +15,62 @@ module ROM
15
15
  end
16
16
 
17
17
  def info
18
- with_response_key(:schema)
18
+ self
19
19
  end
20
20
 
21
- def copy_fields
22
- with_options(
23
- path: :copyfields,
24
- response_key: :copyFields
25
- )
21
+ def copy_fields(source_fields: nil, dest_fields: nil)
22
+ source_fl = Array.wrap(source_fields).join(',') unless source_fields.nil?
23
+ dest_fl = Array.wrap(dest_fields).join(',') unless dest_fields.nil?
24
+
25
+ with_path(:copyfields)
26
+ .add_params('source.fl'=>source_fl, 'dest.fl'=>dest_fl)
26
27
  end
27
28
 
28
- def dynamic_fields
29
- with_options(
30
- path: :dynamicfields,
31
- response_key: :dynamicFields
32
- )
29
+ def dynamic_fields(defaults: true)
30
+ with_path(:dynamicfields)
31
+ .show_defaults(defaults)
33
32
  end
34
33
 
35
- def dynamic_field(name)
36
- with_options(
37
- path: "dynamicfields/#{name}",
38
- response_key: :dynamicField
39
- )
34
+ def dynamic_field(name, defaults: true)
35
+ with_path("dynamicfields/#{name}")
36
+ .show_defaults(defaults)
40
37
  end
41
38
 
42
39
  def similarity
43
- with_options(
44
- path: :similarity,
45
- response_key: :similarity
46
- )
40
+ with_path :similarity
47
41
  end
48
42
 
49
43
  def unique_key
50
- with_options(
51
- path: :uniquekey,
52
- response_key: :uniqueKey
53
- )
44
+ with_path :uniquekey
54
45
  end
55
46
 
56
47
  def version
57
- with_options(
58
- path: :version,
59
- response_key: :version
60
- )
48
+ with_path :version
61
49
  end
62
50
 
63
51
  def schema_name
64
- with_options(
65
- path: :name,
66
- response_key: :name
67
- )
52
+ with_path :name
68
53
  end
69
54
 
70
- def fields
71
- with_options(
72
- path: :fields,
73
- response_key: :fields
74
- )
55
+ def fields(dynamic: true, defaults: true)
56
+ with_path(:fields)
57
+ .include_dynamic(dynamic)
58
+ .show_defaults(defaults)
75
59
  end
76
60
 
77
- def field(name)
78
- with_options(
79
- path: "fields/#{name}",
80
- response_key: :field
81
- )
61
+ def field(name, defaults: true)
62
+ with_path("fields/#{name}")
63
+ .show_defaults(defaults)
82
64
  end
83
65
 
84
- def field_types
85
- with_options(
86
- path: :fieldtypes,
87
- response_key: :fieldTypes
88
- )
66
+ def field_types(defaults: true)
67
+ with_path(:fieldtypes)
68
+ .show_defaults(defaults)
89
69
  end
90
70
 
91
- def field_type(name)
92
- with_options(
93
- path: "fieldtypes/#{name}",
94
- response_key: :fieldType
95
- )
71
+ def field_type(name, defaults: true)
72
+ with_path("fieldtypes/#{name}")
73
+ .show_defaults(defaults)
96
74
  end
97
75
 
98
76
  end
@@ -20,7 +20,7 @@ module ROM
20
20
 
21
21
  def request
22
22
  request_class.new(uri.request_uri, headers).tap do |req|
23
- if dataset.has_request_data?
23
+ if dataset.request_data?
24
24
  req.body = dataset.request_data
25
25
  req.content_type = dataset.content_type
26
26
  end
@@ -33,14 +33,14 @@ module ROM
33
33
 
34
34
  def uri
35
35
  @uri ||= URI(dataset.uri).tap do |u|
36
- if dataset.has_params?
36
+ if dataset.params?
37
37
  u.query ||= URI.encode_www_form(dataset.params)
38
38
  end
39
39
  end
40
40
  end
41
41
 
42
42
  def request_class
43
- if dataset.has_request_data?
43
+ if dataset.request_data?
44
44
  Net::HTTP::Post
45
45
  else
46
46
  Net::HTTP::Get
@@ -4,6 +4,14 @@ module ROM
4
4
 
5
5
  configure do |config|
6
6
  config.default_base_path = 'schema'
7
+
8
+ config.default_response_handler = Proc.new do |*args|
9
+ ResponseHandler.call(*args).values.flatten
10
+ end
11
+
12
+ config.default_request_handler = Proc.new do |dataset|
13
+ RequestHandler.call(dataset.add_params(omitHeader: true))
14
+ end
7
15
  end
8
16
 
9
17
  end
@@ -1,43 +1,27 @@
1
+ require 'forwardable'
2
+
1
3
  module ROM
2
4
  module Solr
3
5
  class SchemaInfoRepo < Repository[:schema_info]
6
+ extend Forwardable
4
7
 
5
8
  auto_struct false
6
9
 
7
- %i[ schema_name similarity unique_key version ].each do |name|
8
- define_method name, ->{ schema_info.send(name).one! }
9
- end
10
-
11
- def info
12
- schema_info.info.one!
13
- end
14
-
15
- def fields(dynamic: true, defaults: true)
16
- schema_info.fields.show_defaults(defaults).include_dynamic(dynamic)
17
- end
18
-
19
- def field(name, defaults: true)
20
- schema_info.field(name).show_defaults(defaults).one
21
- end
22
-
23
- def field_types(defaults: true)
24
- schema_info.field_types.show_defaults(defaults)
25
- end
26
-
27
- def field_type(name, defaults: true)
28
- schema_info.field_type(name).show_defaults(defaults).one
29
- end
30
-
31
- def dynamic_fields
32
- schema_info.dynamic_fields
33
- end
34
-
35
- def dynamic_field(name, defaults: true)
36
- schema_info.dynamic_field(name).show_defaults(defaults).one
37
- end
38
-
39
- def copy_fields
40
- schema_info.copy_fields
10
+ VALUES = %i[ schema_name
11
+ similarity
12
+ unique_key
13
+ version
14
+ info
15
+ field
16
+ field_type
17
+ dynamic_field
18
+ copy_field
19
+ ]
20
+
21
+ def_delegators :schema_info, :fields, :field_types, :dynamic_fields, :copy_fields
22
+
23
+ VALUES.each do |name|
24
+ define_method name, proc { |*args| schema_info.send(name, *args).one! }
41
25
  end
42
26
 
43
27
  end
@@ -0,0 +1,36 @@
1
+ require 'date'
2
+ require 'time'
3
+
4
+ module ROM
5
+ module Solr
6
+ module Utils
7
+
8
+ def phrase(value)
9
+ if value.match?(/ /)
10
+ '"%s"' % value.gsub(/"/, '\"')
11
+ else
12
+ value
13
+ end
14
+ end
15
+
16
+ # Escape a Solr query value
17
+ #
18
+ # @param value [String] raw value
19
+ # @return [String] escaped value
20
+ def escape(value)
21
+ value
22
+ .gsub(ESCAPE_CHARS, '\\1')
23
+ .gsub(DOUBLE_AMPERSAND, '\&\&')
24
+ .gsub(DOUBLE_PIPE, '\|\|')
25
+ end
26
+
27
+ # Formats a value as a Solr date.
28
+ def solr_date(value)
29
+ DateTime.parse(value.to_s).to_time.utc.iso8601
30
+ end
31
+
32
+ extend self
33
+
34
+ end
35
+ end
36
+ end
@@ -1,6 +1,7 @@
1
- require "solrbee/version"
1
+ require 'solrbee/version'
2
2
 
3
- require "rom/solr"
3
+ require 'dry-types'
4
+ require 'rom/solr'
4
5
 
5
6
  module Solrbee
6
7
 
@@ -25,4 +26,8 @@ module Solrbee
25
26
  end
26
27
  end
27
28
 
29
+ module Types
30
+ include Dry.Types()
31
+ end
32
+
28
33
  end
@@ -1,3 +1,3 @@
1
1
  module Solrbee
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
24
24
 
25
25
  spec.add_dependency "rom"
26
26
  spec.add_dependency "rom-http"
27
+ spec.add_dependency "dry-types"
27
28
 
28
29
  spec.add_development_dependency "bundler"
29
30
  spec.add_development_dependency "rake"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solrbee
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Chandek-Stark
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-12-31 00:00:00.000000000 Z
11
+ date: 2021-01-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rom
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dry-types
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: bundler
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -121,7 +135,11 @@ files:
121
135
  - lib/rom/solr/dataset.rb
122
136
  - lib/rom/solr/document_repo.rb
123
137
  - lib/rom/solr/documents_dataset.rb
138
+ - lib/rom/solr/documents_paginator.rb
124
139
  - lib/rom/solr/gateway.rb
140
+ - lib/rom/solr/query.rb
141
+ - lib/rom/solr/query_builder.rb
142
+ - lib/rom/solr/query_templates.rb
125
143
  - lib/rom/solr/relation.rb
126
144
  - lib/rom/solr/relations/documents_relation.rb
127
145
  - lib/rom/solr/relations/schema_info_relation.rb
@@ -131,7 +149,7 @@ files:
131
149
  - lib/rom/solr/schema.rb
132
150
  - lib/rom/solr/schema_info_dataset.rb
133
151
  - lib/rom/solr/schema_info_repo.rb
134
- - lib/rom/solr/select_cursor.rb
152
+ - lib/rom/solr/utils.rb
135
153
  - lib/solrbee.rb
136
154
  - lib/solrbee/version.rb
137
155
  - solrbee.gemspec
@@ -1,56 +0,0 @@
1
- require 'delegate'
2
-
3
- module ROM
4
- module Solr
5
- #
6
- # Wraps a DocumentsDataset to provide pagination with a cursor.
7
- #
8
- class SelectCursor < SimpleDelegator
9
-
10
- def initialize(dataset)
11
- params = { cursorMark: '*' }
12
-
13
- # Sort must include a sort on unique key (id).
14
- sort = dataset.params[:sort]
15
- unless /\bid\b/ =~ sort
16
- params[:sort] = Array.wrap(sort).append('id ASC').join(',')
17
- end
18
-
19
- super dataset.add_params(params)
20
- end
21
-
22
- def each(&block)
23
- return to_enum unless block_given?
24
-
25
- while true
26
- __getobj__.each(&block)
27
-
28
- break if last_page?
29
-
30
- move_cursor
31
- end
32
- end
33
-
34
- def cursor_mark
35
- params[:cursorMark]
36
- end
37
-
38
- def next_cursor_mark
39
- response[:nextCursorMark]
40
- end
41
-
42
- def last_page?
43
- cursor_mark == next_cursor_mark
44
- end
45
-
46
- def move_cursor
47
- __setobj__(next_page)
48
- end
49
-
50
- def next_page
51
- __getobj__.add_params(cursorMark: next_cursor_mark)
52
- end
53
-
54
- end
55
- end
56
- end