solrbee 0.1.2 → 0.5.0

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