datamapper 0.3.2 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +0 -0
- data/Manifest.txt +5 -0
- data/README.txt +11 -0
- data/Rakefile +70 -0
- data/lib/datamapper.rb +8 -0
- metadata +152 -319
- data/CHANGELOG +0 -145
- data/FAQ +0 -96
- data/MIT-LICENSE +0 -22
- data/QUICKLINKS +0 -12
- data/README +0 -105
- data/environment.rb +0 -62
- data/example.rb +0 -156
- data/lib/data_mapper.rb +0 -88
- data/lib/data_mapper/adapters/abstract_adapter.rb +0 -43
- data/lib/data_mapper/adapters/data_object_adapter.rb +0 -480
- data/lib/data_mapper/adapters/mysql_adapter.rb +0 -72
- data/lib/data_mapper/adapters/postgresql_adapter.rb +0 -258
- data/lib/data_mapper/adapters/sql/coersion.rb +0 -134
- data/lib/data_mapper/adapters/sql/commands/load_command.rb +0 -545
- data/lib/data_mapper/adapters/sql/mappings/associations_set.rb +0 -34
- data/lib/data_mapper/adapters/sql/mappings/column.rb +0 -279
- data/lib/data_mapper/adapters/sql/mappings/conditions.rb +0 -172
- data/lib/data_mapper/adapters/sql/mappings/schema.rb +0 -60
- data/lib/data_mapper/adapters/sql/mappings/table.rb +0 -459
- data/lib/data_mapper/adapters/sql/quoting.rb +0 -24
- data/lib/data_mapper/adapters/sqlite3_adapter.rb +0 -159
- data/lib/data_mapper/associations.rb +0 -106
- data/lib/data_mapper/associations/belongs_to_association.rb +0 -160
- data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +0 -437
- data/lib/data_mapper/associations/has_many_association.rb +0 -283
- data/lib/data_mapper/associations/has_n_association.rb +0 -143
- data/lib/data_mapper/associations/reference.rb +0 -47
- data/lib/data_mapper/attributes.rb +0 -73
- data/lib/data_mapper/auto_migrations.rb +0 -36
- data/lib/data_mapper/base.rb +0 -17
- data/lib/data_mapper/callbacks.rb +0 -107
- data/lib/data_mapper/context.rb +0 -112
- data/lib/data_mapper/database.rb +0 -234
- data/lib/data_mapper/dependency_queue.rb +0 -28
- data/lib/data_mapper/embedded_value.rb +0 -145
- data/lib/data_mapper/identity_map.rb +0 -47
- data/lib/data_mapper/is/tree.rb +0 -121
- data/lib/data_mapper/migration.rb +0 -155
- data/lib/data_mapper/persistence.rb +0 -852
- data/lib/data_mapper/property.rb +0 -310
- data/lib/data_mapper/query.rb +0 -164
- data/lib/data_mapper/support/blank.rb +0 -35
- data/lib/data_mapper/support/connection_pool.rb +0 -117
- data/lib/data_mapper/support/enumerable.rb +0 -35
- data/lib/data_mapper/support/errors.rb +0 -16
- data/lib/data_mapper/support/inflector.rb +0 -265
- data/lib/data_mapper/support/object.rb +0 -54
- data/lib/data_mapper/support/serialization.rb +0 -96
- data/lib/data_mapper/support/silence.rb +0 -10
- data/lib/data_mapper/support/string.rb +0 -72
- data/lib/data_mapper/support/struct.rb +0 -7
- data/lib/data_mapper/support/symbol.rb +0 -82
- data/lib/data_mapper/support/typed_set.rb +0 -65
- data/lib/data_mapper/types/base.rb +0 -44
- data/lib/data_mapper/types/string.rb +0 -34
- data/lib/data_mapper/validatable_extensions/errors.rb +0 -12
- data/lib/data_mapper/validatable_extensions/macros.rb +0 -7
- data/lib/data_mapper/validatable_extensions/validatable_instance_methods.rb +0 -62
- data/lib/data_mapper/validatable_extensions/validation_base.rb +0 -18
- data/lib/data_mapper/validatable_extensions/validations/formats/email.rb +0 -43
- data/lib/data_mapper/validatable_extensions/validations/validates_acceptance_of.rb +0 -7
- data/lib/data_mapper/validatable_extensions/validations/validates_confirmation_of.rb +0 -7
- data/lib/data_mapper/validatable_extensions/validations/validates_each.rb +0 -7
- data/lib/data_mapper/validatable_extensions/validations/validates_format_of.rb +0 -28
- data/lib/data_mapper/validatable_extensions/validations/validates_length_of.rb +0 -15
- data/lib/data_mapper/validatable_extensions/validations/validates_numericality_of.rb +0 -7
- data/lib/data_mapper/validatable_extensions/validations/validates_presence_of.rb +0 -7
- data/lib/data_mapper/validatable_extensions/validations/validates_true_for.rb +0 -7
- data/lib/data_mapper/validatable_extensions/validations/validates_uniqueness_of.rb +0 -40
- data/lib/data_mapper/validations.rb +0 -20
- data/lib/data_mapper/validations/number_validator.rb +0 -40
- data/lib/data_mapper/validations/string_validator.rb +0 -20
- data/lib/data_mapper/validations/validator.rb +0 -13
- data/performance.rb +0 -307
- data/plugins/can_has_sphinx/LICENSE +0 -23
- data/plugins/can_has_sphinx/README +0 -4
- data/plugins/can_has_sphinx/REVISION +0 -1
- data/plugins/can_has_sphinx/Rakefile +0 -22
- data/plugins/can_has_sphinx/init.rb +0 -1
- data/plugins/can_has_sphinx/install.rb +0 -1
- data/plugins/can_has_sphinx/lib/acts_as_sphinx.rb +0 -123
- data/plugins/can_has_sphinx/lib/sphinx.rb +0 -460
- data/plugins/can_has_sphinx/scripts/sphinx.sh +0 -47
- data/plugins/can_has_sphinx/tasks/acts_as_sphinx_tasks.rake +0 -41
- data/profile_data_mapper.rb +0 -40
- data/rakefile.rb +0 -159
- data/spec/acts_as_tree_spec.rb +0 -67
- data/spec/adapters/data_object_adapter_spec.rb +0 -31
- data/spec/associations/belongs_to_association_spec.rb +0 -98
- data/spec/associations/has_and_belongs_to_many_association_spec.rb +0 -377
- data/spec/associations/has_many_association_spec.rb +0 -337
- data/spec/attributes_spec.rb +0 -52
- data/spec/auto_migrations_spec.rb +0 -101
- data/spec/callbacks_spec.rb +0 -186
- data/spec/can_has_sphinx.rb +0 -5
- data/spec/coersion_spec.rb +0 -41
- data/spec/column_spec.rb +0 -114
- data/spec/count_command_spec.rb +0 -45
- data/spec/database_spec.rb +0 -18
- data/spec/dataobjects_spec.rb +0 -27
- data/spec/delete_command_spec.rb +0 -11
- data/spec/dependency_spec.rb +0 -29
- data/spec/embedded_value_spec.rb +0 -161
- data/spec/fixtures/animals.yaml +0 -33
- data/spec/fixtures/animals_exhibits.yaml +0 -2
- data/spec/fixtures/careers.yaml +0 -5
- data/spec/fixtures/comments.yaml +0 -1
- data/spec/fixtures/exhibits.yaml +0 -90
- data/spec/fixtures/fruit.yaml +0 -6
- data/spec/fixtures/people.yaml +0 -37
- data/spec/fixtures/posts.yaml +0 -3
- data/spec/fixtures/projects.yaml +0 -13
- data/spec/fixtures/sections.yaml +0 -5
- data/spec/fixtures/serializers.yaml +0 -6
- data/spec/fixtures/tasks.yaml +0 -6
- data/spec/fixtures/tasks_tasks.yaml +0 -2
- data/spec/fixtures/tomatoes.yaml +0 -1
- data/spec/fixtures/users.yaml +0 -1
- data/spec/fixtures/zoos.yaml +0 -24
- data/spec/is_a_tree_spec.rb +0 -149
- data/spec/legacy_spec.rb +0 -16
- data/spec/load_command_spec.rb +0 -322
- data/spec/magic_columns_spec.rb +0 -26
- data/spec/migration_spec.rb +0 -267
- data/spec/mock_adapter.rb +0 -20
- data/spec/models/animal.rb +0 -12
- data/spec/models/candidate.rb +0 -8
- data/spec/models/career.rb +0 -7
- data/spec/models/chain.rb +0 -8
- data/spec/models/comment.rb +0 -6
- data/spec/models/exhibit.rb +0 -14
- data/spec/models/fence.rb +0 -7
- data/spec/models/fruit.rb +0 -8
- data/spec/models/job.rb +0 -8
- data/spec/models/person.rb +0 -30
- data/spec/models/post.rb +0 -14
- data/spec/models/project.rb +0 -41
- data/spec/models/sales_person.rb +0 -5
- data/spec/models/section.rb +0 -8
- data/spec/models/serializer.rb +0 -5
- data/spec/models/task.rb +0 -9
- data/spec/models/tomato.rb +0 -27
- data/spec/models/user.rb +0 -12
- data/spec/models/zoo.rb +0 -13
- data/spec/natural_key_spec.rb +0 -36
- data/spec/paranoia_spec.rb +0 -38
- data/spec/persistence_spec.rb +0 -479
- data/spec/postgres_spec.rb +0 -96
- data/spec/property_spec.rb +0 -151
- data/spec/query_spec.rb +0 -77
- data/spec/save_command_spec.rb +0 -94
- data/spec/schema_spec.rb +0 -8
- data/spec/serialize_spec.rb +0 -19
- data/spec/single_table_inheritance_spec.rb +0 -43
- data/spec/spec_helper.rb +0 -45
- data/spec/support/blank_spec.rb +0 -8
- data/spec/support/inflector_spec.rb +0 -41
- data/spec/support/object_spec.rb +0 -9
- data/spec/support/serialization_spec.rb +0 -61
- data/spec/support/silence_spec.rb +0 -15
- data/spec/support/string_spec.rb +0 -7
- data/spec/support/struct_spec.rb +0 -12
- data/spec/support/typed_set_spec.rb +0 -66
- data/spec/symbolic_operators_spec.rb +0 -27
- data/spec/table_spec.rb +0 -79
- data/spec/types/string.rb +0 -81
- data/spec/validates_confirmation_of_spec.rb +0 -55
- data/spec/validates_format_of_spec.rb +0 -78
- data/spec/validates_length_of_spec.rb +0 -117
- data/spec/validates_uniqueness_of_spec.rb +0 -92
- data/spec/validations/number_validator.rb +0 -59
- data/spec/validations/string_validator.rb +0 -14
- data/spec/validations_spec.rb +0 -141
- data/tasks/fixtures.rb +0 -53
@@ -1,23 +0,0 @@
|
|
1
|
-
Copyright (C) 2005 Kent Sibilev <ksibilev@yahoo.com>
|
2
|
-
All rights reserved.
|
3
|
-
*
|
4
|
-
Redistribution and use in source and binary forms, with or without
|
5
|
-
modification, are permitted provided that the following conditions
|
6
|
-
are met:
|
7
|
-
1. Redistributions of source code must retain the above copyright
|
8
|
-
notice, this list of conditions and the following disclaimer.
|
9
|
-
2. Redistributions in binary form must reproduce the above copyright
|
10
|
-
notice, this list of conditions and the following disclaimer in the
|
11
|
-
documentation and/or other materials provided with the distribution.
|
12
|
-
*
|
13
|
-
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
14
|
-
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
15
|
-
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
16
|
-
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
17
|
-
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
18
|
-
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
19
|
-
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
20
|
-
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
21
|
-
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
22
|
-
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
23
|
-
SUCH DAMAGE.
|
@@ -1 +0,0 @@
|
|
1
|
-
21
|
@@ -1,22 +0,0 @@
|
|
1
|
-
require 'rake'
|
2
|
-
require 'rake/testtask'
|
3
|
-
require 'rake/rdoctask'
|
4
|
-
|
5
|
-
desc 'Default: run unit tests.'
|
6
|
-
task :default => :test
|
7
|
-
|
8
|
-
desc 'Test the acts_as_sphinx plugin.'
|
9
|
-
Rake::TestTask.new(:test) do |t|
|
10
|
-
t.libs << 'lib'
|
11
|
-
t.pattern = 'test/**/*_test.rb'
|
12
|
-
t.verbose = true
|
13
|
-
end
|
14
|
-
|
15
|
-
desc 'Generate documentation for the acts_as_sphinx plugin.'
|
16
|
-
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
-
rdoc.rdoc_dir = 'rdoc'
|
18
|
-
rdoc.title = 'ActsAsSphinx'
|
19
|
-
rdoc.options << '--line-numbers' << '--inline-source'
|
20
|
-
rdoc.rdoc_files.include('README')
|
21
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
-
end
|
@@ -1 +0,0 @@
|
|
1
|
-
require "acts_as_sphinx"
|
@@ -1 +0,0 @@
|
|
1
|
-
# Install hook code here
|
@@ -1,123 +0,0 @@
|
|
1
|
-
require "sphinx"
|
2
|
-
|
3
|
-
module ActsAsSphinx
|
4
|
-
module ClassMethods
|
5
|
-
# Associates the model class with a sphinx index, which will be used by find_with_sphinx method.
|
6
|
-
# You can pass the following options:
|
7
|
-
#
|
8
|
-
# :host is the host name or an IP address where searchd daemon is running, default is localhost
|
9
|
-
# :port is the port number of the searchd process, default is 3312
|
10
|
-
# :index is the name of the index to be used, default is the name of the table for the current model class.
|
11
|
-
def acts_as_sphinx(options = {})
|
12
|
-
options.assert_valid_keys(SphinxClassMethods::VALID_OPTIONS)
|
13
|
-
|
14
|
-
default_options = {:host => 'localhost', :port => 3312, :index => name.tableize}
|
15
|
-
write_inheritable_attribute 'sphinx_options', options.reverse_merge(default_options)
|
16
|
-
extend SphinxClassMethods
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.included(receiver)
|
21
|
-
receiver.extend(ClassMethods)
|
22
|
-
end
|
23
|
-
|
24
|
-
module SphinxClassMethods
|
25
|
-
VALID_OPTIONS = %w[mode offset page limit index weights host
|
26
|
-
port range filter filter_range group_by sort_mode].map(&:to_sym)
|
27
|
-
|
28
|
-
def sphinx_index
|
29
|
-
read_inheritable_attribute('sphinx_options')[:index]
|
30
|
-
end
|
31
|
-
|
32
|
-
def sphinx_options
|
33
|
-
read_inheritable_attribute 'sphinx_options'
|
34
|
-
end
|
35
|
-
|
36
|
-
# Performs a sphinx search and returns a hash object as defined by Sphinx#query method.
|
37
|
-
# This methods accepts the same set of options as :sphinx option of find_with_sphinx method.
|
38
|
-
def ask_sphinx(query, options = {})
|
39
|
-
options.assert_valid_keys(VALID_OPTIONS)
|
40
|
-
|
41
|
-
default_options = {:offset => 0, :limit => 20}
|
42
|
-
default_options.merge! sphinx_options
|
43
|
-
options.reverse_merge! default_options
|
44
|
-
|
45
|
-
if options[:page] && options[:limit]
|
46
|
-
options[:offset] = options[:limit] * (options[:page].to_i - 1)
|
47
|
-
options[:offset] = 0 if options[:offset] < 0
|
48
|
-
end
|
49
|
-
|
50
|
-
sphinx = Sphinx.new
|
51
|
-
sphinx.set_server options[:host], options[:port]
|
52
|
-
sphinx.set_limits options[:offset], options[:limit]
|
53
|
-
sphinx.set_weights options[:weights] if options[:weights]
|
54
|
-
sphinx.set_id_range options[:range] if options[:range]
|
55
|
-
|
56
|
-
options[:filter].each do |attr, values|
|
57
|
-
sphinx.set_filter attr, [*values]
|
58
|
-
end if options[:filter]
|
59
|
-
|
60
|
-
options[:filter_range].each do |attr, (min, max)|
|
61
|
-
sphinx.set_filter_range attr, min, max
|
62
|
-
end if options[:filter_range]
|
63
|
-
|
64
|
-
options[:group_by].each do |attr, func|
|
65
|
-
funcion = Sphinx.const_get("SPH_GROUPBY_#{func.to_s.upcase}") \
|
66
|
-
rescue raise("Unknown group by function #{func}")
|
67
|
-
sphinx.set_group_by attr, funcion
|
68
|
-
end if options[:group_by]
|
69
|
-
|
70
|
-
if options[:mode]
|
71
|
-
match_mode = Sphinx.const_get("SPH_MATCH_#{options[:mode].to_s.upcase}") \
|
72
|
-
rescue raise("Unknown search mode #{options[:mode]}")
|
73
|
-
sphinx.set_match_mode match_mode
|
74
|
-
end
|
75
|
-
|
76
|
-
if options[:sort_mode]
|
77
|
-
sort_mode, sort_expr = options[:sort_mode]
|
78
|
-
sort_mode = Sphinx.const_get("SPH_SORT_#{sort_mode.to_s.upcase}") \
|
79
|
-
rescue raise("Unknown sort mode #{sort_mode}")
|
80
|
-
sphinx.set_sort_mode sort_mode, sort_expr
|
81
|
-
end
|
82
|
-
|
83
|
-
sphinx.query query, options[:index]
|
84
|
-
end
|
85
|
-
|
86
|
-
# Find all model objects using sphinx index.
|
87
|
-
# Besides regular ActiveRecord::Base#find method's options, you can specify
|
88
|
-
# :sphinx key that points to a hash with the following sphinx specific parameters:
|
89
|
-
#
|
90
|
-
# :mode defines the search mode (:all, :any, :boolean, :extended)
|
91
|
-
# :sort_mode defines the sort mode (:relevance, :attr_desc, :attr_asc, :time_segments, :extended),
|
92
|
-
# for example :sort_mode => [:attr_desc, 'myattr']
|
93
|
-
# :limit restricts result to a specified number of objects, default is 20
|
94
|
-
# :offset make this method return from a specific offset, default is 0
|
95
|
-
# :page can be used instead of :offset option to specify the page number
|
96
|
-
# :host overrides the default value of this option, see acts_as_sphinx method
|
97
|
-
# :port overrides the default value of this option, see acts_as_sphinx method
|
98
|
-
# :index overrides the default index name
|
99
|
-
# :weight is an array of weights for each index component (used in the relevance algorithm)
|
100
|
-
# :range is an array that defines the range document ids to be used, e.g. :range => [min, max]
|
101
|
-
# :fiter and :filter_range
|
102
|
-
# options define a search filter by an attribute
|
103
|
-
# :group_by makes the search result to be grouped by an attribute, e.g. :group_by => [attr, function],
|
104
|
-
# where function is :day, :week, :month, :year, or :attr
|
105
|
-
#
|
106
|
-
# The returned array has three special attributes:
|
107
|
-
#
|
108
|
-
# ary.total returns a total hits retrieved for this search
|
109
|
-
# ary.total_found returns a total number of hits found while scanning indexes.
|
110
|
-
# ary.time returns a time spent performing the search.
|
111
|
-
def find_with_sphinx(query, options = {})
|
112
|
-
result = ask_sphinx(query, options.delete(:sphinx) || {})
|
113
|
-
records = result[:matches].empty? ? [] : find(result[:matches].keys, options)
|
114
|
-
records = records.sort_by{|r| -result[:matches][r.id][:weight] }
|
115
|
-
%w[total total_found time].map(&:to_sym).each do |method|
|
116
|
-
class << records; self end.send(:define_method, method) {result[method]}
|
117
|
-
end
|
118
|
-
records
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
ActiveRecord::Base.send :include, ActsAsSphinx
|
@@ -1,460 +0,0 @@
|
|
1
|
-
require "socket"
|
2
|
-
# = sphinx.rb - Sphinx Client Library
|
3
|
-
#
|
4
|
-
# Author:: Dmytro Shteflyuk <mailto:kpumuk@kpumuk.info>.
|
5
|
-
# Copyright:: Copyright (c) 2006 Wildbit, LLC
|
6
|
-
# License:: Distributes under the same terms as Ruby
|
7
|
-
#
|
8
|
-
# This library is distributed under the terms of the Ruby license.
|
9
|
-
# You can freely distribute/modify this library.
|
10
|
-
|
11
|
-
# ==Sphinx Client Library
|
12
|
-
#
|
13
|
-
# The Sphinx Client Library is used to communicate with <tt>searchd</tt>
|
14
|
-
# daemon and get search results from Sphinx.
|
15
|
-
#
|
16
|
-
# ===Usage
|
17
|
-
#
|
18
|
-
# sphinx = Sphinx.new
|
19
|
-
# result = sphinx.query('test')
|
20
|
-
# ids = result[:matches].map { |id, value| id }.join(',')
|
21
|
-
# posts = Post.find :all, :conditions => "id IN (#{ids})"
|
22
|
-
#
|
23
|
-
# docs = posts.map { |post| post.body }
|
24
|
-
# excerpts = sphinx.build_excerpts(docs, 'index', 'test')
|
25
|
-
#
|
26
|
-
class Sphinx
|
27
|
-
|
28
|
-
# :stopdoc:
|
29
|
-
class SphinxError < StandardError; end
|
30
|
-
class SphinxConnectError < SphinxError; end
|
31
|
-
class SphinxResponseError < SphinxError; end
|
32
|
-
class SphinxInternalError < SphinxError; end
|
33
|
-
class SphinxTemporaryError < SphinxError; end
|
34
|
-
class SphinxUnknownError < SphinxError; end
|
35
|
-
# :startdoc:
|
36
|
-
|
37
|
-
# known searchd commands
|
38
|
-
SEARCHD_COMMAND_SEARCH = 0
|
39
|
-
SEARCHD_COMMAND_EXCERPT = 1
|
40
|
-
|
41
|
-
# current client-side command implementation versions
|
42
|
-
VER_COMMAND_SEARCH = 0x104
|
43
|
-
VER_COMMAND_EXCERPT = 0x100
|
44
|
-
|
45
|
-
# known searchd status codes
|
46
|
-
SEARCHD_OK = 0
|
47
|
-
SEARCHD_ERROR = 1
|
48
|
-
SEARCHD_RETRY = 2
|
49
|
-
|
50
|
-
# known match modes
|
51
|
-
SPH_MATCH_ALL = 0
|
52
|
-
SPH_MATCH_ANY = 1
|
53
|
-
SPH_MATCH_PHRASE = 2
|
54
|
-
SPH_MATCH_BOOLEAN = 3
|
55
|
-
SPH_MATCH_EXTENDED = 4
|
56
|
-
|
57
|
-
# known sort modes
|
58
|
-
SPH_SORT_RELEVANCE = 0
|
59
|
-
SPH_SORT_ATTR_DESC = 1
|
60
|
-
SPH_SORT_ATTR_ASC = 2
|
61
|
-
SPH_SORT_TIME_SEGMENTS = 3
|
62
|
-
SPH_SORT_EXTENDED = 4
|
63
|
-
|
64
|
-
# known attribute types
|
65
|
-
SPH_ATTR_INTEGER = 1
|
66
|
-
SPH_ATTR_TIMESTAMP = 2
|
67
|
-
|
68
|
-
# known grouping functions
|
69
|
-
SPH_GROUPBY_DAY = 0
|
70
|
-
SPH_GROUPBY_WEEK = 1
|
71
|
-
SPH_GROUPBY_MONTH = 2
|
72
|
-
SPH_GROUPBY_YEAR = 3
|
73
|
-
SPH_GROUPBY_ATTR = 4
|
74
|
-
|
75
|
-
# Constructs the Sphinx object and sets options to their default values.
|
76
|
-
def initialize
|
77
|
-
@host = 'localhost' # searchd host (default is "localhost")
|
78
|
-
@port = 3312 # searchd port (default is 3312)
|
79
|
-
@offset = 0 # how much records to seek from result-set start (default is 0)
|
80
|
-
@limit = 20 # how much records to return from result-set starting at offset (default is 20)
|
81
|
-
@mode = SPH_MATCH_ALL # query matching mode (default is SPH_MATCH_ALL)
|
82
|
-
@weights = [] # per-field weights (default is 1 for all fields)
|
83
|
-
@sort = SPH_SORT_RELEVANCE # match sorting mode (default is SPH_SORT_RELEVANCE)
|
84
|
-
@sortby = '' # attribute to sort by (defualt is "")
|
85
|
-
@min_id = 0 # min ID to match (default is 0)
|
86
|
-
@max_id = 0xFFFFFFFF # max ID to match (default is UINT_MAX)
|
87
|
-
@min = {} # attribute name to min-value hash (for range filters)
|
88
|
-
@max = {} # attribute name to max-value hash (for range filters)
|
89
|
-
@filter = {} # attribute name to values set hash (for values-set filters)
|
90
|
-
@groupby = '' # group-by attribute name
|
91
|
-
@groupfunc = SPH_GROUPBY_DAY # function to pre-process group-by attribute value with
|
92
|
-
@maxmatches = 1000 # max matches to retrieve
|
93
|
-
|
94
|
-
@error = '' # last error message
|
95
|
-
@warning = '' # last warning message
|
96
|
-
end
|
97
|
-
|
98
|
-
# Get last error message.
|
99
|
-
def last_error
|
100
|
-
@error
|
101
|
-
end
|
102
|
-
|
103
|
-
# Get last warning message.
|
104
|
-
def last_warning
|
105
|
-
@warning
|
106
|
-
end
|
107
|
-
|
108
|
-
# Set searchd server.
|
109
|
-
def set_server(host, port)
|
110
|
-
@host = host
|
111
|
-
@port = port
|
112
|
-
end
|
113
|
-
|
114
|
-
# Set match offset, count, and max number to retrieve.
|
115
|
-
def set_limits(offset, limit, max = 0)
|
116
|
-
@offset = offset
|
117
|
-
@limit = limit
|
118
|
-
@maxmatches = max if max > 0
|
119
|
-
end
|
120
|
-
|
121
|
-
# Set match mode.
|
122
|
-
def set_match_mode(mode)
|
123
|
-
@mode = mode
|
124
|
-
end
|
125
|
-
|
126
|
-
# Set sort mode.
|
127
|
-
def set_sort_mode(mode, sortby = '')
|
128
|
-
@sort = mode
|
129
|
-
@sortby = sortby
|
130
|
-
end
|
131
|
-
|
132
|
-
# Set per-field weights.
|
133
|
-
def set_weights(weights)
|
134
|
-
@weights = weights
|
135
|
-
end
|
136
|
-
|
137
|
-
# Set IDs range to match.
|
138
|
-
#
|
139
|
-
# Only match those records where document ID is beetwen <tt>min_id</tt> and <tt>max_id</tt>
|
140
|
-
# (including <tt>min_id</tt> and <tt>max_id</tt>).
|
141
|
-
def set_id_range(min_id, max_id)
|
142
|
-
@min_id = min_id
|
143
|
-
@max_id = max_id
|
144
|
-
end
|
145
|
-
|
146
|
-
# Set values filter.
|
147
|
-
#
|
148
|
-
# Only match those records where <tt>attr</tt> column values
|
149
|
-
# are in specified set.
|
150
|
-
def set_filter(attr, values)
|
151
|
-
@filter[attr] = values
|
152
|
-
end
|
153
|
-
|
154
|
-
# Set range filter.
|
155
|
-
#
|
156
|
-
# Only match those records where <tt>attr</tt> column value
|
157
|
-
# is beetwen <tt>min</tt> and <tt>max</tt> (including <tt>min</tt> and <tt>max</tt>).
|
158
|
-
def set_filter_range(attr, min, max)
|
159
|
-
@min[attr] = min
|
160
|
-
@max[attr] = max
|
161
|
-
end
|
162
|
-
|
163
|
-
# Set grouping.
|
164
|
-
#
|
165
|
-
# if grouping
|
166
|
-
def set_group_by(attr, func)
|
167
|
-
@groupby = attr
|
168
|
-
@groupfunc = func
|
169
|
-
end
|
170
|
-
|
171
|
-
# Connect to searchd server and run given search query.
|
172
|
-
#
|
173
|
-
# * <tt>query</tt> -- query string
|
174
|
-
# * <tt>index</tt> -- index name to query, default is "*" which means to query all indexes
|
175
|
-
#
|
176
|
-
# returns hash which has the following keys on success:
|
177
|
-
#
|
178
|
-
# * <tt>:matches</tt> -- hash which maps found document_id to ( "weight", "group" ) hash
|
179
|
-
# * <tt>:total</tt> -- total amount of matches retrieved (upto SPH_MAX_MATCHES, see sphinx.h)
|
180
|
-
# * <tt>:total_found</tt> -- total amount of matching documents in index
|
181
|
-
# * <tt>:time</tt> -- search time
|
182
|
-
# * <tt>:words</tt> -- hash which maps query terms (stemmed!) to ( :docs, :hits ) hash
|
183
|
-
def query(query, index = '*')
|
184
|
-
sock = connect
|
185
|
-
|
186
|
-
# build request
|
187
|
-
|
188
|
-
# mode and limits
|
189
|
-
req = [@offset, @limit, @mode, @sort].pack('NNNN')
|
190
|
-
req << [@sortby.length].pack('N')
|
191
|
-
req << @sortby
|
192
|
-
# query itself
|
193
|
-
req << [query.length].pack('N')
|
194
|
-
req << query
|
195
|
-
# weights
|
196
|
-
req << [@weights.length].pack('N')
|
197
|
-
req << @weights.pack('N' * @weights.length)
|
198
|
-
# indexes
|
199
|
-
req << [index.length].pack('N')
|
200
|
-
req << index
|
201
|
-
# id range
|
202
|
-
req << [@min_id.to_i, @max_id.to_i].pack('NN')
|
203
|
-
|
204
|
-
# filters
|
205
|
-
req << [@min.length + @filter.length].pack('N')
|
206
|
-
@min.each do |attribute, min|
|
207
|
-
req << [attribute.length].pack('N')
|
208
|
-
req << attribute
|
209
|
-
req << [0, min, @max[attribute]].pack('NNN')
|
210
|
-
end
|
211
|
-
|
212
|
-
@filter.each do |attribute, values|
|
213
|
-
req << [attribute.length].pack('N')
|
214
|
-
req << attribute
|
215
|
-
req << [values.length].pack('N')
|
216
|
-
req << values.pack('N' * values.length)
|
217
|
-
end
|
218
|
-
|
219
|
-
# group-by
|
220
|
-
req << [@groupfunc, @groupby.length].pack('NN')
|
221
|
-
req << @groupby
|
222
|
-
|
223
|
-
# max matches to retrieve
|
224
|
-
req << [@maxmatches].pack('N')
|
225
|
-
|
226
|
-
# send query, get response
|
227
|
-
len = req.length
|
228
|
-
# add header
|
229
|
-
req = [SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, len].pack('nnN') + req
|
230
|
-
sock.send(req, 0)
|
231
|
-
|
232
|
-
response = get_response(sock, VER_COMMAND_SEARCH)
|
233
|
-
|
234
|
-
# parse response
|
235
|
-
result = {}
|
236
|
-
max = response.length # protection from broken response
|
237
|
-
|
238
|
-
#read schema
|
239
|
-
p = 0
|
240
|
-
fields = []
|
241
|
-
attrs = {}
|
242
|
-
|
243
|
-
nfields = response[p, 4].unpack('N*').first
|
244
|
-
p += 4
|
245
|
-
while nfields > 0 and p < max
|
246
|
-
nfields -= 1
|
247
|
-
len = response[p, 4].unpack('N*').first
|
248
|
-
p += 4
|
249
|
-
fields << response[p, len]
|
250
|
-
p += len
|
251
|
-
end
|
252
|
-
result[:fields] = fields
|
253
|
-
|
254
|
-
nattrs = response[p, 4].unpack('N*').first
|
255
|
-
p += 4
|
256
|
-
while nattrs > 0 && p < max
|
257
|
-
nattrs -= 1
|
258
|
-
len = response[p, 4].unpack('N*').first
|
259
|
-
p += 4
|
260
|
-
attr = response[p, len]
|
261
|
-
p += len
|
262
|
-
type = response[p, 4].unpack('N*').first
|
263
|
-
p += 4
|
264
|
-
attrs[attr.to_sym] = type;
|
265
|
-
end
|
266
|
-
result[:attrs] = attrs
|
267
|
-
|
268
|
-
# read match count
|
269
|
-
count = response[p, 4].unpack('N*').first
|
270
|
-
p += 4
|
271
|
-
|
272
|
-
# read matches
|
273
|
-
result[:matches] = {}
|
274
|
-
while count > 0 and p < max
|
275
|
-
count -= 1
|
276
|
-
doc, weight = response[p, 8].unpack('N*N*')
|
277
|
-
p += 8
|
278
|
-
|
279
|
-
result[:matches][doc] ||= {}
|
280
|
-
result[:matches][doc][:weight] = weight
|
281
|
-
attrs.each do |attr, type|
|
282
|
-
val = response[p, 4].unpack('N*').first
|
283
|
-
p += 4
|
284
|
-
result[:matches][doc][:attrs] ||= {}
|
285
|
-
result[:matches][doc][:attrs][attr] = val
|
286
|
-
end
|
287
|
-
end
|
288
|
-
result[:total], result[:total_found], result[:time], words = \
|
289
|
-
response[p, 16].unpack('N*N*N*N*')
|
290
|
-
result[:time] = '%.3f' % (result[:time] / 1000)
|
291
|
-
p += 16
|
292
|
-
|
293
|
-
result[:words] = {}
|
294
|
-
while words > 0 and p < max
|
295
|
-
words -= 1
|
296
|
-
len = response[p, 4].unpack('N*').first
|
297
|
-
p += 4
|
298
|
-
word = response[p, len]
|
299
|
-
p += len
|
300
|
-
docs, hits = response[p, 8].unpack('N*N*')
|
301
|
-
p += 8
|
302
|
-
result[:words][word] = {:docs => docs, :hits => hits}
|
303
|
-
end
|
304
|
-
|
305
|
-
result
|
306
|
-
end
|
307
|
-
|
308
|
-
# Connect to searchd server and generate exceprts from given documents.
|
309
|
-
#
|
310
|
-
# * <tt>index</tt> -- a string specifiying the index which settings will be used
|
311
|
-
# for stemming, lexing and case folding
|
312
|
-
# * <tt>docs</tt> -- an array of strings which represent the documents' contents
|
313
|
-
# * <tt>words</tt> -- a string which contains the words to highlight
|
314
|
-
# * <tt>opts</tt> is a hash which contains additional optional highlighting parameters.
|
315
|
-
#
|
316
|
-
# You can use following parameters:
|
317
|
-
# * <tt>:before_match</tt> -- a string to insert before a set of matching words, default is "<b>"
|
318
|
-
# * <tt>:after_match</tt> -- a string to insert after a set of matching words, default is "<b>"
|
319
|
-
# * <tt>:chunk_separator</tt> -- a string to insert between excerpts chunks, default is " ... "
|
320
|
-
# * <tt>:limit</tt> -- max excerpt size in symbols (codepoints), default is 256
|
321
|
-
# * <tt>:around</tt> -- how much words to highlight around each match, default is 5
|
322
|
-
#
|
323
|
-
# Returns an array of string excerpts on success.
|
324
|
-
def build_excerpts(docs, index, words, opts = {})
|
325
|
-
sock = connect
|
326
|
-
|
327
|
-
# fixup options
|
328
|
-
opts[:before_match] ||= '<b>';
|
329
|
-
opts[:after_match] ||= '</b>';
|
330
|
-
opts[:chunk_separator] ||= ' ... ';
|
331
|
-
opts[:limit] ||= 256;
|
332
|
-
opts[:around] ||= 5;
|
333
|
-
|
334
|
-
# build request
|
335
|
-
|
336
|
-
# v.1.0 req
|
337
|
-
req = [0, 1].pack('N2'); # mode=0, flags=1 (remove spaces)
|
338
|
-
# req index
|
339
|
-
req << [index.length].pack('N')
|
340
|
-
req << index
|
341
|
-
# req words
|
342
|
-
req << [words.length].pack('N')
|
343
|
-
req << words
|
344
|
-
|
345
|
-
# options
|
346
|
-
req << [opts[:before_match].length].pack('N')
|
347
|
-
req << opts[:before_match]
|
348
|
-
req << [opts[:after_match].length].pack('N')
|
349
|
-
req << opts[:after_match]
|
350
|
-
req << [opts[:chunk_separator].length].pack('N')
|
351
|
-
req << opts[:chunk_separator]
|
352
|
-
req << [opts[:limit].to_i, opts[:around].to_i].pack('NN')
|
353
|
-
|
354
|
-
# documents
|
355
|
-
req << [docs.size].pack('N');
|
356
|
-
docs.each do |doc|
|
357
|
-
req << [doc.length].pack('N')
|
358
|
-
req << doc
|
359
|
-
end
|
360
|
-
|
361
|
-
# send query, get response
|
362
|
-
len = req.length
|
363
|
-
# add header
|
364
|
-
req = [SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, len].pack('nnN') + req
|
365
|
-
sock.send(req, 0)
|
366
|
-
|
367
|
-
response = get_response(sock, VER_COMMAND_EXCERPT)
|
368
|
-
|
369
|
-
# parse response
|
370
|
-
p = 0
|
371
|
-
res = []
|
372
|
-
rlen = response.length
|
373
|
-
docs.each do |doc|
|
374
|
-
len = response[p, 4].unpack('N*').first;
|
375
|
-
p += 4
|
376
|
-
if p + len > rlen
|
377
|
-
@error = 'incomplete reply'
|
378
|
-
raise SphinxResponseError, @error
|
379
|
-
end
|
380
|
-
res << response[p, len]
|
381
|
-
p += len
|
382
|
-
end
|
383
|
-
return res;
|
384
|
-
end
|
385
|
-
|
386
|
-
# Connect to searchd server.
|
387
|
-
def connect
|
388
|
-
begin
|
389
|
-
sock = TCPSocket.new(@host, @port)
|
390
|
-
rescue
|
391
|
-
@error = "connection to #{@host}:#{@port} failed"
|
392
|
-
raise SphinxConnectError, @error
|
393
|
-
end
|
394
|
-
|
395
|
-
v = sock.recv(4).unpack('N*').first
|
396
|
-
if v < 1
|
397
|
-
sock.close
|
398
|
-
@error = "expected searchd protocol version 1+, got version '#{v}'"
|
399
|
-
raise SphinxConnectError, @error
|
400
|
-
end
|
401
|
-
|
402
|
-
sock.send([1].pack('N'), 0)
|
403
|
-
sock
|
404
|
-
end
|
405
|
-
private :connect
|
406
|
-
|
407
|
-
# get and check response packet from searchd server
|
408
|
-
def get_response(sock, client_version)
|
409
|
-
header = sock.recv(8)
|
410
|
-
status, ver, len = header.unpack('n2N')
|
411
|
-
response = ''
|
412
|
-
left = len
|
413
|
-
while left > 0 do
|
414
|
-
begin
|
415
|
-
chunk = sock.recv(left)
|
416
|
-
if chunk
|
417
|
-
response << chunk
|
418
|
-
left -= chunk.length
|
419
|
-
end
|
420
|
-
rescue EOFError
|
421
|
-
end
|
422
|
-
end if left
|
423
|
-
sock.close
|
424
|
-
|
425
|
-
# check response
|
426
|
-
read = response.length
|
427
|
-
if not response or read != len
|
428
|
-
@error = len \
|
429
|
-
? "failed to read searchd response (status=#{status}, ver=#{ver}, len=#{len}, read=#{read})" \
|
430
|
-
: "received zero-sized searchd response"
|
431
|
-
raise SphinxResponseError, @error
|
432
|
-
end
|
433
|
-
|
434
|
-
# check status
|
435
|
-
if status == SEARCHD_ERROR
|
436
|
-
@error = "searchd error: " + response[4,].to_s
|
437
|
-
raise SphinxInternalError, @error
|
438
|
-
end
|
439
|
-
|
440
|
-
if status == SEARCHD_RETRY
|
441
|
-
@error = "temporary searchd error: " + response[4,]
|
442
|
-
raise SphinxTemporaryError, @error
|
443
|
-
end
|
444
|
-
|
445
|
-
unless status == SEARCHD_OK
|
446
|
-
@error = "unknown status code '#{status}'"
|
447
|
-
raise SphinxUnknownError, @error
|
448
|
-
end
|
449
|
-
|
450
|
-
# check version
|
451
|
-
if ver < client_version
|
452
|
-
@warning = "searchd command v.%d.%d older than client's v.%d.%d, some options might not work" % \
|
453
|
-
ver >> 8, ver & 0xff, client_ver >> 8, client_ver & 0xff
|
454
|
-
end
|
455
|
-
|
456
|
-
return response
|
457
|
-
end
|
458
|
-
private :get_response
|
459
|
-
|
460
|
-
end
|