riddle 1.4.0 → 1.5.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.
- data/.gitignore +6 -0
- data/.travis.yml +16 -0
- data/Gemfile +6 -0
- data/HISTORY +45 -0
- data/LICENCE +20 -0
- data/README.textile +5 -3
- data/Rakefile +23 -0
- data/lib/riddle.rb +1 -0
- data/lib/riddle/0.9.9/configuration/searchd.rb +10 -8
- data/lib/riddle/auto_version.rb +2 -2
- data/lib/riddle/client.rb +117 -118
- data/lib/riddle/configuration.rb +6 -6
- data/lib/riddle/configuration/distributed_index.rb +16 -16
- data/lib/riddle/configuration/sql_source.rb +5 -5
- data/lib/riddle/controller.rb +28 -25
- data/lib/riddle/query.rb +31 -20
- data/lib/riddle/query/select.rb +69 -8
- data/lib/riddle/version.rb +3 -0
- data/riddle.gemspec +25 -0
- data/spec/fixtures/.gitignore +2 -0
- data/spec/fixtures/data/0.9.9/anchor.bin +0 -0
- data/spec/fixtures/data/0.9.9/any.bin +0 -0
- data/spec/fixtures/data/0.9.9/boolean.bin +0 -0
- data/spec/fixtures/data/0.9.9/comment.bin +0 -0
- data/spec/fixtures/data/0.9.9/distinct.bin +0 -0
- data/spec/fixtures/data/0.9.9/field_weights.bin +0 -0
- data/spec/fixtures/data/0.9.9/filter.bin +0 -0
- data/spec/fixtures/data/0.9.9/filter_array.bin +0 -0
- data/spec/fixtures/data/0.9.9/filter_array_exclude.bin +0 -0
- data/spec/fixtures/data/0.9.9/filter_boolean.bin +0 -0
- data/spec/fixtures/data/0.9.9/filter_floats.bin +0 -0
- data/spec/fixtures/data/0.9.9/filter_floats_exclude.bin +0 -0
- data/spec/fixtures/data/0.9.9/filter_range.bin +0 -0
- data/spec/fixtures/data/0.9.9/filter_range_exclude.bin +0 -0
- data/spec/fixtures/data/0.9.9/group.bin +0 -0
- data/spec/fixtures/data/0.9.9/index.bin +0 -0
- data/spec/fixtures/data/0.9.9/index_weights.bin +0 -0
- data/spec/fixtures/data/0.9.9/keywords_with_hits.bin +0 -0
- data/spec/fixtures/data/0.9.9/keywords_without_hits.bin +0 -0
- data/spec/fixtures/data/0.9.9/overrides.bin +0 -0
- data/spec/fixtures/data/0.9.9/phrase.bin +0 -0
- data/spec/fixtures/data/0.9.9/rank_mode.bin +0 -0
- data/spec/fixtures/data/0.9.9/select.bin +0 -0
- data/spec/fixtures/data/0.9.9/simple.bin +0 -0
- data/spec/fixtures/data/0.9.9/sort.bin +0 -0
- data/spec/fixtures/data/0.9.9/update_simple.bin +0 -0
- data/spec/fixtures/data/0.9.9/weights.bin +0 -0
- data/spec/fixtures/data/1.10/anchor.bin +0 -0
- data/spec/fixtures/data/1.10/any.bin +0 -0
- data/spec/fixtures/data/1.10/boolean.bin +0 -0
- data/spec/fixtures/data/1.10/comment.bin +0 -0
- data/spec/fixtures/data/1.10/distinct.bin +0 -0
- data/spec/fixtures/data/1.10/field_weights.bin +0 -0
- data/spec/fixtures/data/1.10/filter.bin +0 -0
- data/spec/fixtures/data/1.10/filter_array.bin +0 -0
- data/spec/fixtures/data/1.10/filter_array_exclude.bin +0 -0
- data/spec/fixtures/data/1.10/filter_boolean.bin +0 -0
- data/spec/fixtures/data/1.10/filter_floats.bin +0 -0
- data/spec/fixtures/data/1.10/filter_floats_exclude.bin +0 -0
- data/spec/fixtures/data/1.10/filter_range.bin +0 -0
- data/spec/fixtures/data/1.10/filter_range_exclude.bin +0 -0
- data/spec/fixtures/data/1.10/group.bin +0 -0
- data/spec/fixtures/data/1.10/index.bin +0 -0
- data/spec/fixtures/data/1.10/index_weights.bin +0 -0
- data/spec/fixtures/data/1.10/keywords_with_hits.bin +0 -0
- data/spec/fixtures/data/1.10/keywords_without_hits.bin +0 -0
- data/spec/fixtures/data/1.10/overrides.bin +0 -0
- data/spec/fixtures/data/1.10/phrase.bin +0 -0
- data/spec/fixtures/data/1.10/rank_mode.bin +0 -0
- data/spec/fixtures/data/1.10/select.bin +0 -0
- data/spec/fixtures/data/1.10/simple.bin +0 -0
- data/spec/fixtures/data/1.10/sort.bin +0 -0
- data/spec/fixtures/data/1.10/update_simple.bin +0 -0
- data/spec/fixtures/data/1.10/weights.bin +0 -0
- data/spec/fixtures/data/2.0.1/anchor.bin +0 -0
- data/spec/fixtures/data/2.0.1/any.bin +0 -0
- data/spec/fixtures/data/2.0.1/boolean.bin +0 -0
- data/spec/fixtures/data/2.0.1/comment.bin +0 -0
- data/spec/fixtures/data/2.0.1/distinct.bin +0 -0
- data/spec/fixtures/data/2.0.1/field_weights.bin +0 -0
- data/spec/fixtures/data/2.0.1/filter.bin +0 -0
- data/spec/fixtures/data/2.0.1/filter_array.bin +0 -0
- data/spec/fixtures/data/2.0.1/filter_array_exclude.bin +0 -0
- data/spec/fixtures/data/2.0.1/filter_boolean.bin +0 -0
- data/spec/fixtures/data/2.0.1/filter_floats.bin +0 -0
- data/spec/fixtures/data/2.0.1/filter_floats_exclude.bin +0 -0
- data/spec/fixtures/data/2.0.1/filter_range.bin +0 -0
- data/spec/fixtures/data/2.0.1/filter_range_exclude.bin +0 -0
- data/spec/fixtures/data/2.0.1/group.bin +0 -0
- data/spec/fixtures/data/2.0.1/index.bin +0 -0
- data/spec/fixtures/data/2.0.1/index_weights.bin +0 -0
- data/spec/fixtures/data/2.0.1/keywords_with_hits.bin +0 -0
- data/spec/fixtures/data/2.0.1/keywords_without_hits.bin +0 -0
- data/spec/fixtures/data/2.0.1/overrides.bin +0 -0
- data/spec/fixtures/data/2.0.1/phrase.bin +0 -0
- data/spec/fixtures/data/2.0.1/rank_mode.bin +0 -0
- data/spec/fixtures/data/2.0.1/select.bin +0 -0
- data/spec/fixtures/data/2.0.1/simple.bin +0 -0
- data/spec/fixtures/data/2.0.1/sort.bin +0 -0
- data/spec/fixtures/data/2.0.1/update_simple.bin +0 -0
- data/spec/fixtures/data/2.0.1/weights.bin +0 -0
- data/spec/fixtures/data_generator.0.9.8.php +208 -0
- data/spec/fixtures/data_generator.0.9.9.php +5 -0
- data/spec/fixtures/data_generator.1.10.php +5 -0
- data/spec/fixtures/data_generator.2.0.1.php +5 -0
- data/spec/fixtures/data_generator.php +223 -0
- data/spec/fixtures/sphinxapi.0.9.8.php +1228 -0
- data/spec/fixtures/sphinxapi.0.9.9.php +1646 -0
- data/spec/fixtures/sphinxapi.1.10.php +1728 -0
- data/spec/fixtures/sphinxapi.2.0.1.php +1731 -0
- data/spec/fixtures/sql/conf.example.yml +3 -0
- data/spec/fixtures/sql/data.sql +25000 -0
- data/spec/fixtures/sql/data.tsv +25000 -0
- data/spec/fixtures/sql/structure.sql +16 -0
- data/spec/functional/connection_spec.rb +10 -12
- data/spec/functional/excerpt_spec.rb +1 -1
- data/spec/functional/keywords_spec.rb +1 -1
- data/spec/functional/persistance_spec.rb +1 -1
- data/spec/functional/search_spec.rb +1 -1
- data/spec/functional/status_spec.rb +1 -1
- data/spec/functional/update_spec.rb +1 -1
- data/spec/riddle/auto_version_spec.rb +18 -10
- data/spec/riddle/query/select_spec.rb +78 -14
- data/spec/riddle/query_spec.rb +5 -3
- data/spec/spec_helper.rb +13 -15
- data/spec/support/binary_fixtures.rb +18 -0
- data/spec/support/sphinx.rb +135 -0
- data/spec/unit/client_spec.rb +150 -142
- data/spec/unit/configuration/distributed_index_spec.rb +15 -15
- data/spec/unit/configuration/searchd_spec.rb +28 -3
- data/spec/unit/configuration_spec.rb +6 -6
- metadata +254 -68
- data/spec/sphinx_helper.rb +0 -96
data/lib/riddle/configuration.rb
CHANGED
|
@@ -14,20 +14,20 @@ module Riddle
|
|
|
14
14
|
class Configuration
|
|
15
15
|
class ConfigurationError < StandardError #:nodoc:
|
|
16
16
|
end
|
|
17
|
-
|
|
18
|
-
attr_reader :
|
|
17
|
+
|
|
18
|
+
attr_reader :indices, :searchd
|
|
19
19
|
attr_accessor :indexer
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
def initialize
|
|
22
22
|
@indexer = Riddle::Configuration::Indexer.new
|
|
23
23
|
@searchd = Riddle::Configuration::Searchd.new
|
|
24
|
-
@
|
|
24
|
+
@indices = []
|
|
25
25
|
end
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
def render
|
|
28
28
|
(
|
|
29
29
|
[@indexer.render, @searchd.render] +
|
|
30
|
-
@
|
|
30
|
+
@indices.collect { |index| index.render }
|
|
31
31
|
).join("\n")
|
|
32
32
|
end
|
|
33
33
|
end
|
|
@@ -2,51 +2,51 @@ module Riddle
|
|
|
2
2
|
class Configuration
|
|
3
3
|
class DistributedIndex < Riddle::Configuration::Section
|
|
4
4
|
def self.settings
|
|
5
|
-
[
|
|
5
|
+
[
|
|
6
6
|
:type, :local, :agent, :agent_blackhole,
|
|
7
7
|
:agent_connect_timeout, :agent_query_timeout
|
|
8
8
|
]
|
|
9
9
|
end
|
|
10
|
-
|
|
11
|
-
attr_accessor :name, :
|
|
10
|
+
|
|
11
|
+
attr_accessor :name, :local_indices, :remote_indices, :agent_blackhole,
|
|
12
12
|
:agent_connect_timeout, :agent_query_timeout
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
def initialize(name)
|
|
15
15
|
@name = name
|
|
16
|
-
@
|
|
17
|
-
@
|
|
16
|
+
@local_indices = []
|
|
17
|
+
@remote_indices = []
|
|
18
18
|
@agent_blackhole = []
|
|
19
19
|
end
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
def type
|
|
22
22
|
"distributed"
|
|
23
23
|
end
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
def local
|
|
26
|
-
self.
|
|
26
|
+
self.local_indices
|
|
27
27
|
end
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
def agent
|
|
30
|
-
agents =
|
|
30
|
+
agents = remote_indices.collect { |index| index.remote }.uniq
|
|
31
31
|
agents.collect { |agent|
|
|
32
|
-
agent + ":" +
|
|
32
|
+
agent + ":" + remote_indices.select { |index|
|
|
33
33
|
index.remote == agent
|
|
34
34
|
}.collect { |index| index.name }.join(",")
|
|
35
35
|
}
|
|
36
36
|
end
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
def render
|
|
39
39
|
raise ConfigurationError unless valid?
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
(
|
|
42
42
|
["index #{name}", "{"] +
|
|
43
43
|
settings_body +
|
|
44
44
|
["}", ""]
|
|
45
45
|
).join("\n")
|
|
46
46
|
end
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
def valid?
|
|
49
|
-
@
|
|
49
|
+
@local_indices.length > 0 || @remote_indices.length > 0
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
end
|
|
@@ -13,16 +13,16 @@ module Riddle
|
|
|
13
13
|
:sql_column_buffers, :sql_field_string, :sql_field_str2wordcount,
|
|
14
14
|
:sql_query_post, :sql_query_post_index, :sql_ranged_throttle,
|
|
15
15
|
:sql_query_info, :mssql_winauth, :mssql_unicode, :unpack_zlib,
|
|
16
|
-
|
|
16
|
+
:unpack_mysqlcompress, :unpack_mysqlcompress_maxsize
|
|
17
17
|
]
|
|
18
18
|
end
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
attr_accessor *self.settings
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
def initialize(name, type)
|
|
23
23
|
@name = name
|
|
24
24
|
@type = type
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
@sql_query_pre = []
|
|
27
27
|
@sql_joined_field = []
|
|
28
28
|
@sql_file_field = []
|
|
@@ -42,7 +42,7 @@ module Riddle
|
|
|
42
42
|
@unpack_zlib = []
|
|
43
43
|
@unpack_mysqlcompress = []
|
|
44
44
|
end
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
def valid?
|
|
47
47
|
super && (!( @sql_host.nil? || @sql_user.nil? || @sql_db.nil? ||
|
|
48
48
|
@sql_query.nil? ) || !@parent.nil?)
|
data/lib/riddle/controller.rb
CHANGED
|
@@ -1,58 +1,61 @@
|
|
|
1
1
|
module Riddle
|
|
2
2
|
class Controller
|
|
3
3
|
attr_accessor :path, :bin_path, :searchd_binary_name, :indexer_binary_name
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
def initialize(configuration, path)
|
|
6
6
|
@configuration = configuration
|
|
7
7
|
@path = path
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
@bin_path = ''
|
|
10
10
|
@searchd_binary_name = 'searchd'
|
|
11
11
|
@indexer_binary_name = 'indexer'
|
|
12
12
|
end
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
def sphinx_version
|
|
15
15
|
`#{indexer} 2>&1`[/Sphinx (\d+\.\d+(\.\d+|(?:-dev|(\-id64)?\-beta)))/, 1]
|
|
16
16
|
rescue
|
|
17
17
|
nil
|
|
18
18
|
end
|
|
19
|
-
|
|
20
|
-
def index(*
|
|
21
|
-
options =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
cmd = "#{indexer} --config \"#{@path}\" #{
|
|
19
|
+
|
|
20
|
+
def index(*indices)
|
|
21
|
+
options = indices.last.is_a?(Hash) ? indices.pop : {}
|
|
22
|
+
indices << '--all' if indices.empty?
|
|
23
|
+
|
|
24
|
+
cmd = "#{indexer} --config \"#{@path}\" #{indices.join(' ')}"
|
|
25
25
|
cmd << " --rotate" if running?
|
|
26
26
|
options[:verbose] ? system(cmd) : `#{cmd}`
|
|
27
27
|
end
|
|
28
|
-
|
|
29
|
-
def start
|
|
28
|
+
|
|
29
|
+
def start(options={})
|
|
30
30
|
return if running?
|
|
31
31
|
check_for_configuration_file
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
cmd = "#{searchd} --pidfile --config \"#{@path}\""
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
cmd << " --nodetach" if options[:nodetach]
|
|
35
|
+
|
|
36
|
+
if options[:nodetach]
|
|
37
|
+
exec(cmd)
|
|
38
|
+
elsif RUBY_PLATFORM =~ /mswin|mingw/
|
|
36
39
|
system("start /B #{cmd} 1> NUL 2>&1")
|
|
37
40
|
else
|
|
38
41
|
`#{cmd}`
|
|
39
42
|
end
|
|
40
|
-
|
|
43
|
+
|
|
41
44
|
sleep(1)
|
|
42
|
-
|
|
45
|
+
|
|
43
46
|
unless running?
|
|
44
47
|
puts "Failed to start searchd daemon. Check #{@configuration.searchd.log}."
|
|
45
48
|
end
|
|
46
49
|
end
|
|
47
|
-
|
|
50
|
+
|
|
48
51
|
def stop
|
|
49
52
|
return true unless running?
|
|
50
53
|
check_for_configuration_file
|
|
51
|
-
|
|
54
|
+
|
|
52
55
|
stop_flag = 'stopwait'
|
|
53
56
|
stop_flag = 'stop' if Riddle.loaded_version.split('.').first == '0'
|
|
54
57
|
cmd = %(#{searchd} --pidfile --config "#{@path}" --#{stop_flag})
|
|
55
|
-
|
|
58
|
+
|
|
56
59
|
if RUBY_PLATFORM =~ /mswin|mingw/
|
|
57
60
|
system("start /B #{cmd} 1> NUL 2>&1")
|
|
58
61
|
else
|
|
@@ -61,7 +64,7 @@ module Riddle
|
|
|
61
64
|
ensure
|
|
62
65
|
return !running?
|
|
63
66
|
end
|
|
64
|
-
|
|
67
|
+
|
|
65
68
|
def pid
|
|
66
69
|
if File.exists?(@configuration.searchd.pid_file)
|
|
67
70
|
File.read(@configuration.searchd.pid_file)[/\d+/]
|
|
@@ -69,23 +72,23 @@ module Riddle
|
|
|
69
72
|
nil
|
|
70
73
|
end
|
|
71
74
|
end
|
|
72
|
-
|
|
75
|
+
|
|
73
76
|
def running?
|
|
74
77
|
!!pid && !!Process.kill(0, pid.to_i)
|
|
75
78
|
rescue
|
|
76
79
|
false
|
|
77
80
|
end
|
|
78
|
-
|
|
81
|
+
|
|
79
82
|
private
|
|
80
|
-
|
|
83
|
+
|
|
81
84
|
def indexer
|
|
82
85
|
"#{bin_path}#{indexer_binary_name}"
|
|
83
86
|
end
|
|
84
|
-
|
|
87
|
+
|
|
85
88
|
def searchd
|
|
86
89
|
"#{bin_path}#{searchd_binary_name}"
|
|
87
90
|
end
|
|
88
|
-
|
|
91
|
+
|
|
89
92
|
def check_for_configuration_file
|
|
90
93
|
return if File.exist?(@path)
|
|
91
94
|
raise "Configuration file '#{@path}' does not exist"
|
data/lib/riddle/query.rb
CHANGED
|
@@ -1,86 +1,97 @@
|
|
|
1
1
|
module Riddle::Query
|
|
2
2
|
def self.connection(address = '127.0.0.1', port = 9312)
|
|
3
3
|
require 'mysql2'
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
# If you use localhost, MySQL insists on a socket connection, but Sphinx
|
|
6
6
|
# requires a TCP connection. Using 127.0.0.1 fixes that.
|
|
7
7
|
address = '127.0.0.1' if address == 'localhost'
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
Mysql2::Client.new(
|
|
10
10
|
:host => address,
|
|
11
11
|
:port => port
|
|
12
12
|
)
|
|
13
13
|
end
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
def self.meta
|
|
16
16
|
'SHOW META'
|
|
17
17
|
end
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
def self.warnings
|
|
20
20
|
'SHOW WARNINGS'
|
|
21
21
|
end
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
def self.status
|
|
24
24
|
'SHOW STATUS'
|
|
25
25
|
end
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
def self.tables
|
|
28
28
|
'SHOW TABLES'
|
|
29
29
|
end
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
def self.variables
|
|
32
32
|
'SHOW VARIABLES'
|
|
33
33
|
end
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
def self.collation
|
|
36
36
|
'SHOW COLLATION'
|
|
37
37
|
end
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
def self.describe(index)
|
|
40
40
|
"DESCRIBE #{index}"
|
|
41
41
|
end
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
def self.begin
|
|
44
44
|
'BEGIN'
|
|
45
45
|
end
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
def self.commit
|
|
48
48
|
'COMMIT'
|
|
49
49
|
end
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
def self.rollback
|
|
52
52
|
'ROLLBACK'
|
|
53
53
|
end
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
def self.set(variable, values, global = true)
|
|
56
56
|
values = "(#{values.join(', ')})" if values.is_a?(Array)
|
|
57
57
|
"SET#{ ' GLOBAL' if global } #{variable} = #{values}"
|
|
58
58
|
end
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
def self.snippets(data, index, query, options = nil)
|
|
61
61
|
options = ', ' + options.keys.collect { |key|
|
|
62
62
|
"#{options[key]} AS #{key}"
|
|
63
63
|
}.join(', ') unless options.nil?
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
"CALL SNIPPETS('#{data}', '#{index}', '#{query}'#{options})"
|
|
66
66
|
end
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
def self.create_function(name, type, file)
|
|
69
69
|
type = type.to_s.upcase
|
|
70
70
|
"CREATE FUNCTION #{name} RETURNS #{type} SONAME '#{file}'"
|
|
71
71
|
end
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
def self.drop_function(name)
|
|
74
74
|
"DROP FUNCTION #{name}"
|
|
75
75
|
end
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
def self.update(index, id, values = {})
|
|
78
78
|
values = values.keys.collect { |key|
|
|
79
|
-
"#{key} = #{values[key]}"
|
|
79
|
+
"#{key} = #{translate_value values[key]}"
|
|
80
80
|
}.join(', ')
|
|
81
|
-
|
|
81
|
+
|
|
82
82
|
"UPDATE #{index} SET #{values} WHERE id = #{id}"
|
|
83
83
|
end
|
|
84
|
+
|
|
85
|
+
def self.translate_value(value)
|
|
86
|
+
case value
|
|
87
|
+
when TrueClass
|
|
88
|
+
1
|
|
89
|
+
when FalseClass
|
|
90
|
+
0
|
|
91
|
+
else
|
|
92
|
+
value
|
|
93
|
+
end
|
|
94
|
+
end
|
|
84
95
|
end
|
|
85
96
|
|
|
86
97
|
require 'riddle/query/delete'
|
data/lib/riddle/query/select.rb
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
class Riddle::Query::Select
|
|
2
2
|
def initialize
|
|
3
|
+
@values = ['*']
|
|
3
4
|
@indices = []
|
|
4
5
|
@matching = nil
|
|
5
6
|
@wheres = {}
|
|
7
|
+
@where_nots = {}
|
|
6
8
|
@group_by = nil
|
|
7
9
|
@order_by = nil
|
|
8
10
|
@order_within_group_by = nil
|
|
@@ -11,6 +13,11 @@ class Riddle::Query::Select
|
|
|
11
13
|
@options = {}
|
|
12
14
|
end
|
|
13
15
|
|
|
16
|
+
def values(*values)
|
|
17
|
+
@values += values
|
|
18
|
+
self
|
|
19
|
+
end
|
|
20
|
+
|
|
14
21
|
def from(*indices)
|
|
15
22
|
@indices += indices
|
|
16
23
|
self
|
|
@@ -26,6 +33,11 @@ class Riddle::Query::Select
|
|
|
26
33
|
self
|
|
27
34
|
end
|
|
28
35
|
|
|
36
|
+
def where_not(filters = {})
|
|
37
|
+
@where_nots.merge!(filters)
|
|
38
|
+
self
|
|
39
|
+
end
|
|
40
|
+
|
|
29
41
|
def group_by(attribute)
|
|
30
42
|
@group_by = attribute
|
|
31
43
|
self
|
|
@@ -57,7 +69,7 @@ class Riddle::Query::Select
|
|
|
57
69
|
end
|
|
58
70
|
|
|
59
71
|
def to_sql
|
|
60
|
-
sql = "SELECT
|
|
72
|
+
sql = "SELECT #{ @values.join(', ') } FROM #{ @indices.join(', ') }"
|
|
61
73
|
sql << " WHERE #{ combined_wheres }" if wheres?
|
|
62
74
|
sql << " GROUP BY #{@group_by}" if !@group_by.nil?
|
|
63
75
|
sql << " ORDER BY #{@order_by}" if !@order_by.nil?
|
|
@@ -66,20 +78,20 @@ class Riddle::Query::Select
|
|
|
66
78
|
end
|
|
67
79
|
sql << " #{limit_clause}" unless @limit.nil? && @offset.nil?
|
|
68
80
|
sql << " #{options_clause}" unless @options.empty?
|
|
69
|
-
|
|
81
|
+
|
|
70
82
|
sql
|
|
71
83
|
end
|
|
72
84
|
|
|
73
85
|
private
|
|
74
86
|
|
|
75
87
|
def wheres?
|
|
76
|
-
!(@wheres.empty? && @matching.nil?)
|
|
88
|
+
!(@wheres.empty? && @where_nots.empty? && @matching.nil?)
|
|
77
89
|
end
|
|
78
90
|
|
|
79
91
|
def combined_wheres
|
|
80
92
|
if @matching.nil?
|
|
81
93
|
wheres_to_s
|
|
82
|
-
elsif @wheres.empty?
|
|
94
|
+
elsif @wheres.empty? && @where_nots.empty?
|
|
83
95
|
"MATCH('#{@matching}')"
|
|
84
96
|
else
|
|
85
97
|
"MATCH('#{@matching}') AND #{wheres_to_s}"
|
|
@@ -87,9 +99,49 @@ class Riddle::Query::Select
|
|
|
87
99
|
end
|
|
88
100
|
|
|
89
101
|
def wheres_to_s
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
102
|
+
(
|
|
103
|
+
@wheres.keys.collect { |key|
|
|
104
|
+
filter_comparison_and_value key, @wheres[key]
|
|
105
|
+
} +
|
|
106
|
+
@where_nots.keys.collect { |key|
|
|
107
|
+
exclusive_filter_comparison_and_value key, @where_nots[key]
|
|
108
|
+
}
|
|
109
|
+
).join(' AND ')
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def filter_comparison_and_value(attribute, value)
|
|
113
|
+
case value
|
|
114
|
+
when Array
|
|
115
|
+
"#{attribute} IN (#{value.collect { |val| filter_value(val) }.join(', ')})"
|
|
116
|
+
when Range
|
|
117
|
+
"#{attribute} BETWEEN #{filter_value(value.first)} AND #{filter_value(value.last)}"
|
|
118
|
+
else
|
|
119
|
+
"#{attribute} = #{filter_value(value)}"
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def exclusive_filter_comparison_and_value(attribute, value)
|
|
124
|
+
case value
|
|
125
|
+
when Array
|
|
126
|
+
"#{attribute} NOT IN (#{value.collect { |val| filter_value(val) }.join(', ')})"
|
|
127
|
+
when Range
|
|
128
|
+
"#{attribute} < #{filter_value(value.first)} OR #{attribute} > #{filter_value(value.last)}"
|
|
129
|
+
else
|
|
130
|
+
"#{attribute} <> #{filter_value(value)}"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def filter_value(value)
|
|
135
|
+
case value
|
|
136
|
+
when TrueClass
|
|
137
|
+
1
|
|
138
|
+
when FalseClass
|
|
139
|
+
0
|
|
140
|
+
when Time
|
|
141
|
+
value.to_i
|
|
142
|
+
else
|
|
143
|
+
value
|
|
144
|
+
end
|
|
93
145
|
end
|
|
94
146
|
|
|
95
147
|
def limit_clause
|
|
@@ -102,7 +154,16 @@ class Riddle::Query::Select
|
|
|
102
154
|
|
|
103
155
|
def options_clause
|
|
104
156
|
'OPTION ' + @options.keys.collect { |key|
|
|
105
|
-
"#{key}=#{@options[key]}"
|
|
157
|
+
"#{key}=#{option_value @options[key]}"
|
|
106
158
|
}.join(', ')
|
|
107
159
|
end
|
|
160
|
+
|
|
161
|
+
def option_value(value)
|
|
162
|
+
case value
|
|
163
|
+
when Hash
|
|
164
|
+
'(' + value.collect { |key, value| "#{key}=#{value}" }.join(', ') + ')'
|
|
165
|
+
else
|
|
166
|
+
value
|
|
167
|
+
end
|
|
168
|
+
end
|
|
108
169
|
end
|