solrbee 0.4.0 → 0.5.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: 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