dm-sphinx-adapter 0.4 → 0.5

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