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
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/Gemfile
CHANGED
data/Makefile
CHANGED
@@ -2,7 +2,4 @@ SHELL = /bin/bash
|
|
2
2
|
|
3
3
|
.PHONY : test
|
4
4
|
test:
|
5
|
-
|
6
|
-
while ! curl -fs http://localhost:8983/solr/solrbee/admin/ping 2>/dev/null ; do sleep 1 ; done
|
7
|
-
bundle exec rake
|
8
|
-
docker stop solrbee-test
|
5
|
+
./test.sh
|
data/README.md
CHANGED
@@ -25,17 +25,7 @@ Or install it yourself as:
|
|
25
25
|
|
26
26
|
## Usage
|
27
27
|
|
28
|
-
|
29
|
-
$ bundle console
|
30
|
-
irb(main):001:0> client = Solrbee::Client.new('solrbee')
|
31
|
-
=> #<Solrbee::Client:0x00007fd3410d7c50 @collection="solrbee", @uri=#<URI::HTTP http://localhost:8983/solr/solrbee>>
|
32
|
-
irb(main):002:0> client.unique_key
|
33
|
-
=> "id"
|
34
|
-
irb(main):003:0> client.schema_version
|
35
|
-
=> 1.6
|
36
|
-
irb(main):004:0> client.schema_name
|
37
|
-
=> "default-config"
|
38
|
-
```
|
28
|
+
TODO
|
39
29
|
|
40
30
|
## Development
|
41
31
|
|
data/config/api.yml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
field_types:
|
2
|
+
path: fieldtypes
|
3
|
+
key: fieldTypes
|
4
|
+
field_type:
|
5
|
+
desc: GET /schema/fieldtype/[name]
|
6
|
+
path: fieldtypes/%{name}
|
7
|
+
args:
|
8
|
+
- name
|
9
|
+
key: fieldType
|
10
|
+
dynamic_fields:
|
11
|
+
desc: GET /schema/dynamicfields
|
12
|
+
path: dynamicfields
|
13
|
+
key: dynamicFields
|
14
|
+
dynamic_field:
|
15
|
+
desc: GET /schema/dynamicfields/[name]
|
16
|
+
args:
|
17
|
+
- name
|
18
|
+
path: dynamicfields/%{name}
|
19
|
+
key: dynamicField
|
20
|
+
copy_fields:
|
21
|
+
path: copyfields
|
22
|
+
key: copyFields
|
data/lib/rom/solr.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'rom-http'
|
3
|
+
|
4
|
+
module ROM
|
5
|
+
module Solr
|
6
|
+
|
7
|
+
ESCAPE_CHARS = Regexp.new('[%s]' % Regexp.escape('+-!(){}[]^"~*?:/'))
|
8
|
+
DOUBLE_AMPERSAND = Regexp.new('&&')
|
9
|
+
DOUBLE_PIPE = Regexp.new('\|\|')
|
10
|
+
|
11
|
+
def self.dataset_class(name)
|
12
|
+
prefix = name.to_s.split(/[_\/]/).map(&:capitalize).join('')
|
13
|
+
const_name = "#{prefix}Dataset"
|
14
|
+
const_defined?(const_name, false) ? const_get(const_name, false) : Dataset
|
15
|
+
end
|
16
|
+
|
17
|
+
module Types
|
18
|
+
include ROM::HTTP::Types
|
19
|
+
end
|
20
|
+
|
21
|
+
UUID = Types::String.default { SecureRandom.uuid }
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Utilities
|
27
|
+
require 'rom/solr/array'
|
28
|
+
require 'rom/solr/utils'
|
29
|
+
|
30
|
+
# Handlers
|
31
|
+
require 'rom/solr/request_handler'
|
32
|
+
require 'rom/solr/response_handler'
|
33
|
+
|
34
|
+
# Datasets
|
35
|
+
require 'rom/solr/dataset'
|
36
|
+
require 'rom/solr/documents_dataset'
|
37
|
+
require 'rom/solr/schema_info_dataset'
|
38
|
+
|
39
|
+
# Gateway
|
40
|
+
require 'rom/solr/gateway'
|
41
|
+
|
42
|
+
# Schemas
|
43
|
+
require 'rom/solr/schema'
|
44
|
+
|
45
|
+
# Relations
|
46
|
+
require 'rom/solr/relation'
|
47
|
+
|
48
|
+
# Repositories
|
49
|
+
require 'rom/solr/repository'
|
50
|
+
require 'rom/solr/schema_info_repo'
|
51
|
+
require 'rom/solr/document_repo'
|
52
|
+
|
53
|
+
# Commands
|
54
|
+
require 'rom/solr/commands'
|
55
|
+
|
56
|
+
ROM.register_adapter(:solr, ROM::Solr)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ROM
|
2
|
+
module Solr
|
3
|
+
module Commands
|
4
|
+
class Create < ROM::Commands::Create
|
5
|
+
adapter :solr
|
6
|
+
end
|
7
|
+
|
8
|
+
class Update < ROM::Commands::Update
|
9
|
+
adapter :solr
|
10
|
+
end
|
11
|
+
|
12
|
+
class Delete < ROM::Commands::Delete
|
13
|
+
adapter :solr
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module ROM
|
2
|
+
module Solr
|
3
|
+
class Dataset < ROM::HTTP::Dataset
|
4
|
+
|
5
|
+
setting :default_base_path, reader: true
|
6
|
+
|
7
|
+
configure do |config|
|
8
|
+
config.default_response_handler = ResponseHandler
|
9
|
+
config.default_request_handler = RequestHandler
|
10
|
+
end
|
11
|
+
|
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 }
|
17
|
+
|
18
|
+
# @override Query parameters are valid with POST, too.
|
19
|
+
def uri
|
20
|
+
uri_s = [options[:uri], path].compact.reject(&:empty?).join('/')
|
21
|
+
|
22
|
+
URI(uri_s).tap do |u|
|
23
|
+
u.query = param_encoder.call(params) if params?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def with_request_data(data)
|
28
|
+
with_options(request_data: data)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Copies and makes private superclass #response method
|
32
|
+
alias_method :__response__, :response
|
33
|
+
private :__response__
|
34
|
+
|
35
|
+
# @override Cache response by default
|
36
|
+
def response
|
37
|
+
cache.fetch_or_store(:response) { __response__ }
|
38
|
+
end
|
39
|
+
|
40
|
+
def response_header(key)
|
41
|
+
response.dig(:responseHeader, key)
|
42
|
+
end
|
43
|
+
|
44
|
+
def request_data?
|
45
|
+
!request_data.nil?
|
46
|
+
end
|
47
|
+
|
48
|
+
def params?
|
49
|
+
params.any?
|
50
|
+
end
|
51
|
+
|
52
|
+
def status
|
53
|
+
response_header(:status)
|
54
|
+
end
|
55
|
+
|
56
|
+
def qtime
|
57
|
+
response_header(:QTime)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def cache
|
63
|
+
@cache ||= Concurrent::Map.new
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rom/solr/query_builder'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
module Solr
|
5
|
+
class DocumentRepo < Repository[:documents]
|
6
|
+
|
7
|
+
auto_struct false
|
8
|
+
|
9
|
+
def find(id)
|
10
|
+
documents.by_unique_key(id).one!
|
11
|
+
end
|
12
|
+
|
13
|
+
def all
|
14
|
+
documents.all
|
15
|
+
end
|
16
|
+
|
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)
|
27
|
+
end
|
28
|
+
|
29
|
+
def update(docs, **opts)
|
30
|
+
docs_command(:update_documents, docs, **opts)
|
31
|
+
end
|
32
|
+
|
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)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module ROM
|
2
|
+
module Solr
|
3
|
+
class DocumentsDataset < Dataset
|
4
|
+
|
5
|
+
configure do |config|
|
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?
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
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
|