solrbee 0.1.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|