simple_solr_client 0.1.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 +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +349 -0
- data/Rakefile +11 -0
- data/lib/simple_solr.rb +42 -0
- data/lib/simple_solr/client.rb +139 -0
- data/lib/simple_solr/client/core_admin.rb +0 -0
- data/lib/simple_solr/core.rb +50 -0
- data/lib/simple_solr/core/admin.rb +47 -0
- data/lib/simple_solr/core/core_data.rb +51 -0
- data/lib/simple_solr/core/index.rb +25 -0
- data/lib/simple_solr/core/search.rb +21 -0
- data/lib/simple_solr/response/document.rb +45 -0
- data/lib/simple_solr/response/generic_response.rb +19 -0
- data/lib/simple_solr/response/query_response.rb +54 -0
- data/lib/simple_solr/schema.rb +261 -0
- data/lib/simple_solr/schema/analysis.rb +58 -0
- data/lib/simple_solr/schema/copyfield.rb +42 -0
- data/lib/simple_solr/schema/dynamic_field.rb +23 -0
- data/lib/simple_solr/schema/field.rb +35 -0
- data/lib/simple_solr/schema/field_or_type.rb +112 -0
- data/lib/simple_solr/schema/field_type.rb +62 -0
- data/lib/simple_solr/schema/matcher.rb +16 -0
- data/lib/simple_solr/version.rb +3 -0
- data/simple_solr_client.gemspec +39 -0
- data/solr_sample_core/conf/_schema_analysis_stopwords_english.json +38 -0
- data/solr_sample_core/conf/_schema_analysis_synonyms_english.json +11 -0
- data/solr_sample_core/conf/admin-extra.html +24 -0
- data/solr_sample_core/conf/admin-extra.menu-bottom.html +25 -0
- data/solr_sample_core/conf/admin-extra.menu-top.html +25 -0
- data/solr_sample_core/conf/clustering/carrot2/kmeans-attributes.xml +19 -0
- data/solr_sample_core/conf/clustering/carrot2/lingo-attributes.xml +24 -0
- data/solr_sample_core/conf/clustering/carrot2/stc-attributes.xml +19 -0
- data/solr_sample_core/conf/currency.xml +67 -0
- data/solr_sample_core/conf/elevate.xml +38 -0
- data/solr_sample_core/conf/lang/contractions_ca.txt +8 -0
- data/solr_sample_core/conf/lang/contractions_fr.txt +15 -0
- data/solr_sample_core/conf/lang/contractions_ga.txt +5 -0
- data/solr_sample_core/conf/lang/contractions_it.txt +23 -0
- data/solr_sample_core/conf/lang/hyphenations_ga.txt +5 -0
- data/solr_sample_core/conf/lang/stemdict_nl.txt +6 -0
- data/solr_sample_core/conf/lang/stoptags_ja.txt +420 -0
- data/solr_sample_core/conf/lang/stopwords_ar.txt +125 -0
- data/solr_sample_core/conf/lang/stopwords_bg.txt +193 -0
- data/solr_sample_core/conf/lang/stopwords_ca.txt +220 -0
- data/solr_sample_core/conf/lang/stopwords_ckb.txt +136 -0
- data/solr_sample_core/conf/lang/stopwords_cz.txt +172 -0
- data/solr_sample_core/conf/lang/stopwords_da.txt +110 -0
- data/solr_sample_core/conf/lang/stopwords_de.txt +294 -0
- data/solr_sample_core/conf/lang/stopwords_el.txt +78 -0
- data/solr_sample_core/conf/lang/stopwords_en.txt +54 -0
- data/solr_sample_core/conf/lang/stopwords_es.txt +356 -0
- data/solr_sample_core/conf/lang/stopwords_eu.txt +99 -0
- data/solr_sample_core/conf/lang/stopwords_fa.txt +313 -0
- data/solr_sample_core/conf/lang/stopwords_fi.txt +97 -0
- data/solr_sample_core/conf/lang/stopwords_fr.txt +186 -0
- data/solr_sample_core/conf/lang/stopwords_ga.txt +110 -0
- data/solr_sample_core/conf/lang/stopwords_gl.txt +161 -0
- data/solr_sample_core/conf/lang/stopwords_hi.txt +235 -0
- data/solr_sample_core/conf/lang/stopwords_hu.txt +211 -0
- data/solr_sample_core/conf/lang/stopwords_hy.txt +46 -0
- data/solr_sample_core/conf/lang/stopwords_id.txt +359 -0
- data/solr_sample_core/conf/lang/stopwords_it.txt +303 -0
- data/solr_sample_core/conf/lang/stopwords_ja.txt +127 -0
- data/solr_sample_core/conf/lang/stopwords_lv.txt +172 -0
- data/solr_sample_core/conf/lang/stopwords_nl.txt +119 -0
- data/solr_sample_core/conf/lang/stopwords_no.txt +194 -0
- data/solr_sample_core/conf/lang/stopwords_pt.txt +253 -0
- data/solr_sample_core/conf/lang/stopwords_ro.txt +233 -0
- data/solr_sample_core/conf/lang/stopwords_ru.txt +243 -0
- data/solr_sample_core/conf/lang/stopwords_sv.txt +133 -0
- data/solr_sample_core/conf/lang/stopwords_th.txt +119 -0
- data/solr_sample_core/conf/lang/stopwords_tr.txt +212 -0
- data/solr_sample_core/conf/lang/userdict_ja.txt +29 -0
- data/solr_sample_core/conf/mapping-FoldToASCII.txt +3813 -0
- data/solr_sample_core/conf/mapping-ISOLatin1Accent.txt +246 -0
- data/solr_sample_core/conf/protwords.txt +21 -0
- data/solr_sample_core/conf/schema.xml +62 -0
- data/solr_sample_core/conf/scripts.conf +24 -0
- data/solr_sample_core/conf/solrconfig.xml +1702 -0
- data/solr_sample_core/conf/spellings.txt +2 -0
- data/solr_sample_core/conf/stopwords.txt +14 -0
- data/solr_sample_core/conf/syn.txt +0 -0
- data/solr_sample_core/conf/synonyms.txt +29 -0
- data/solr_sample_core/conf/token_fixing_charfilter.txt +110 -0
- data/solr_sample_core/conf/update-script.js +53 -0
- data/solr_sample_core/conf/velocity/README.txt +101 -0
- data/solr_sample_core/conf/velocity/VM_global_library.vm +175 -0
- data/solr_sample_core/conf/velocity/browse.vm +33 -0
- data/solr_sample_core/conf/velocity/cluster.vm +19 -0
- data/solr_sample_core/conf/velocity/cluster_results.vm +31 -0
- data/solr_sample_core/conf/velocity/debug.vm +28 -0
- data/solr_sample_core/conf/velocity/did_you_mean.vm +9 -0
- data/solr_sample_core/conf/velocity/error.vm +11 -0
- data/solr_sample_core/conf/velocity/facet_fields.vm +23 -0
- data/solr_sample_core/conf/velocity/facet_pivot.vm +12 -0
- data/solr_sample_core/conf/velocity/facet_queries.vm +12 -0
- data/solr_sample_core/conf/velocity/facet_ranges.vm +23 -0
- data/solr_sample_core/conf/velocity/facets.vm +10 -0
- data/solr_sample_core/conf/velocity/footer.vm +43 -0
- data/solr_sample_core/conf/velocity/head.vm +35 -0
- data/solr_sample_core/conf/velocity/header.vm +7 -0
- data/solr_sample_core/conf/velocity/hit.vm +25 -0
- data/solr_sample_core/conf/velocity/hit_grouped.vm +43 -0
- data/solr_sample_core/conf/velocity/hit_plain.vm +25 -0
- data/solr_sample_core/conf/velocity/join_doc.vm +20 -0
- data/solr_sample_core/conf/velocity/jquery.autocomplete.css +48 -0
- data/solr_sample_core/conf/velocity/jquery.autocomplete.js +763 -0
- data/solr_sample_core/conf/velocity/layout.vm +24 -0
- data/solr_sample_core/conf/velocity/main.css +230 -0
- data/solr_sample_core/conf/velocity/mime_type_lists.vm +68 -0
- data/solr_sample_core/conf/velocity/pagination_bottom.vm +22 -0
- data/solr_sample_core/conf/velocity/pagination_top.vm +29 -0
- data/solr_sample_core/conf/velocity/product_doc.vm +32 -0
- data/solr_sample_core/conf/velocity/query.vm +42 -0
- data/solr_sample_core/conf/velocity/query_form.vm +64 -0
- data/solr_sample_core/conf/velocity/query_group.vm +43 -0
- data/solr_sample_core/conf/velocity/query_spatial.vm +75 -0
- data/solr_sample_core/conf/velocity/results_list.vm +22 -0
- data/solr_sample_core/conf/velocity/richtext_doc.vm +153 -0
- data/solr_sample_core/conf/velocity/suggest.vm +8 -0
- data/solr_sample_core/conf/velocity/tabs.vm +50 -0
- data/solr_sample_core/conf/xslt/example.xsl +132 -0
- data/solr_sample_core/conf/xslt/example_atom.xsl +67 -0
- data/solr_sample_core/conf/xslt/example_rss.xsl +66 -0
- data/solr_sample_core/conf/xslt/luke.xsl +337 -0
- data/solr_sample_core/conf/xslt/updateXml.xsl +70 -0
- data/spec/client_basics_spec.rb +26 -0
- data/spec/connect_spec.rb +25 -0
- data/spec/core_basics.rb +21 -0
- data/spec/index_spec.rb +31 -0
- data/spec/load_spec.rb +7 -0
- data/spec/minitest_helper.rb +36 -0
- data/spec/schema_spec.rb +113 -0
- metadata +284 -0
|
File without changes
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Pre-define the inheritance so Ruby doesn't complain
|
|
2
|
+
# on import.
|
|
3
|
+
require 'simple_solr/client'
|
|
4
|
+
require 'simple_solr/schema'
|
|
5
|
+
module SimpleSolrClient
|
|
6
|
+
class Core < Client
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
require 'simple_solr/core/admin'
|
|
12
|
+
require 'simple_solr/core/core_data'
|
|
13
|
+
require 'simple_solr/core/index'
|
|
14
|
+
require 'simple_solr/core/search'
|
|
15
|
+
|
|
16
|
+
class SimpleSolrClient::Core
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
include SimpleSolrClient::Core::Admin
|
|
20
|
+
include SimpleSolrClient::Core::CoreData
|
|
21
|
+
include SimpleSolrClient::Core::Index
|
|
22
|
+
include SimpleSolrClient::Core::Search
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
attr_reader :core
|
|
26
|
+
alias_method :name, :core
|
|
27
|
+
|
|
28
|
+
def initialize(url, core)
|
|
29
|
+
super(url)
|
|
30
|
+
@core = core
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Override #url so we're now talking to the core
|
|
35
|
+
def url(*args)
|
|
36
|
+
[@base_url, @core, *args].join('/').chomp('/')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Send JSON to this core's update/json handler
|
|
40
|
+
def update(object_to_post, response_type = nil)
|
|
41
|
+
post_json('update/json', object_to_post, response_type)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def schema
|
|
45
|
+
@schema ||= SimpleSolrClient::Schema.new(self)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module SimpleSolrClient::Core::Admin
|
|
2
|
+
def ping
|
|
3
|
+
get('admin/ping')
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
# Is the server up (and responding to a ping?)
|
|
7
|
+
# @return [Boolean]
|
|
8
|
+
def up?
|
|
9
|
+
begin
|
|
10
|
+
ping.status == 'OK'
|
|
11
|
+
rescue
|
|
12
|
+
false
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Send a commit command
|
|
17
|
+
# @return self
|
|
18
|
+
def commit
|
|
19
|
+
update({'commit' => {}})
|
|
20
|
+
self
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Send an optimize command
|
|
24
|
+
# @return self
|
|
25
|
+
def optimize
|
|
26
|
+
update({"optimize" => {}})
|
|
27
|
+
self
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Reload the core (for when you've changed the schema, solrconfig, synonyms, etc.)
|
|
31
|
+
# Make sure to mark the schema as dirty!
|
|
32
|
+
# @return self
|
|
33
|
+
def reload
|
|
34
|
+
get('admin/cores', {:force_top_level_url => true, :core => core, :action => 'RELOAD'})
|
|
35
|
+
@schema = nil
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Unload the current core and delete all its files
|
|
40
|
+
# @return The Solr response
|
|
41
|
+
def unload
|
|
42
|
+
get('admin/cores', {:force_top_level_url => true, :core => core, :action => 'UNLOAD', :deleteInstanceDir => true})
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
end
|
|
47
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module SimpleSolrClient::Core::CoreData
|
|
2
|
+
attr_reader :raw_solr_hash
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# Get the core data for this core
|
|
6
|
+
# This is weird in that while the data is about a specific
|
|
7
|
+
# core, we need to call it at the client_url level; hence
|
|
8
|
+
# all the screwing around with force_top_level_url
|
|
9
|
+
#
|
|
10
|
+
# It would make sense to cache this until, say, a commit or a
|
|
11
|
+
# reload, but the added complexity isn't yet worth it.
|
|
12
|
+
def core_data_hash
|
|
13
|
+
cdata = get('admin/cores', {:force_top_level_url => true})
|
|
14
|
+
cdata['status'][core]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def index
|
|
18
|
+
core_data_hash['index']
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def default?
|
|
22
|
+
core_data_hash['isDefaultCore']
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def last_modified
|
|
26
|
+
Time.parse index['lastModified']
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def number_of_documents
|
|
30
|
+
index['numDocs']
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def data_dir
|
|
34
|
+
core_data_hash['dataDir']
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def instance_dir
|
|
38
|
+
core_data_hash['instanceDir']
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def config_file
|
|
42
|
+
File.join(instance_dir, 'conf', core_data_hash['config'])
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def schema_file
|
|
46
|
+
File.join(instance_dir, 'conf', core_data_hash['schema'])
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module SimpleSolrClient::Core::Index
|
|
2
|
+
# Add the given hash or array of hashes
|
|
3
|
+
# @return self
|
|
4
|
+
def add_docs(*hash_or_hashes)
|
|
5
|
+
update(hash_or_hashes.flatten)
|
|
6
|
+
self
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# A raw delete. Your query needs to be legal (e.g., escaped) already
|
|
10
|
+
# @param [String] q The query to identify items to delete
|
|
11
|
+
# @return self
|
|
12
|
+
def delete(q)
|
|
13
|
+
update({:delete => {:query => q}})
|
|
14
|
+
self
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Delete all document in the index and immdiately commit
|
|
18
|
+
# @return self
|
|
19
|
+
def clear
|
|
20
|
+
delete('*:*').commit
|
|
21
|
+
self
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'simple_solr/response/query_response'
|
|
2
|
+
|
|
3
|
+
module SimpleSolrClient::Core::Search
|
|
4
|
+
|
|
5
|
+
def fv_search(field, value)
|
|
6
|
+
v = value
|
|
7
|
+
v = SimpleSolrClient.lucene_escape Array(value).join(' ') unless v == '*'
|
|
8
|
+
kv = "#{field}:(#{v})"
|
|
9
|
+
get('select', {:q => kv}, SimpleSolrClient::Response::QueryResponse)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def all
|
|
13
|
+
fv_search('*', '*')
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def id(i)
|
|
17
|
+
fv_search('id', i).first
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
class SimpleSolrClient::Response::Document
|
|
2
|
+
extend Forwardable
|
|
3
|
+
include Comparable
|
|
4
|
+
|
|
5
|
+
attr_accessor :rank
|
|
6
|
+
|
|
7
|
+
def <=>(other)
|
|
8
|
+
other = other.score if other.respond_to? :score,
|
|
9
|
+
self.score <=> other
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# @!method [](key)
|
|
13
|
+
# @param [String] key The name of the stored field
|
|
14
|
+
# @return [String, Array<String>] the value(s) of the requested field
|
|
15
|
+
def_delegators :@solr_doc_hash, :[], :keys, :values, :to_s
|
|
16
|
+
|
|
17
|
+
# !@attribute solr_doc_hash
|
|
18
|
+
# @return [Hash] the original, un-munged solr data for this document passed into the initializer
|
|
19
|
+
attr_accessor :solr_doc_hash, :rank
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Create a single document from a Hash representation of the solr return value
|
|
23
|
+
# @param solrdochash [Hash] The ruby-hash representation of a Solr document
|
|
24
|
+
# as returned by a solr query
|
|
25
|
+
def initialize(solrdochash)
|
|
26
|
+
@solr_doc_hash = solrdochash
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# The value of the 'id' field of this document
|
|
31
|
+
def id
|
|
32
|
+
@solr_doc_hash['id']
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# The score of this document on thsi query
|
|
36
|
+
def score
|
|
37
|
+
@solr_doc_hash['score']
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def to_h
|
|
42
|
+
@solr_doc_hash.merge({'_rank' => @rank})
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'forwardable'
|
|
2
|
+
|
|
3
|
+
module SimpleSolrClient
|
|
4
|
+
module Response
|
|
5
|
+
class GenericResponse
|
|
6
|
+
extend Forwardable
|
|
7
|
+
def_delegators :@solr_response, :[]
|
|
8
|
+
|
|
9
|
+
def initialize(solr_response_hash)
|
|
10
|
+
@solr_response = solr_response_hash
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def status
|
|
14
|
+
@solr_response['status']
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require 'simple_solr/response/generic_response'
|
|
2
|
+
require 'simple_solr/response/document'
|
|
3
|
+
|
|
4
|
+
class SimpleSolrClient::Response::QueryResponse < SimpleSolrClient::Response::GenericResponse
|
|
5
|
+
extend Forwardable
|
|
6
|
+
include Enumerable
|
|
7
|
+
|
|
8
|
+
attr_reader :num_found, :docs, :first_index, :docs, :params, :page
|
|
9
|
+
|
|
10
|
+
def_delegators :@docs, :each, :count, :size
|
|
11
|
+
def_delegators :@indexed_docs, :[]
|
|
12
|
+
|
|
13
|
+
def initialize(solr_response)
|
|
14
|
+
super
|
|
15
|
+
resp = @solr_response['response']
|
|
16
|
+
@num_found = resp['numFound']
|
|
17
|
+
@first_index = resp['start'] + 1
|
|
18
|
+
|
|
19
|
+
@docs = []
|
|
20
|
+
@indexed_docs = {}
|
|
21
|
+
resp['docs'].each_with_index do |d, i|
|
|
22
|
+
doc_rank = i + @first_index
|
|
23
|
+
doc = SimpleSolrClient::Response::Document.new(d)
|
|
24
|
+
doc.rank = doc_rank
|
|
25
|
+
@docs << doc
|
|
26
|
+
@indexed_docs[doc.id] = doc
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def last_index
|
|
31
|
+
@first_index + @num_found
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def rank(id)
|
|
35
|
+
@indexed_docs[id.to_s].rank
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def score(id)
|
|
39
|
+
@indexed_docs[id.to_s].score
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @return [Boolean] True if there are no documents
|
|
43
|
+
def empty?
|
|
44
|
+
@docs.empty?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def each_with_rank
|
|
49
|
+
return self.enum_for(:each_with_rank) unless block_given?
|
|
50
|
+
@docs.each { |x| yield x, x.rank }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
end
|
|
54
|
+
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
require 'nokogiri'
|
|
2
|
+
|
|
3
|
+
require 'simple_solr/schema/matcher'
|
|
4
|
+
require 'simple_solr/schema/copyfield'
|
|
5
|
+
require 'simple_solr/schema/field'
|
|
6
|
+
require 'simple_solr/schema/dynamic_field'
|
|
7
|
+
require 'simple_solr/schema/field_type'
|
|
8
|
+
|
|
9
|
+
class SimpleSolrClient::Schema
|
|
10
|
+
# A simplistic representation of a schema
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
attr_reader :xmldoc
|
|
14
|
+
|
|
15
|
+
def initialize(core)
|
|
16
|
+
@core = core
|
|
17
|
+
@fields = {}
|
|
18
|
+
@dynamic_fields = {}
|
|
19
|
+
@copy_fields = Hash.new { |h, k| h[k] = [] }
|
|
20
|
+
@field_types = {}
|
|
21
|
+
self.load
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def fields
|
|
26
|
+
@fields.values.map { |x| x.resolve_type(self) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def field(n)
|
|
30
|
+
@fields[n].resolve_type(self)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def dynamic_fields
|
|
34
|
+
@dynamic_fields.values.map { |x| x.resolve_type(self) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def dynamic_field(n)
|
|
38
|
+
@dynamic_fields[n].resolve_type(self)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def copy_fields_for(n)
|
|
42
|
+
@copy_fields[n]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def copy_fields
|
|
46
|
+
@copy_fields.values.flatten
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def add_field(f)
|
|
50
|
+
@fields[f.name] = f
|
|
51
|
+
field(f.name)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def drop_field(str)
|
|
55
|
+
@fields.delete(str)
|
|
56
|
+
self
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def field_types
|
|
61
|
+
@field_types.values
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def field_type(k)
|
|
65
|
+
@field_types[k]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# When we add dynamic fields, we need to keep them sorted by
|
|
70
|
+
# length of the key, since that's how they match
|
|
71
|
+
def add_dynamic_field(f)
|
|
72
|
+
raise "Dynamic field should be dynamic and have a '*' in it somewhere; '#{f.name}' does not" unless f.name =~ /\*/
|
|
73
|
+
@dynamic_fields[f.name] = f
|
|
74
|
+
|
|
75
|
+
@dynamic_fields = @dynamic_fields.sort { |a, b| b[0].size <=> a[0].size }.to_h
|
|
76
|
+
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def drop_dynamic_field(str)
|
|
80
|
+
@dynamic_fields.delete(str)
|
|
81
|
+
self
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def add_copy_field(f)
|
|
85
|
+
cf = @copy_fields[f.source]
|
|
86
|
+
cf << f
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def drop_copy_field(str)
|
|
90
|
+
@copy_fields.delete(str)
|
|
91
|
+
self
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def add_field_type(ft)
|
|
95
|
+
ft.core = @core
|
|
96
|
+
@field_types[ft.name] = ft
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def drop_field_type(str)
|
|
100
|
+
@field_types.delete(str)
|
|
101
|
+
self
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# For loading, we get the information about the fields via the API,
|
|
106
|
+
# but grab an XML document for modifying/writing
|
|
107
|
+
def load
|
|
108
|
+
@xmldoc = Nokogiri.XML(@core.raw_get_content('admin/file', {:file => 'schema.xml'})) do |config|
|
|
109
|
+
config.noent
|
|
110
|
+
end
|
|
111
|
+
load_explicit_fields
|
|
112
|
+
load_dynamic_fields
|
|
113
|
+
load_copy_fields
|
|
114
|
+
load_field_types
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def load_explicit_fields
|
|
119
|
+
@fields = {}
|
|
120
|
+
@core.get('schema/fields')['fields'].each do |field_hash|
|
|
121
|
+
add_field(Field.new_from_solr_hash(field_hash))
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def load_dynamic_fields
|
|
126
|
+
@dynamic_fields = {}
|
|
127
|
+
@core.get('schema/dynamicfields')['dynamicFields'].each do |field_hash|
|
|
128
|
+
f = DynamicField.new_from_solr_hash(field_hash)
|
|
129
|
+
if @dynamic_fields[f.name]
|
|
130
|
+
raise "Dynamic field '#{f.name}' defined more than once"
|
|
131
|
+
end
|
|
132
|
+
add_dynamic_field(f)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def load_copy_fields
|
|
137
|
+
@copy_fields = Hash.new { |h, k| h[k] = [] }
|
|
138
|
+
@core.get('schema/copyfields')['copyFields'].each do |cfield_hash|
|
|
139
|
+
add_copy_field(CopyField.new(cfield_hash['source'], cfield_hash['dest']))
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def load_field_types
|
|
144
|
+
@field_types = {}
|
|
145
|
+
@core.get('schema/fieldtypes')['fieldTypes'].each do |fthash|
|
|
146
|
+
ft = FieldType.new_from_solr_hash(fthash)
|
|
147
|
+
type_name = ft.name
|
|
148
|
+
attr = "[@name=\"#{type_name}\"]"
|
|
149
|
+
node = @xmldoc.css("fieldType#{attr}").first || @xmldoc.css("fieldtype#{attr}").first
|
|
150
|
+
unless node
|
|
151
|
+
puts "Failed for type #{type_name}"
|
|
152
|
+
end
|
|
153
|
+
ft.xml = node.to_xml
|
|
154
|
+
add_field_type(ft)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def clean_schema_xml
|
|
159
|
+
d = @xmldoc.dup
|
|
160
|
+
d.xpath('//comment()').remove
|
|
161
|
+
d.css('field').remove
|
|
162
|
+
d.css('fieldType').remove
|
|
163
|
+
d.css('fieldtype').remove
|
|
164
|
+
d.css('dynamicField').remove
|
|
165
|
+
d.css('copyField').remove
|
|
166
|
+
d.css('dynamicfield').remove
|
|
167
|
+
d.css('copyfield').remove
|
|
168
|
+
d.css('schema').children.find_all { |x| x.name == 'text' }.each { |x| x.remove }
|
|
169
|
+
d
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def to_xml
|
|
173
|
+
# Get a clean schema XML document
|
|
174
|
+
d = clean_schema_xml
|
|
175
|
+
s = d.css('schema').first
|
|
176
|
+
[fields, dynamic_fields, copy_fields, field_types].flatten.each do |f|
|
|
177
|
+
s.add_child f.to_xml_node
|
|
178
|
+
end
|
|
179
|
+
d.to_xml
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def write
|
|
184
|
+
File.open(@core.schema_file, 'w:utf-8') do |out|
|
|
185
|
+
out.puts self.to_xml
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def reload
|
|
190
|
+
@core.reload
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# Figuring out which fields are actually produced can be hard:
|
|
195
|
+
# * If a non-dynamic field name matches, no dynamic_fields will match
|
|
196
|
+
# * The result of a copyField may match another dynamicField, but the
|
|
197
|
+
# result of *that* will not match more copyFields
|
|
198
|
+
# * dynamicFields are matched longest to shortest
|
|
199
|
+
#
|
|
200
|
+
# Suppose I have the following:
|
|
201
|
+
# dynamic *_ts => string
|
|
202
|
+
# dynamic *_t => string
|
|
203
|
+
# dynamic *_s => string
|
|
204
|
+
# dynamic *_ddd => string
|
|
205
|
+
#
|
|
206
|
+
# copy *_ts => *_t
|
|
207
|
+
# copy *_ts => *_s
|
|
208
|
+
# copy *_s => *_ddd
|
|
209
|
+
#
|
|
210
|
+
# You might expect:
|
|
211
|
+
# name_ts => string
|
|
212
|
+
# name_ts copied to name_t => string
|
|
213
|
+
# name_ts copied to name_s => string
|
|
214
|
+
# name_s copied to name_ddd => string
|
|
215
|
+
#
|
|
216
|
+
# ...giving us name_ts, name_t, name_s, and name_ddd
|
|
217
|
+
#
|
|
218
|
+
# What you'll find is that we don't get name_ddd, since
|
|
219
|
+
# name_s was generated by a wildcard-enabled copyField
|
|
220
|
+
# and that's where things stop.
|
|
221
|
+
#
|
|
222
|
+
# However, if you explicitly add a field called
|
|
223
|
+
# name_s, it *will* get copied to name_ddd.
|
|
224
|
+
#
|
|
225
|
+
# Yeah. It's confusing.
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def first_matching_field(str)
|
|
229
|
+
f = fields.find { |x| x.matches str } or first_matching_dfield(str)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def first_matching_dfield(str)
|
|
233
|
+
df = dynamic_fields.find { |x| x.matches str }
|
|
234
|
+
if df
|
|
235
|
+
f = Field.new(df.to_h)
|
|
236
|
+
f[:name] = df.dynamic_name str
|
|
237
|
+
end
|
|
238
|
+
f
|
|
239
|
+
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def resulting_fields(str)
|
|
243
|
+
rv = []
|
|
244
|
+
f = first_matching_field(str)
|
|
245
|
+
rv << f
|
|
246
|
+
copy_fields.each do |cf|
|
|
247
|
+
if cf.matches(f.name)
|
|
248
|
+
dname = cf.dynamic_name(f.name)
|
|
249
|
+
fmf = Field.new(first_matching_field(dname).to_h)
|
|
250
|
+
fmf[:name] = dname
|
|
251
|
+
rv << fmf
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
rv.uniq
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
|