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 +4 -4
- data/lib/rom/solr.rb +19 -14
- data/lib/rom/solr/commands/create_documents.rb +1 -1
- data/lib/rom/solr/commands/delete_documents.rb +1 -1
- data/lib/rom/solr/commands/update_documents.rb +1 -1
- data/lib/rom/solr/dataset.rb +21 -25
- data/lib/rom/solr/document_repo.rb +22 -10
- data/lib/rom/solr/documents_dataset.rb +52 -2
- data/lib/rom/solr/documents_paginator.rb +21 -0
- data/lib/rom/solr/query.rb +135 -0
- data/lib/rom/solr/query_builder.rb +36 -0
- data/lib/rom/solr/query_templates.rb +34 -0
- data/lib/rom/solr/relation.rb +17 -17
- data/lib/rom/solr/relations/documents_relation.rb +98 -43
- data/lib/rom/solr/relations/schema_info_relation.rb +30 -52
- data/lib/rom/solr/request_handler.rb +3 -3
- data/lib/rom/solr/schema_info_dataset.rb +8 -0
- data/lib/rom/solr/schema_info_repo.rb +18 -34
- data/lib/rom/solr/utils.rb +36 -0
- data/lib/solrbee.rb +7 -2
- data/lib/solrbee/version.rb +1 -1
- data/solrbee.gemspec +1 -0
- metadata +21 -3
- data/lib/rom/solr/select_cursor.rb +0 -56
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 32b15ba57c92024c22c90c365c673098bbc5031fb14e76ae62ea75868e0d1ec3
|
4
|
+
data.tar.gz: 7aa16b385b8ddff45506966c1fbbb9b3e6252cfff2053b4f29377f09b9c4b9a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c20241f1f4ed8e31fee8aa383bcf1a643f3dd94f8a6435d2ba170efe2858b0215472c65434575d62dfe9b40fb3899050c9bf39eb1457443ad29ff744d926f87f
|
7
|
+
data.tar.gz: '023180eea1874e7d4c17caa61bf39b5b8d5163b5681b4d0356ee8b41c65de4d9a4b32ddc233645d38b780fc6a4ae8345bd3d808d241e2d108021c38e67f9ccc5'
|
data/lib/rom/solr.rb
CHANGED
@@ -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
|
-
|
27
|
+
require 'rom/solr/array'
|
28
|
+
require 'rom/solr/utils'
|
24
29
|
|
25
30
|
# Handlers
|
26
|
-
|
27
|
-
|
31
|
+
require 'rom/solr/request_handler'
|
32
|
+
require 'rom/solr/response_handler'
|
28
33
|
|
29
34
|
# Datasets
|
30
|
-
|
31
|
-
|
32
|
-
|
35
|
+
require 'rom/solr/dataset'
|
36
|
+
require 'rom/solr/documents_dataset'
|
37
|
+
require 'rom/solr/schema_info_dataset'
|
33
38
|
|
34
39
|
# Gateway
|
35
|
-
|
40
|
+
require 'rom/solr/gateway'
|
36
41
|
|
37
42
|
# Schemas
|
38
|
-
|
43
|
+
require 'rom/solr/schema'
|
39
44
|
|
40
45
|
# Relations
|
41
|
-
|
46
|
+
require 'rom/solr/relation'
|
42
47
|
|
43
48
|
# Repositories
|
44
|
-
|
45
|
-
|
46
|
-
|
49
|
+
require 'rom/solr/repository'
|
50
|
+
require 'rom/solr/schema_info_repo'
|
51
|
+
require 'rom/solr/document_repo'
|
47
52
|
|
48
53
|
# Commands
|
49
|
-
|
54
|
+
require 'rom/solr/commands'
|
50
55
|
|
51
56
|
ROM.register_adapter(:solr, ROM::Solr)
|
data/lib/rom/solr/dataset.rb
CHANGED
@@ -2,44 +2,32 @@ module ROM
|
|
2
2
|
module Solr
|
3
3
|
class Dataset < ROM::HTTP::Dataset
|
4
4
|
|
5
|
-
setting :
|
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 :
|
15
|
-
option :
|
16
|
-
|
17
|
-
|
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
|
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
|
53
|
-
|
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
|
48
|
+
def params?
|
57
49
|
params.any?
|
58
50
|
end
|
59
51
|
|
60
|
-
|
52
|
+
def status
|
53
|
+
response_header(:status)
|
54
|
+
end
|
61
55
|
|
62
|
-
def
|
63
|
-
|
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
|
-
|
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
|
24
|
-
|
29
|
+
def update(docs, **opts)
|
30
|
+
docs_command(:update_documents, docs, **opts)
|
25
31
|
end
|
26
32
|
|
27
|
-
|
28
|
-
|
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.
|
7
|
-
|
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
|
data/lib/rom/solr/relation.rb
CHANGED
@@ -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
|
-
|
10
|
-
|
16
|
+
# Need this?
|
11
17
|
option :output_schema, default: ->{ NOOP_OUTPUT_SCHEMA }
|
12
18
|
|
13
|
-
def wt(
|
14
|
-
add_params(wt: Types::Coercible::String[
|
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
|
-
|
22
|
-
|
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
|
-
|
26
|
-
to_enum.count
|
26
|
+
add_params(logParamsList: new_log_params.join(','))
|
27
27
|
end
|
28
28
|
|
29
|
-
def
|
30
|
-
|
29
|
+
def omit_header(omit = true)
|
30
|
+
add_params(omitHeader: Types::Bool[omit])
|
31
31
|
end
|
32
32
|
|
33
|
-
def
|
34
|
-
|
33
|
+
def count
|
34
|
+
to_enum.count
|
35
35
|
end
|
36
36
|
|
37
37
|
end
|
@@ -1,16 +1,21 @@
|
|
1
|
-
require 'rom/solr/
|
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
|
-
|
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
|
35
|
-
|
51
|
+
def cursor?
|
52
|
+
params.key?(:cursorMark)
|
36
53
|
end
|
37
54
|
|
38
|
-
def
|
39
|
-
|
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
|
-
|
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(
|
95
|
+
request_data: JSON.dump(data)
|
56
96
|
)
|
57
97
|
end
|
58
98
|
|
59
99
|
def delete(docs)
|
60
|
-
|
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
|
-
#
|
121
|
+
# Common Query Parameters
|
71
122
|
#
|
72
|
-
|
73
|
-
|
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(*
|
78
|
-
|
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
|
-
|
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
|
147
|
+
add_params cache: Types::Bool[enabled]
|
89
148
|
end
|
90
149
|
|
91
150
|
def segment_terminate_early(enabled = true)
|
92
|
-
add_params
|
151
|
+
add_params segmentTerminateEarly: Types::Bool[enabled]
|
93
152
|
end
|
94
153
|
|
95
154
|
def time_allowed(millis)
|
96
|
-
add_params
|
155
|
+
add_params timeAllowed: Types::Coercible::Integer[millis]
|
97
156
|
end
|
98
157
|
|
99
158
|
def explain_other(query)
|
100
|
-
add_params
|
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
|
163
|
+
add_params start: Types::Coercible::Integer[offset]
|
109
164
|
end
|
110
165
|
|
111
166
|
def sort(*criteria)
|
112
|
-
|
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
|
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
|
179
|
+
add_params defType: Types::Coercible::String[value]
|
122
180
|
end
|
123
181
|
|
124
182
|
def debug(setting)
|
125
|
-
|
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
|
-
|
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
|
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
|
198
|
+
add_params commit: Types::Bool[value]
|
142
199
|
end
|
143
200
|
|
144
201
|
def commit_within(millis)
|
145
|
-
|
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
|
206
|
+
add_params overwrite: Types::Bool[value]
|
152
207
|
end
|
153
208
|
|
154
209
|
def expunge_deletes(value = true)
|
155
|
-
add_params
|
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
|
-
|
18
|
+
self
|
19
19
|
end
|
20
20
|
|
21
|
-
def copy_fields
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
30
|
-
|
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
|
-
|
37
|
-
|
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
|
-
|
44
|
-
path: :similarity,
|
45
|
-
response_key: :similarity
|
46
|
-
)
|
40
|
+
with_path :similarity
|
47
41
|
end
|
48
42
|
|
49
43
|
def unique_key
|
50
|
-
|
51
|
-
path: :uniquekey,
|
52
|
-
response_key: :uniqueKey
|
53
|
-
)
|
44
|
+
with_path :uniquekey
|
54
45
|
end
|
55
46
|
|
56
47
|
def version
|
57
|
-
|
58
|
-
path: :version,
|
59
|
-
response_key: :version
|
60
|
-
)
|
48
|
+
with_path :version
|
61
49
|
end
|
62
50
|
|
63
51
|
def schema_name
|
64
|
-
|
65
|
-
path: :name,
|
66
|
-
response_key: :name
|
67
|
-
)
|
52
|
+
with_path :name
|
68
53
|
end
|
69
54
|
|
70
|
-
def fields
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
79
|
-
|
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
|
-
|
86
|
-
|
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
|
-
|
93
|
-
|
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.
|
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.
|
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.
|
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
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
data/lib/solrbee.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
require
|
1
|
+
require 'solrbee/version'
|
2
2
|
|
3
|
-
require
|
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
|
data/lib/solrbee/version.rb
CHANGED
data/solrbee.gemspec
CHANGED
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
|
+
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:
|
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/
|
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
|