dm-sphinx-adapter 0.4 → 0.5

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. data/History.txt +12 -0
  2. data/Manifest.txt +24 -13
  3. data/README.txt +44 -28
  4. data/Rakefile +1 -1
  5. data/dm-sphinx-adapter.gemspec +7 -7
  6. data/lib/dm-sphinx-adapter.rb +8 -6
  7. data/lib/dm-sphinx-adapter/adapter.rb +218 -0
  8. data/lib/dm-sphinx-adapter/attribute.rb +38 -0
  9. data/lib/dm-sphinx-adapter/client.rb +109 -0
  10. data/lib/dm-sphinx-adapter/config.rb +122 -0
  11. data/lib/dm-sphinx-adapter/config_parser.rb +71 -0
  12. data/lib/dm-sphinx-adapter/index.rb +38 -0
  13. data/lib/dm-sphinx-adapter/query.rb +67 -0
  14. data/lib/dm-sphinx-adapter/resource.rb +103 -0
  15. data/test/{fixtures/item.sql → files/dm_sphinx_adapter_test.sql} +3 -3
  16. data/test/{fixtures/item_resource_explicit.rb → files/resource_explicit.rb} +5 -6
  17. data/test/{fixtures/item_resource_only.rb → files/resource_resource.rb} +4 -5
  18. data/test/{fixtures/item.rb → files/resource_searchable.rb} +8 -5
  19. data/test/files/resource_storage_name.rb +11 -0
  20. data/test/files/resource_vanilla.rb +7 -0
  21. data/test/{data → files}/sphinx.conf +11 -6
  22. data/test/test_adapter.rb +38 -0
  23. data/test/test_adapter_explicit.rb +48 -0
  24. data/test/test_adapter_resource.rb +25 -0
  25. data/test/test_adapter_searchable.rb +23 -0
  26. data/test/test_adapter_vanilla.rb +46 -0
  27. data/test/test_client.rb +9 -21
  28. data/test/test_config.rb +58 -24
  29. data/test/test_config_parser.rb +29 -0
  30. data/test/test_query.rb +47 -0
  31. data/test/test_type_attribute.rb +8 -0
  32. data/test/test_type_index.rb +8 -0
  33. metadata +38 -19
  34. data/lib/dm-sphinx-adapter/sphinx_adapter.rb +0 -220
  35. data/lib/dm-sphinx-adapter/sphinx_attribute.rb +0 -22
  36. data/lib/dm-sphinx-adapter/sphinx_client.rb +0 -81
  37. data/lib/dm-sphinx-adapter/sphinx_config.rb +0 -160
  38. data/lib/dm-sphinx-adapter/sphinx_index.rb +0 -21
  39. data/lib/dm-sphinx-adapter/sphinx_resource.rb +0 -88
  40. data/test/helper.rb +0 -7
  41. data/test/test_search.rb +0 -52
