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
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
|