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.
Files changed (133) hide show
  1. data/.gitignore +6 -0
  2. data/.travis.yml +16 -0
  3. data/Gemfile +6 -0
  4. data/HISTORY +45 -0
  5. data/LICENCE +20 -0
  6. data/README.textile +5 -3
  7. data/Rakefile +23 -0
  8. data/lib/riddle.rb +1 -0
  9. data/lib/riddle/0.9.9/configuration/searchd.rb +10 -8
  10. data/lib/riddle/auto_version.rb +2 -2
  11. data/lib/riddle/client.rb +117 -118
  12. data/lib/riddle/configuration.rb +6 -6
  13. data/lib/riddle/configuration/distributed_index.rb +16 -16
  14. data/lib/riddle/configuration/sql_source.rb +5 -5
  15. data/lib/riddle/controller.rb +28 -25
  16. data/lib/riddle/query.rb +31 -20
  17. data/lib/riddle/query/select.rb +69 -8
  18. data/lib/riddle/version.rb +3 -0
  19. data/riddle.gemspec +25 -0
  20. data/spec/fixtures/.gitignore +2 -0
  21. data/spec/fixtures/data/0.9.9/anchor.bin +0 -0
  22. data/spec/fixtures/data/0.9.9/any.bin +0 -0
  23. data/spec/fixtures/data/0.9.9/boolean.bin +0 -0
  24. data/spec/fixtures/data/0.9.9/comment.bin +0 -0
  25. data/spec/fixtures/data/0.9.9/distinct.bin +0 -0
  26. data/spec/fixtures/data/0.9.9/field_weights.bin +0 -0
  27. data/spec/fixtures/data/0.9.9/filter.bin +0 -0
  28. data/spec/fixtures/data/0.9.9/filter_array.bin +0 -0
  29. data/spec/fixtures/data/0.9.9/filter_array_exclude.bin +0 -0
  30. data/spec/fixtures/data/0.9.9/filter_boolean.bin +0 -0
  31. data/spec/fixtures/data/0.9.9/filter_floats.bin +0 -0
  32. data/spec/fixtures/data/0.9.9/filter_floats_exclude.bin +0 -0
  33. data/spec/fixtures/data/0.9.9/filter_range.bin +0 -0
  34. data/spec/fixtures/data/0.9.9/filter_range_exclude.bin +0 -0
  35. data/spec/fixtures/data/0.9.9/group.bin +0 -0
  36. data/spec/fixtures/data/0.9.9/index.bin +0 -0
  37. data/spec/fixtures/data/0.9.9/index_weights.bin +0 -0
  38. data/spec/fixtures/data/0.9.9/keywords_with_hits.bin +0 -0
  39. data/spec/fixtures/data/0.9.9/keywords_without_hits.bin +0 -0
  40. data/spec/fixtures/data/0.9.9/overrides.bin +0 -0
  41. data/spec/fixtures/data/0.9.9/phrase.bin +0 -0
  42. data/spec/fixtures/data/0.9.9/rank_mode.bin +0 -0
  43. data/spec/fixtures/data/0.9.9/select.bin +0 -0
  44. data/spec/fixtures/data/0.9.9/simple.bin +0 -0
  45. data/spec/fixtures/data/0.9.9/sort.bin +0 -0
  46. data/spec/fixtures/data/0.9.9/update_simple.bin +0 -0
  47. data/spec/fixtures/data/0.9.9/weights.bin +0 -0
  48. data/spec/fixtures/data/1.10/anchor.bin +0 -0
  49. data/spec/fixtures/data/1.10/any.bin +0 -0
  50. data/spec/fixtures/data/1.10/boolean.bin +0 -0
  51. data/spec/fixtures/data/1.10/comment.bin +0 -0
  52. data/spec/fixtures/data/1.10/distinct.bin +0 -0
  53. data/spec/fixtures/data/1.10/field_weights.bin +0 -0
  54. data/spec/fixtures/data/1.10/filter.bin +0 -0
  55. data/spec/fixtures/data/1.10/filter_array.bin +0 -0
  56. data/spec/fixtures/data/1.10/filter_array_exclude.bin +0 -0
  57. data/spec/fixtures/data/1.10/filter_boolean.bin +0 -0
  58. data/spec/fixtures/data/1.10/filter_floats.bin +0 -0
  59. data/spec/fixtures/data/1.10/filter_floats_exclude.bin +0 -0
  60. data/spec/fixtures/data/1.10/filter_range.bin +0 -0
  61. data/spec/fixtures/data/1.10/filter_range_exclude.bin +0 -0
  62. data/spec/fixtures/data/1.10/group.bin +0 -0
  63. data/spec/fixtures/data/1.10/index.bin +0 -0
  64. data/spec/fixtures/data/1.10/index_weights.bin +0 -0
  65. data/spec/fixtures/data/1.10/keywords_with_hits.bin +0 -0
  66. data/spec/fixtures/data/1.10/keywords_without_hits.bin +0 -0
  67. data/spec/fixtures/data/1.10/overrides.bin +0 -0
  68. data/spec/fixtures/data/1.10/phrase.bin +0 -0
  69. data/spec/fixtures/data/1.10/rank_mode.bin +0 -0
  70. data/spec/fixtures/data/1.10/select.bin +0 -0
  71. data/spec/fixtures/data/1.10/simple.bin +0 -0
  72. data/spec/fixtures/data/1.10/sort.bin +0 -0
  73. data/spec/fixtures/data/1.10/update_simple.bin +0 -0
  74. data/spec/fixtures/data/1.10/weights.bin +0 -0
  75. data/spec/fixtures/data/2.0.1/anchor.bin +0 -0
  76. data/spec/fixtures/data/2.0.1/any.bin +0 -0
  77. data/spec/fixtures/data/2.0.1/boolean.bin +0 -0
  78. data/spec/fixtures/data/2.0.1/comment.bin +0 -0
  79. data/spec/fixtures/data/2.0.1/distinct.bin +0 -0
  80. data/spec/fixtures/data/2.0.1/field_weights.bin +0 -0
  81. data/spec/fixtures/data/2.0.1/filter.bin +0 -0
  82. data/spec/fixtures/data/2.0.1/filter_array.bin +0 -0
  83. data/spec/fixtures/data/2.0.1/filter_array_exclude.bin +0 -0
  84. data/spec/fixtures/data/2.0.1/filter_boolean.bin +0 -0
  85. data/spec/fixtures/data/2.0.1/filter_floats.bin +0 -0
  86. data/spec/fixtures/data/2.0.1/filter_floats_exclude.bin +0 -0
  87. data/spec/fixtures/data/2.0.1/filter_range.bin +0 -0
  88. data/spec/fixtures/data/2.0.1/filter_range_exclude.bin +0 -0
  89. data/spec/fixtures/data/2.0.1/group.bin +0 -0
  90. data/spec/fixtures/data/2.0.1/index.bin +0 -0
  91. data/spec/fixtures/data/2.0.1/index_weights.bin +0 -0
  92. data/spec/fixtures/data/2.0.1/keywords_with_hits.bin +0 -0
  93. data/spec/fixtures/data/2.0.1/keywords_without_hits.bin +0 -0
  94. data/spec/fixtures/data/2.0.1/overrides.bin +0 -0
  95. data/spec/fixtures/data/2.0.1/phrase.bin +0 -0
  96. data/spec/fixtures/data/2.0.1/rank_mode.bin +0 -0
  97. data/spec/fixtures/data/2.0.1/select.bin +0 -0
  98. data/spec/fixtures/data/2.0.1/simple.bin +0 -0
  99. data/spec/fixtures/data/2.0.1/sort.bin +0 -0
  100. data/spec/fixtures/data/2.0.1/update_simple.bin +0 -0
  101. data/spec/fixtures/data/2.0.1/weights.bin +0 -0
  102. data/spec/fixtures/data_generator.0.9.8.php +208 -0
  103. data/spec/fixtures/data_generator.0.9.9.php +5 -0
  104. data/spec/fixtures/data_generator.1.10.php +5 -0
  105. data/spec/fixtures/data_generator.2.0.1.php +5 -0
  106. data/spec/fixtures/data_generator.php +223 -0
  107. data/spec/fixtures/sphinxapi.0.9.8.php +1228 -0
  108. data/spec/fixtures/sphinxapi.0.9.9.php +1646 -0
  109. data/spec/fixtures/sphinxapi.1.10.php +1728 -0
  110. data/spec/fixtures/sphinxapi.2.0.1.php +1731 -0
  111. data/spec/fixtures/sql/conf.example.yml +3 -0
  112. data/spec/fixtures/sql/data.sql +25000 -0
  113. data/spec/fixtures/sql/data.tsv +25000 -0
  114. data/spec/fixtures/sql/structure.sql +16 -0
  115. data/spec/functional/connection_spec.rb +10 -12
  116. data/spec/functional/excerpt_spec.rb +1 -1
  117. data/spec/functional/keywords_spec.rb +1 -1
  118. data/spec/functional/persistance_spec.rb +1 -1
  119. data/spec/functional/search_spec.rb +1 -1
  120. data/spec/functional/status_spec.rb +1 -1
  121. data/spec/functional/update_spec.rb +1 -1
  122. data/spec/riddle/auto_version_spec.rb +18 -10
  123. data/spec/riddle/query/select_spec.rb +78 -14
  124. data/spec/riddle/query_spec.rb +5 -3
  125. data/spec/spec_helper.rb +13 -15
  126. data/spec/support/binary_fixtures.rb +18 -0
  127. data/spec/support/sphinx.rb +135 -0
  128. data/spec/unit/client_spec.rb +150 -142
  129. data/spec/unit/configuration/distributed_index_spec.rb +15 -15
  130. data/spec/unit/configuration/searchd_spec.rb +28 -3
  131. data/spec/unit/configuration_spec.rb +6 -6
  132. metadata +254 -68
  133. data/spec/sphinx_helper.rb +0 -96