@@ -1,22 +0,0 @@
1
- module DataMapper
2
- class SphinxAttribute < Property
3
-
4
- # DataMapper types supported as Sphinx attributes.
5
- TYPES = [
6
- TrueClass, # sql_attr_bool
7
- String, # sql_attr_str2ordinal
8
- # DataMapper::Types::Text,
9
- Float, # sql_attr_float
10
- Integer, # sql_attr_uint
11
- # BigDecimal, # sql_attr_float?
12
- DateTime, # sql_attr_timestamp
13
- Date, # sql_attr_timestamp
14
- Time, # sql_attr_timestamp
15
- # Object,
16
- # Class,
17
- # DataMapper::Types::Discriminator,
18
- DataMapper::Types::Serial # sql_attr_uint
19
- ]
20
-
21
- end # SphinxAttribute
22
- end # DataMapper
@@ -1,81 +0,0 @@
1
- require 'rubygems'
2
-
3
- gem 'riddle', '~> 0.9'
4
- require 'riddle'
5
-
6
- module DataMapper
7
- class SphinxClient
8
- def initialize(uri_or_options)
9
- # TODO: Documentation.
10
- @config = SphinxConfig.new(uri_or_options)
11
- end
12
-
13
- ##
14
- # Search one or more indexes.
15
- #
16
- # @param [String] query The sphinx query string.
17
- # @param [Array, String] indexes A string or array of indexes to search. Default is '*' (all).
18
- # @param [Hash] options Any options you'd like to pass through to Riddle::Client.
19
- # @see Riddle::Client
20
- def search(query, indexes = '*', options = {})
21
- indexes = indexes.join(' ') if indexes.kind_of?(Array)
22
-
23
- client = Riddle::Client.new(@config.address, @config.port)
24
- options.each{|k, v| client.method("#{k}=".to_sym).call(v) if client.respond_to?("#{k}=".to_sym)}
25
- client.query(query, indexes.to_s)
26
- end
27
-
28
- ##
29
- # Index one or more indexes.
30
- #
31
- # @param [Array, String] indexes Defaults to --all if indexes is nil or '*'.
32
- def index(indexes = nil, options = {})
33
- indexes = indexes.join(' ') if indexes.kind_of?(Array)
34
-
35
- command = @config.indexer_bin
36
- command << " --rotate" if running?
37
- command << ((indexes.nil? || indexes == '*') ? ' --all' : " #{indexes.to_s}")
38
- warn "Sphinx: Indexer #{$1}" if `#{command}` =~ /(?:error|fatal|warning):?\s*([^\n]+)/i
39
- end
40
-
41
- protected
42
-
43
- ##
44
- # Is the client running.
45
- #
46
- # Tests the address and port set in the configuration file.
47
- def running?
48
- !!TCPSocket.new(@config.address, @config.port) rescue nil
49
- end
50
- end # SphinxClient
51
-
52
- ##
53
- # Managed searchd if you don't already have god/monit doing the job for you.
54
- #
55
- # Requires you have daemon_controller installed.
56
- # @see http://github.com/FooBarWidget/daemon_controller/tree/master
57
- class SphinxManagedClient < SphinxClient
58
- def initialize(url_or_options)
59
- super
60
-
61
- # Fire up searchd.
62
- require 'daemon_controller'
63
- @client = DaemonController.new(
64
- :identifier => 'Sphinx searchd',
65
- :start_command => @config.searchd_bin,
66
- :stop_command => "#{@config.searchd_bin} --stop",
67
- :ping_command => method(:running?),
68
- :pid_file => @config.pid_file,
69
- :log_file => @config.log
70
- )
71
- end
72
-
73
- def search(*args)
74
- @client.connect{super}
75
- end
76
-
77
- def stop
78
- @client.stop
79
- end
80
- end # SphinxManagedClient
81
- end # DataMapper
@@ -1,160 +0,0 @@
1
- require 'strscan'
2
- require 'pathname'
3
-
4
- # TODO: Error classes.
5
- # TODO: Just warn if a config file can't be found.
6
-
7
- module DataMapper
8
- class SphinxConfig
9
-
10
- ##
11
- # Read a sphinx configuration file.
12
- #
13
- # This class just gives you access to handy searchd {} configuration options. It does not validate your
14
- # configuration file beyond basic syntax checking.
15
- def initialize(uri_or_options = {})
16
- @config = []
17
- @searchd = {
18
- 'address' => '0.0.0.0',
19
- 'log' => 'searchd.log',
20
- 'max_children' => 0,
21
- 'max_matches' => 1000,
22
- 'pid_file' => nil,
23
- 'port' => 3312,
24
- 'preopen_indexes' => 0,
25
- 'query_log' => '',
26
- 'read_timeout' => 5,
27
- 'seamless_rotate' => 1,
28
- 'unlink_old' => 1
29
- }
30
-
31
- path = case uri_or_options
32
- when Addressable::URI, DataObjects::URI then uri_or_options.path
33
- when Hash then uri_or_options[:config] || uri_or_options[:database]
34
- when Pathname then uri_or_options
35
- when String then DataObjects::URI.parse(uri_or_options).path
36
- end
37
- parse('' + path.to_s) # Force stringy since Pathname#to_s is broken IMO.
38
- end
39
-
40
- ##
41
- # Configuration file full path name.
42
- #
43
- # @return [String]
44
- def config
45
- @config
46
- end
47
-
48
- ##
49
- # Indexer binary full path name and config argument.
50
- def indexer_bin(use_config = true)
51
- path = 'indexer' # TODO: Real.
52
- path << " --config #{config}" if config
53
- path
54
- end
55
-
56
- ##
57
- # Searchd binardy full path name and config argument.
58
- def searchd_bin(use_config = true)
59
- path = 'searchd' # TODO: Real.
60
- path << " --config #{config}" if config
61
- path
62
- end
63
-
64
- ##
65
- # Searchd address.
66
- def address
67
- searchd['address']
68
- end
69
-
70
- ##
71
- # Searchd port.
72
- def port
73
- searchd['port']
74
- end
75
-
76
- ##
77
- # Searchd pid_file.
78
- def pid_file
79
- searchd['pid_file'] or raise "Mandatory pid_file option missing from searchd configuration."
80
- end
81
-
82
- ##
83
- # Searchd log file.
84
- def log
85
- searchd['log']
86
- end
87
-
88
- ##
89
- # Searchd configuration options.
90
- #
91
- # @see http://www.sphinxsearch.com/doc.html#confgroup-searchd
92
- def searchd
93
- @searchd
94
- end
95
-
96
- protected
97
-
98
- ##
99
- # Parse a sphinx config file.
100
- #
101
- # @param [String] path Searches path, ./path, /path, /usr/local/etc/sphinx.conf, ./sphinx.conf in that order.
102
- def parse(path = '')
103
- # TODO: Three discrete things going on here, should be three subs.
104
- paths = [
105
- path,
106
- path.gsub(%r{^/}, './'),
107
- path.gsub(%r{^\./}, '/'),
108
- '/usr/local/etc/sphinx.conf', # TODO: Does this one depend on where searchd/indexer is installed?
109
- './sphinx.conf'
110
- ]
111
- paths.find do |path|
112
- @config = Pathname.new(path).expand_path
113
- @config.readable? && `#{indexer_bin}` !~ /fatal|error/i
114
- end or raise IOError, %{No readable config file (looked in #{paths.join(', ')})}
115
-
116
- source = config.read
117
- source.gsub!(/\r\n|\r|\n/, "\n") # Everything in \n
118
- source.gsub!(/\s*\\\n\s*/, ' ') # Remove unixy line wraps.
119
- @in = StringScanner.new(source)
120
- blocks(blocks = [])
121
- @in = nil
122
-
123
- searchd = blocks.find{|c| c['type'] =~ /searchd/i} || {}
124
- @searchd.update(searchd)
125
- end
126
-
127
- private
128
-
129
- def blocks(out = []) #:nodoc:
130
- if @in.scan(/\#[^\n]*\n/) || @in.scan(/\s+/)
131
- blocks(out)
132
- elsif @in.scan(/indexer|searchd|source|index/i)
133
- out << group = {'type' => @in.matched}
134
- if @in.matched =~ /^(?:index|source)$/i
135
- @in.scan(/\s* ([\w_\-]+) (?:\s*:\s*([\w_\-]+))? \s*/x) or raise "Expected #{group[:type]} name."
136
- group['name'] = @in[1]
137
- group['ancestor'] = @in[2]
138
- end
139
- @in.scan(/\s*\{/) or raise %q{Expected '\{'.}
140
- pairs(kv = {})
141
- group.merge!(kv)
142
- @in.scan(/\s*\}/) or raise %q{Expected '\}'.}
143
- blocks(out)
144
- else
145
- raise "Unknown near: #{@in.peek(30)}" unless @in.eos?
146
- end
147
- end
148
-
149
- def pairs(out = {}) #:nodoc:
150
- if @in.scan(/\#[^\n]*\n/) || @in.scan(/\s+/)
151
- pairs(out)
152
- elsif @in.scan(/[\w_-]+/)
153
- key = @in.matched
154
- @in.scan(/\s*=/) or raise %q{Expected '='.}
155
- out[key] = @in.scan(/[^\n]*\n/).strip
156
- pairs(out)
157
- end
158
- end
159
- end # SphinxConfig
160
- end # DataMapper
@@ -1,21 +0,0 @@
1
- module DataMapper
2
- class SphinxIndex
3
- include Assertions
4
-
5
- attr_reader :model, :name, :options
6
-
7
- def initialize(model, name, options = {})
8
- assert_kind_of 'model', model, Model
9
- assert_kind_of 'name', name, Symbol, String
10
- assert_kind_of 'options', options, Hash
11
-
12
- @model = model
13
- @name = name.to_sym
14
- @delta = options.fetch(:delta, nil)
15
- end
16
-
17
- def delta?
18
- !!@delta
19
- end
20
- end # SphinxIndex
21
- end # DataMapper
@@ -1,88 +0,0 @@
1
- module DataMapper
2
- ##
3
- # Declare Sphinx indexes in your resource.
4
- #
5
- # model Items
6
- # include Sphinx::Resource
7
- #
8
- # # .. normal properties and such for :default
9
- #
10
- # repository(:search) do
11
- # # Query some_index, some_index_delta in that order.
12
- # index :some_index
13
- # index :some_index_delta, :delta => true
14
- #
15
- # # Sortable by some attributes.
16
- # attribute :updated_at, DateTime # sql_attr_timestamp
17
- # attribute :age, Integer # sql_attr_uint
18
- # attribute :deleted, Boolean # sql_attr_bool
19
- # end
20
- # end
21
- module SphinxResource
22
- def self.included(model) #:nodoc:
23
- model.class_eval do
24
- include DataMapper::Resource
25
- extend ClassMethods
26
- end
27
- end
28
-
29
- module ClassMethods
30
- def self.extended(model) #:nodoc:
31
- model.instance_variable_set(:@sphinx_indexes, {})
32
- model.instance_variable_set(:@sphinx_attributes, {})
33
- end
34
-
35
- ##
36
- # Defines a sphinx index on the resource.
37
- #
38
- # Indexes are naturally ordered, with delta indexes at the end of the list so that duplicate document IDs in
39
- # delta indexes override your main indexes.
40
- #
41
- # @param [Symbol] name the name of a sphinx index to search for this resource
42
- # @param [Hash(Symbol => String)] options a hash of available options
43
- # @see DataMapper::SphinxIndex
44
- def index(name, options = {})
45
- index = SphinxIndex.new(self, name, options)
46
- indexes = sphinx_indexes(repository_name)
47
- indexes << index
48
-
49
- # TODO: I'm such a Ruby nub. In the meantime I've gone back to my Perl roots.
50
- # This is a Schwartzian transform to sort delta indexes to the bottom and natural sort by name.
51
- mapped = indexes.map{|i| [(i.delta? ? 1 : 0), i.name, i]}
52
- sorted = mapped.sort{|a, b| a[0] <=> b[0] || a[1] <=> b[1]}
53
- indexes.replace(sorted.map{|i| i[2]})
54
-
55
- index
56
- end
57
-
58
- ##
59
- # List of declared sphinx indexes for this model.
60
- def sphinx_indexes(repository_name = default_repository_name)
61
- @sphinx_indexes[repository_name] ||= []
62
- end
63
-
64
- ##
65
- # Defines a sphinx attribute on the resource.
66
- #
67
- # @param [Symbol] name the name of a sphinx attribute to order/restrict by for this resource
68
- # @param [Class] type the type to define this attribute as
69
- # @param [Hash(Symbol => String)] options a hash of available options
70
- # @see DataMapper::SphinxAttribute
71
- def attribute(name, type, options = {})
72
- # Attributes are just properties without a getter/setter in the model.
73
- # This keeps DataMapper::Query happy when building queries.
74
- attribute = SphinxAttribute.new(self, name, type, options)
75
- properties(repository_name)[attribute.name] = attribute
76
- attribute
77
- end
78
-
79
- ##
80
- # List of declared sphinx attributes for this model.
81
- def sphinx_attributes(repository_name = default_repository_name)
82
- properties(repository_name).grep{|p| p.kind_of? SphinxAttribute}
83
- end
84
-
85
- end # ClassMethods
86
- end # SphinxResource
87
- end # DataMapper
88
-
data/test/helper.rb DELETED
@@ -1,7 +0,0 @@
1
- require 'dm-sphinx-adapter'
2
- require 'test/fixtures/item'
3
- require 'test/fixtures/item_resource_only'
4
- require 'test/fixtures/item_resource_explicit'
5
- require 'test/unit'
6
-
7
- # DataMapper::Logger.new(STDOUT, :debug)
data/test/test_search.rb DELETED
@@ -1,52 +0,0 @@
1
- require 'helper'
2
-
3
-
4
- class TestSearch < Test::Unit::TestCase
5
- def setup
6
- # TODO: A little too brutal for me.
7
- Dir.chdir(File.join(File.dirname(__FILE__), 'fixtures')) do
8
- system 'mysql -u root dm_sphinx_adapter_test < item.sql' \
9
- or raise %q{Tests require the dm_sphinx_adapter_test.items table.}
10
- end
11
-
12
- @config = Pathname.new(__FILE__).dirname.expand_path / 'data' / 'sphinx.conf'
13
- DataMapper.setup(:default, 'mysql://localhost/dm_sphinx_adapter_test')
14
- DataMapper.setup(:search,
15
- :adapter => 'sphinx',
16
- :config => @config,
17
- :managed => true
18
- )
19
- end
20
-
21
- def teardown
22
- DataMapper.repository(:search).adapter.client.stop
23
- # You can also build a new client with the same config and call stop on that.
24
- # client = DataMapper::SphinxManagedClient.new(@config)
25
- # client.stop
26
- end
27
-
28
- def test_search
29
- assert_nothing_raised{ Item.search }
30
- assert_nothing_raised{ Item.search(:name => 'foo') }
31
- assert !Item.search(:name => 'foo').empty?
32
- end
33
-
34
- def test_search_resource_only
35
- assert_nothing_raised{ ItemResourceOnly.search }
36
- assert_nothing_raised{ ItemResourceOnly.search(:name => 'foo') }
37
- assert !ItemResourceOnly.search(:name => 'foo').empty?
38
- end
39
-
40
- def test_search_resource_explicit
41
- assert_nothing_raised{ ItemResourceExplicit.search }
42
- assert_nothing_raised{ ItemResourceExplicit.search(:name => 'foo') }
43
- assert !ItemResourceExplicit.search(:name => 'foo').empty?
44
- end
45
-
46
- def test_search_attributes
47
- # Attributes that exist only in :search and the sphinx.
48
- assert_nothing_raised do
49
- ItemResourceExplicit.search(:updated => (Time.now - 10 .. Time.now + 10))
50
- end
51
- end
52
- end # TestSearch