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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -0
  3. data/Makefile +1 -4
  4. data/README.md +1 -11
  5. data/config/api.yml +22 -0
  6. data/lib/rom/solr.rb +56 -0
  7. data/lib/rom/solr/array.rb +10 -0
  8. data/lib/rom/solr/commands.rb +17 -0
  9. data/lib/rom/solr/commands/create_documents.rb +15 -0
  10. data/lib/rom/solr/commands/delete_documents.rb +15 -0
  11. data/lib/rom/solr/commands/delete_documents_by_query.rb +15 -0
  12. data/lib/rom/solr/commands/update_documents.rb +15 -0
  13. data/lib/rom/solr/dataset.rb +68 -0
  14. data/lib/rom/solr/document_repo.rb +45 -0
  15. data/lib/rom/solr/documents_dataset.rb +62 -0
  16. data/lib/rom/solr/documents_paginator.rb +21 -0
  17. data/lib/rom/solr/gateway.rb +14 -0
  18. data/lib/rom/solr/query.rb +135 -0
  19. data/lib/rom/solr/query_builder.rb +36 -0
  20. data/lib/rom/solr/query_templates.rb +34 -0
  21. data/lib/rom/solr/relation.rb +39 -0
  22. data/lib/rom/solr/relations/documents_relation.rb +215 -0
  23. data/lib/rom/solr/relations/schema_info_relation.rb +78 -0
  24. data/lib/rom/solr/repository.rb +9 -0
  25. data/lib/rom/solr/request_handler.rb +52 -0
  26. data/lib/rom/solr/response_handler.rb +12 -0
  27. data/lib/rom/solr/schema.rb +7 -0
  28. data/lib/rom/solr/schema_info_dataset.rb +19 -0
  29. data/lib/rom/solr/schema_info_repo.rb +29 -0
  30. data/lib/rom/solr/utils.rb +36 -0
  31. data/lib/solrbee.rb +22 -28
  32. data/lib/solrbee/version.rb +1 -1
  33. data/solrbee.gemspec +3 -1
  34. data/test.sh +14 -0
  35. metadata +58 -9
  36. data/lib/solrbee/api_methods.rb +0 -94
  37. data/lib/solrbee/client.rb +0 -31
  38. data/lib/solrbee/cursor.rb +0 -37
  39. data/lib/solrbee/query.rb +0 -35
  40. data/lib/solrbee/request.rb +0 -45
  41. data/lib/solrbee/response.rb +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 356cd5b312edac759a4090191b33a48282a49b379bf44aecdf224ec47ce73fd0
4
- data.tar.gz: f0d399ed8c9eccfe86fffd3492ba248fe7b736407aee26a2465afa54962d89bd
3
+ metadata.gz: 32b15ba57c92024c22c90c365c673098bbc5031fb14e76ae62ea75868e0d1ec3
4
+ data.tar.gz: 7aa16b385b8ddff45506966c1fbbb9b3e6252cfff2053b4f29377f09b9c4b9a1
5
5
  SHA512:
6
- metadata.gz: 7002601a792323c6533157152f92cab212e23fffd006f714f89b6f3610a20e4dfa35fab27451bb7a93b9e1f0e920e8b7081720e261143bb5568264a8d3817449
7
- data.tar.gz: d29a32a7d00f985bf38bf1f7c36a044f78064739999b318ef4d3070eb1e411a04124c7c3e9140a6afce92ef28df018c6caa1554b82a5a44d307292d41d7af95f
6
+ metadata.gz: c20241f1f4ed8e31fee8aa383bcf1a643f3dd94f8a6435d2ba170efe2858b0215472c65434575d62dfe9b40fb3899050c9bf39eb1457443ad29ff744d926f87f
7
+ data.tar.gz: '023180eea1874e7d4c17caa61bf39b5b8d5163b5681b4d0356ee8b41c65de4d9a4b32ddc233645d38b780fc6a4ae8345bd3d808d241e2d108021c38e67f9ccc5'
data/Gemfile CHANGED
@@ -1,3 +1,6 @@
1
1
  source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
+
5
+ gem 'rom', '5.2.4'
6
+ gem 'rom-http', git: 'https://github.com/rom-rb/rom-http.git'
data/Makefile CHANGED
@@ -2,7 +2,4 @@ SHELL = /bin/bash
2
2
 
3
3
  .PHONY : test
4
4
  test:
5
- docker run --rm -d -p 8983:8983 --name solrbee-test solr:8 solr-precreate solrbee
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
 
@@ -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
@@ -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,10 @@
1
+ Array.class_eval do
2
+
3
+ # Inspired by Rails's Array.wrap
4
+ def self.wrap(obj)
5
+ return [] if obj.nil?
6
+ return obj.to_ary if obj.respond_to?(:to_ary)
7
+ Array.new(1, obj)
8
+ end
9
+
10
+ end
@@ -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,15 @@
1
+ module ROM
2
+ module Solr
3
+ module Commands
4
+ class CreateDocuments < Create
5
+
6
+ relation :documents
7
+
8
+ def execute(docs)
9
+ relation.insert(docs).response
10
+ end
11
+
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module ROM
2
+ module Solr
3
+ module Commands
4
+ class DeleteDocuments < Delete
5
+
6
+ relation :documents
7
+
8
+ def execute(docs)
9
+ relation.delete(docs).response
10
+ end
11
+
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module ROM
2
+ module Solr
3
+ module Commands
4
+ class DeleteDocumentsByQuery < Delete
5
+
6
+ relation :documents
7
+
8
+ def execute(query)
9
+ relation.delete_by_query(query).response
10
+ end
11
+
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module ROM
2
+ module Solr
3
+ module Commands
4
+ class UpdateDocuments < Update
5
+
6
+ relation :documents
7
+
8
+ def execute(docs)
9
+ relation.update(docs).response
10
+ end
11
+
12
+ end
13
+ end
14
+ end
15
+ 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,14 @@
1
+ module ROM
2
+ module Solr
3
+ class Gateway < ROM::HTTP::Gateway
4
+
5
+ adapter :solr
6
+
7
+ # @override
8
+ def dataset(name)
9
+ ROM::Solr.dataset_class(name).new(config)
10
+ end
11
+
12
+ end
13
+ end
14
+ 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