@@ -14,20 +14,20 @@ module Riddle
14
14
  class Configuration
15
15
  class ConfigurationError < StandardError #:nodoc:
16
16
  end
17
-
18
- attr_reader :indexes, :searchd
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
- @indexes = []
24
+ @indices = []
25
25
  end
26
-
26
+
27
27
  def render
28
28
  (
29
29
  [@indexer.render, @searchd.render] +
30
- @indexes.collect { |index| index.render }
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, :local_indexes, :remote_indexes, :agent_blackhole,
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
- @local_indexes = []
17
- @remote_indexes = []
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.local_indexes
26
+ self.local_indices
27
27
  end
28
-
28
+
29
29
  def agent
30
- agents = remote_indexes.collect { |index| index.remote }.uniq
30
+ agents = remote_indices.collect { |index| index.remote }.uniq
31
31
  agents.collect { |agent|
32
- agent + ":" + remote_indexes.select { |index|
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
- @local_indexes.length > 0 || @remote_indexes.length > 0
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
- :unpack_mysqlcompress, :unpack_mysqlcompress_maxsize
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?)
@@ -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(*indexes)
21
- options = indexes.last.is_a?(Hash) ? indexes.pop : {}
22
- indexes << '--all' if indexes.empty?
23
-
24
- cmd = "#{indexer} --config \"#{@path}\" #{indexes.join(' ')}"
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
- if RUBY_PLATFORM =~ /mswin|mingw/
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"
@@ -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'
@@ -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 * FROM #{ @indices.join(', ') }"
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
- @wheres.keys.collect { |key|
91
- "#{key} = #{@wheres[key]}"
92
- }.join(' AND ')
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