solrbee 0.1.2 → 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/Gemfile +3 -0
- data/Makefile +1 -4
- data/README.md +1 -11
- data/config/api.yml +22 -0
- data/lib/rom/solr.rb +56 -0
- data/lib/rom/solr/array.rb +10 -0
- data/lib/rom/solr/commands.rb +17 -0
- data/lib/rom/solr/commands/create_documents.rb +15 -0
- data/lib/rom/solr/commands/delete_documents.rb +15 -0
- data/lib/rom/solr/commands/delete_documents_by_query.rb +15 -0
- data/lib/rom/solr/commands/update_documents.rb +15 -0
- data/lib/rom/solr/dataset.rb +68 -0
- data/lib/rom/solr/document_repo.rb +45 -0
- data/lib/rom/solr/documents_dataset.rb +62 -0
- data/lib/rom/solr/documents_paginator.rb +21 -0
- data/lib/rom/solr/gateway.rb +14 -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 +39 -0
- data/lib/rom/solr/relations/documents_relation.rb +215 -0
- data/lib/rom/solr/relations/schema_info_relation.rb +78 -0
- data/lib/rom/solr/repository.rb +9 -0
- data/lib/rom/solr/request_handler.rb +52 -0
- data/lib/rom/solr/response_handler.rb +12 -0
- data/lib/rom/solr/schema.rb +7 -0
- data/lib/rom/solr/schema_info_dataset.rb +19 -0
- data/lib/rom/solr/schema_info_repo.rb +29 -0
- data/lib/rom/solr/utils.rb +36 -0
- data/lib/solrbee.rb +22 -28
- data/lib/solrbee/version.rb +1 -1
- data/solrbee.gemspec +3 -1
- data/test.sh +14 -0
- metadata +58 -9
- data/lib/solrbee/api_methods.rb +0 -94
- data/lib/solrbee/client.rb +0 -31
- data/lib/solrbee/cursor.rb +0 -37
- data/lib/solrbee/query.rb +0 -35
- data/lib/solrbee/request.rb +0 -45
- data/lib/solrbee/response.rb +0 -25
@@ -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
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
module Solr
|
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
|
11
|
+
|
12
|
+
adapter :solr
|
13
|
+
auto_struct false
|
14
|
+
auto_map false
|
15
|
+
|
16
|
+
# Need this?
|
17
|
+
option :output_schema, default: ->{ NOOP_OUTPUT_SCHEMA }
|
18
|
+
|
19
|
+
def wt(writer)
|
20
|
+
add_params(wt: Types::Coercible::String.enum(*RESPONSE_WRITERS)[writer])
|
21
|
+
end
|
22
|
+
|
23
|
+
def log_params_list(*log_params)
|
24
|
+
new_log_params = Types::Array.of(Types::String)[log_params]
|
25
|
+
|
26
|
+
add_params(logParamsList: new_log_params.join(','))
|
27
|
+
end
|
28
|
+
|
29
|
+
def omit_header(omit = true)
|
30
|
+
add_params(omitHeader: Types::Bool[omit])
|
31
|
+
end
|
32
|
+
|
33
|
+
def count
|
34
|
+
to_enum.count
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
require 'rom/solr/documents_paginator'
|
2
|
+
require 'rom/solr/query_builder'
|
3
|
+
|
4
|
+
module ROM
|
5
|
+
module Solr
|
6
|
+
class DocumentsRelation < Relation
|
7
|
+
|
8
|
+
def_delegators :dataset, :num_found, :num_found_exact?
|
9
|
+
|
10
|
+
schema(:documents) { }
|
11
|
+
|
12
|
+
# @override
|
13
|
+
def each(&block)
|
14
|
+
return super unless cursor?
|
15
|
+
|
16
|
+
return to_enum unless block_given?
|
17
|
+
|
18
|
+
DocumentsPaginator.new(dataset).each(&block)
|
19
|
+
end
|
20
|
+
|
21
|
+
# @override FIXME: Get from Solr schema unique key
|
22
|
+
def primary_key
|
23
|
+
:id
|
24
|
+
end
|
25
|
+
|
26
|
+
def by_unique_key(id)
|
27
|
+
q('%s:%s' % [ primary_key, id ])
|
28
|
+
end
|
29
|
+
|
30
|
+
def all
|
31
|
+
q('*:*')
|
32
|
+
end
|
33
|
+
|
34
|
+
# @override
|
35
|
+
def count
|
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
|
49
|
+
end
|
50
|
+
|
51
|
+
def cursor?
|
52
|
+
params.key?(:cursorMark)
|
53
|
+
end
|
54
|
+
|
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)
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Commands
|
69
|
+
#
|
70
|
+
|
71
|
+
def insert(docs)
|
72
|
+
# FIXME: use input schema
|
73
|
+
data = Array.wrap(docs).map do |doc|
|
74
|
+
doc.transform_keys!(&:to_sym)
|
75
|
+
doc[:id] ||= UUID[]
|
76
|
+
doc
|
77
|
+
end
|
78
|
+
|
79
|
+
with_options(
|
80
|
+
base_path: 'update/json/docs',
|
81
|
+
content_type: 'application/json',
|
82
|
+
request_data: JSON.dump(data)
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
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
|
+
|
92
|
+
with_options(
|
93
|
+
base_path: 'update/json/docs',
|
94
|
+
content_type: 'application/json',
|
95
|
+
request_data: JSON.dump(data)
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
def delete(docs)
|
100
|
+
# FIXME: use input schema
|
101
|
+
ids = Array.wrap(docs)
|
102
|
+
.map { |doc| doc.transform_keys(&:to_sym) }
|
103
|
+
.map { |doc| doc.fetch(:id) }
|
104
|
+
|
105
|
+
with_options(
|
106
|
+
base_path: 'update',
|
107
|
+
content_type: 'application/json',
|
108
|
+
request_data: JSON.dump(delete: ids)
|
109
|
+
)
|
110
|
+
end
|
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
|
+
|
120
|
+
#
|
121
|
+
# Common Query Parameters
|
122
|
+
#
|
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(' ')
|
129
|
+
end
|
130
|
+
|
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
|
136
|
+
end
|
137
|
+
|
138
|
+
def fl(*fields)
|
139
|
+
new_fields = Types::Array.of(Types::Coercible::String)[fields]
|
140
|
+
|
141
|
+
add_params fl: new_fields.join(',')
|
142
|
+
end
|
143
|
+
|
144
|
+
alias_method :fields, :fl
|
145
|
+
|
146
|
+
def cache(enabled = true)
|
147
|
+
add_params cache: Types::Bool[enabled]
|
148
|
+
end
|
149
|
+
|
150
|
+
def segment_terminate_early(enabled = true)
|
151
|
+
add_params segmentTerminateEarly: Types::Bool[enabled]
|
152
|
+
end
|
153
|
+
|
154
|
+
def time_allowed(millis)
|
155
|
+
add_params timeAllowed: Types::Coercible::Integer[millis]
|
156
|
+
end
|
157
|
+
|
158
|
+
def explain_other(query)
|
159
|
+
add_params explainOther: Types::String[query]
|
160
|
+
end
|
161
|
+
|
162
|
+
def start(offset)
|
163
|
+
add_params start: Types::Coercible::Integer[offset]
|
164
|
+
end
|
165
|
+
|
166
|
+
def sort(*criteria)
|
167
|
+
new_sort = Types::Array.of(Types::String)[criteria]
|
168
|
+
|
169
|
+
add_params sort: new_sort.join(',')
|
170
|
+
end
|
171
|
+
|
172
|
+
def rows(num)
|
173
|
+
add_params rows: Types::Coercible::Integer[num]
|
174
|
+
end
|
175
|
+
|
176
|
+
alias_method :limit, :rows
|
177
|
+
|
178
|
+
def def_type(value)
|
179
|
+
add_params defType: Types::Coercible::String[value]
|
180
|
+
end
|
181
|
+
|
182
|
+
def debug(setting)
|
183
|
+
add_params debug: Types::Coercible::String.enum('query', 'timing', 'results', 'all', 'true')[setting]
|
184
|
+
end
|
185
|
+
|
186
|
+
def echo_params(setting)
|
187
|
+
add_params echoParams: Types::Coercible::String.enum('explicit', 'all', 'none')[setting]
|
188
|
+
end
|
189
|
+
|
190
|
+
def min_exact_count(num)
|
191
|
+
add_params minExactCount: Types::Coercible::Integer[num]
|
192
|
+
end
|
193
|
+
|
194
|
+
#
|
195
|
+
# Commit parameters
|
196
|
+
#
|
197
|
+
def commit(value = true)
|
198
|
+
add_params commit: Types::Bool[value]
|
199
|
+
end
|
200
|
+
|
201
|
+
def commit_within(millis)
|
202
|
+
add_params commitWithin: Types::Coercible::Integer.optional[millis]
|
203
|
+
end
|
204
|
+
|
205
|
+
def overwrite(value = true)
|
206
|
+
add_params overwrite: Types::Bool[value]
|
207
|
+
end
|
208
|
+
|
209
|
+
def expunge_deletes(value = true)
|
210
|
+
add_params expungeDeletes: Types::Bool[value]
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module ROM
|
2
|
+
module Solr
|
3
|
+
class SchemaInfoRelation < Relation
|
4
|
+
|
5
|
+
schema(:schema_info) do
|
6
|
+
# no-op
|
7
|
+
end
|
8
|
+
|
9
|
+
def show_defaults(show = true)
|
10
|
+
add_params(showDefaults: Types::Bool[show])
|
11
|
+
end
|
12
|
+
|
13
|
+
def include_dynamic(enabled = true)
|
14
|
+
add_params(includeDynamic: Types::Bool[enabled])
|
15
|
+
end
|
16
|
+
|
17
|
+
def info
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
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)
|
27
|
+
end
|
28
|
+
|
29
|
+
def dynamic_fields(defaults: true)
|
30
|
+
with_path(:dynamicfields)
|
31
|
+
.show_defaults(defaults)
|
32
|
+
end
|
33
|
+
|
34
|
+
def dynamic_field(name, defaults: true)
|
35
|
+
with_path("dynamicfields/#{name}")
|
36
|
+
.show_defaults(defaults)
|
37
|
+
end
|
38
|
+
|
39
|
+
def similarity
|
40
|
+
with_path :similarity
|
41
|
+
end
|
42
|
+
|
43
|
+
def unique_key
|
44
|
+
with_path :uniquekey
|
45
|
+
end
|
46
|
+
|
47
|
+
def version
|
48
|
+
with_path :version
|
49
|
+
end
|
50
|
+
|
51
|
+
def schema_name
|
52
|
+
with_path :name
|
53
|
+
end
|
54
|
+
|
55
|
+
def fields(dynamic: true, defaults: true)
|
56
|
+
with_path(:fields)
|
57
|
+
.include_dynamic(dynamic)
|
58
|
+
.show_defaults(defaults)
|
59
|
+
end
|
60
|
+
|
61
|
+
def field(name, defaults: true)
|
62
|
+
with_path("fields/#{name}")
|
63
|
+
.show_defaults(defaults)
|
64
|
+
end
|
65
|
+
|
66
|
+
def field_types(defaults: true)
|
67
|
+
with_path(:fieldtypes)
|
68
|
+
.show_defaults(defaults)
|
69
|
+
end
|
70
|
+
|
71
|
+
def field_type(name, defaults: true)
|
72
|
+
with_path("fieldtypes/#{name}")
|
73
|
+
.show_defaults(defaults)
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module ROM
|
2
|
+
module Solr
|
3
|
+
class RequestHandler
|
4
|
+
|
5
|
+
def self.call(dataset)
|
6
|
+
new(dataset).execute
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :dataset
|
10
|
+
|
11
|
+
def initialize(dataset)
|
12
|
+
@dataset = dataset
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute
|
16
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme.eql?('https')) do |http|
|
17
|
+
http.request(request)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def request
|
22
|
+
request_class.new(uri.request_uri, headers).tap do |req|
|
23
|
+
if dataset.request_data?
|
24
|
+
req.body = dataset.request_data
|
25
|
+
req.content_type = dataset.content_type
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def headers
|
31
|
+
dataset.headers.transform_keys(&:to_s)
|
32
|
+
end
|
33
|
+
|
34
|
+
def uri
|
35
|
+
@uri ||= URI(dataset.uri).tap do |u|
|
36
|
+
if dataset.params?
|
37
|
+
u.query ||= URI.encode_www_form(dataset.params)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def request_class
|
43
|
+
if dataset.request_data?
|
44
|
+
Net::HTTP::Post
|
45
|
+
else
|
46
|
+
Net::HTTP::Get
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|