oedipus 0.0.8 → 0.0.9

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.
@@ -85,10 +85,15 @@ static VALUE odp_close(VALUE self) {
85
85
  return Qtrue;
86
86
  }
87
87
 
88
- static VALUE odp_execute(VALUE self, VALUE sql) {
88
+ static VALUE odp_execute(int argc, VALUE * args, VALUE self) {
89
+ VALUE sql;
89
90
  OdpMysql * conn;
90
91
 
91
- Check_Type(sql, T_STRING);
92
+ if (0 == argc) {
93
+ rb_raise(rb_eArgError, "Wrong number of arguments (0 for 1..*)");
94
+ }
95
+
96
+ Check_Type(args[0], T_STRING);
92
97
 
93
98
  Data_Get_Struct(self, OdpMysql, conn);
94
99
 
@@ -96,6 +101,8 @@ static VALUE odp_execute(VALUE self, VALUE sql) {
96
101
  odp_raise(self, "Cannot execute query on a closed connection");
97
102
  }
98
103
 
104
+ sql = odp_replace_bind_values(conn, args[0], &args[1], argc - 1);
105
+
99
106
  if (mysql_query(conn->ptr, RSTRING_PTR(sql))) {
100
107
  odp_raise(self, "Failed to execute statement(s)");
101
108
  }
@@ -103,7 +110,8 @@ static VALUE odp_execute(VALUE self, VALUE sql) {
103
110
  return INT2NUM(mysql_affected_rows(conn->ptr));
104
111
  }
105
112
 
106
- static VALUE odp_query(VALUE self, VALUE sql) {
113
+ static VALUE odp_query(int argc, VALUE * args, VALUE self) {
114
+ VALUE sql;
107
115
  OdpMysql * conn;
108
116
  MYSQL_RES * rs;
109
117
  int status;
@@ -116,7 +124,11 @@ static VALUE odp_query(VALUE self, VALUE sql) {
116
124
  VALUE hash;
117
125
  VALUE results;
118
126
 
119
- Check_Type(sql, T_STRING);
127
+ if (0 == argc) {
128
+ rb_raise(rb_eArgError, "Wrong number of arguments (0 for 1..*)");
129
+ }
130
+
131
+ Check_Type(args[0], T_STRING);
120
132
 
121
133
  Data_Get_Struct(self, OdpMysql, conn);
122
134
 
@@ -124,6 +136,8 @@ static VALUE odp_query(VALUE self, VALUE sql) {
124
136
  odp_raise(self, "Cannot execute query on a closed connection");
125
137
  }
126
138
 
139
+ sql = odp_replace_bind_values(conn, args[0], &args[1], argc - 1);
140
+
127
141
  if (mysql_query(conn->ptr, RSTRING_PTR(sql))) {
128
142
  odp_raise(self, "Failed to execute statement(s)");
129
143
  }
@@ -175,6 +189,50 @@ static void odp_free(OdpMysql * conn) {
175
189
  free(conn);
176
190
  }
177
191
 
192
+ static VALUE odp_replace_bind_values(OdpMysql * conn, VALUE sql, VALUE * bind_values, int num_values) {
193
+ int i;
194
+ VALUE v;
195
+ VALUE q;
196
+ VALUE idx;
197
+
198
+ q = rb_str_new("?", 1);
199
+ sql = rb_funcall(sql, rb_intern("dup"), 0);
200
+
201
+ // FIXME: Do a real lexical scan of the string, to avoid replacing '?' inside comments/strings
202
+
203
+ for (i = 0; i < num_values; ++i) {
204
+ if ((idx = rb_funcall(sql, rb_intern("index"), 1, q)) == Qnil) {
205
+ break;
206
+ }
207
+
208
+ v = bind_values[i];
209
+
210
+ if (ODP_KIND_OF_P(v, rb_cInteger)) {
211
+ ODP_STR_SUB(sql, idx, ODP_TO_S(v));
212
+ } else if (ODP_KIND_OF_P(v, rb_cNumeric)) {
213
+ ODP_STR_SUB(sql, idx, ODP_TO_S(ODP_TO_F(v)));
214
+ } else {
215
+ if (T_STRING != TYPE(v)) {
216
+ v = ODP_TO_S(v);
217
+ }
218
+
219
+ {
220
+ char * v_ptr = RSTRING_PTR(v);
221
+ unsigned long v_len = strlen(v_ptr);
222
+
223
+ char escaped_str [v_len * 2 + 1];
224
+ char quoted_str [v_len * 2 + 3];
225
+
226
+ mysql_real_escape_string(conn->ptr, escaped_str, v_ptr, v_len);
227
+ sprintf(quoted_str, "'%s'", escaped_str);
228
+ ODP_STR_SUB(sql, idx, rb_str_new2(quoted_str));
229
+ }
230
+ }
231
+ }
232
+
233
+ return sql;
234
+ }
235
+
178
236
  static VALUE odp_cast_value(MYSQL_FIELD f, char * v, unsigned long len) {
179
237
  short s;
180
238
  int i;
@@ -225,16 +283,19 @@ static VALUE odp_cast_value(MYSQL_FIELD f, char * v, unsigned long len) {
225
283
  /* -- Extension initialization -- */
226
284
 
227
285
  void Init_oedipus(void) {
286
+ VALUE mOedipus;
287
+ VALUE cMysql;
288
+
228
289
  rb_require("bigdecimal");
229
290
 
230
- VALUE mOedipus = rb_define_module("Oedipus");
231
- VALUE cMysql = rb_define_class_under(mOedipus, "Mysql", rb_cObject);
291
+ mOedipus = rb_define_module("Oedipus");
292
+ cMysql = rb_define_class_under(mOedipus, "Mysql", rb_cObject);
232
293
 
233
294
  rb_define_method(cMysql, "initialize", odp_initialize, 2);
234
295
  rb_define_method(cMysql, "open", odp_open, 0);
235
296
  rb_define_method(cMysql, "close", odp_close, 0);
236
- rb_define_method(cMysql, "execute", odp_execute, 1);
237
- rb_define_method(cMysql, "query", odp_query, 1);
297
+ rb_define_method(cMysql, "execute", odp_execute, -1);
298
+ rb_define_method(cMysql, "query", odp_query, -1);
238
299
 
239
300
  rb_define_singleton_method(cMysql, "new", odp_new, 2);
240
301
  }
@@ -10,6 +10,12 @@
10
10
  #include <ruby.h>
11
11
  #include <mysql.h>
12
12
 
13
+ // Macros for lazy fingers
14
+ #define ODP_TO_S(v) rb_funcall(v, rb_intern("to_s"), 0)
15
+ #define ODP_TO_F(n) rb_funcall(n, rb_intern("to_f"), 0)
16
+ #define ODP_KIND_OF_P(v, type) (rb_funcall(v, rb_intern("kind_of?"), 1, type) == Qtrue)
17
+ #define ODP_STR_SUB(s, pos, replace) rb_funcall(s, rb_intern("[]="), 2, pos, replace)
18
+
13
19
  /*! Internal struct used to reference a mysql connection */
14
20
  typedef struct {
15
21
  /*! Boolean representing the connected state */
@@ -33,13 +39,10 @@ static VALUE odp_open(VALUE self);
33
39
  static VALUE odp_close(VALUE self);
34
40
 
35
41
  /*! Execute an SQL non-read query and return the number of rows affected */
36
- static VALUE odp_execute(VALUE self, VALUE sql);
42
+ static VALUE odp_execute(int argc, VALUE * args, VALUE self);
37
43
 
38
44
  /*! Execute several SQL read queries and return the result sets */
39
- static VALUE odp_query(VALUE self, VALUE sql);
40
-
41
- /*! Cast the given field to a ruby data type */
42
- static VALUE odp_cast_value(MYSQL_FIELD f, char * v, unsigned long len);
45
+ static VALUE odp_query(int argc, VALUE * args, VALUE self);
43
46
 
44
47
  /* -- Internal methods -- */
45
48
 
@@ -48,3 +51,9 @@ static void odp_raise(VALUE self, const char *msg);
48
51
 
49
52
  /*! Free memory allocated to mysql */
50
53
  static void odp_free(OdpMysql * conn);
54
+
55
+ /*! Substitute all ? markers with the values in bind_values */
56
+ static VALUE odp_replace_bind_values(OdpMysql * conn, VALUE sql, VALUE * bind_values, int num_values);
57
+
58
+ /*! Cast the given field to a ruby data type */
59
+ static VALUE odp_cast_value(MYSQL_FIELD f, char * v, unsigned long len);
@@ -10,13 +10,8 @@
10
10
  module Oedipus
11
11
  # Between comparison of range.
12
12
  class Comparison::Between < Comparison
13
- def to_s
14
- [
15
- "BETWEEN",
16
- Connection.quote(v.first),
17
- "AND",
18
- Connection.quote(v.exclude_end? ? v.end - 1 : v.end)
19
- ].join(" ")
13
+ def to_sql
14
+ ["BETWEEN ? AND ?", v.first, v.exclude_end? ? v.end - 1 : v.end]
20
15
  end
21
16
 
22
17
  def inverse
@@ -10,8 +10,8 @@
10
10
  module Oedipus
11
11
  # Equality comparison of value.
12
12
  class Comparison::Equal < Comparison
13
- def to_s
14
- "= #{Connection.quote(v)}"
13
+ def to_sql
14
+ ["= ?", v]
15
15
  end
16
16
 
17
17
  def inverse
@@ -10,8 +10,8 @@
10
10
  module Oedipus
11
11
  # Greater than comparison of +v+.
12
12
  class Comparison::GT < Comparison
13
- def to_s
14
- "> #{Connection.quote(v)}"
13
+ def to_sql
14
+ ["> ?", v]
15
15
  end
16
16
 
17
17
  def inverse
@@ -10,8 +10,8 @@
10
10
  module Oedipus
11
11
  # Greater than or equal comparison of +v+.
12
12
  class Comparison::GTE < Comparison
13
- def to_s
14
- ">= #{Connection.quote(v)}"
13
+ def to_sql
14
+ [">= ?", v]
15
15
  end
16
16
 
17
17
  def inverse
@@ -10,8 +10,8 @@
10
10
  module Oedipus
11
11
  # IN comparison of +v+.
12
12
  class Comparison::In < Comparison
13
- def to_s
14
- "IN (#{v.map { |o| Connection.quote(o)}.join(', ')})"
13
+ def to_sql
14
+ ["IN (#{v.map{'?'}.join(', ')})", *v]
15
15
  end
16
16
 
17
17
  def inverse
@@ -10,8 +10,8 @@
10
10
  module Oedipus
11
11
  # Less than comparison of +v+.
12
12
  class Comparison::LT < Comparison
13
- def to_s
14
- "< #{Connection.quote(v)}"
13
+ def to_sql
14
+ ["< ?", v]
15
15
  end
16
16
 
17
17
  def inverse
@@ -10,8 +10,8 @@
10
10
  module Oedipus
11
11
  # Less than or equal comparison of +v+.
12
12
  class Comparison::LTE < Comparison
13
- def to_s
14
- "<= #{Connection.quote(v)}"
13
+ def to_sql
14
+ ["<= ?", v]
15
15
  end
16
16
 
17
17
  def inverse
@@ -14,8 +14,8 @@ module Oedipus
14
14
  super(Comparison.of(v))
15
15
  end
16
16
 
17
- def to_s
18
- v.inverse.to_s
17
+ def to_sql
18
+ v.inverse.to_sql
19
19
  end
20
20
 
21
21
  def inverse
@@ -10,8 +10,8 @@
10
10
  module Oedipus
11
11
  # Negation comparison of value.
12
12
  class Comparison::NotEqual < Comparison
13
- def to_s
14
- "!= #{Connection.quote(v)}"
13
+ def to_sql
14
+ ["!= ?", v]
15
15
  end
16
16
 
17
17
  def inverse
@@ -10,8 +10,8 @@
10
10
  module Oedipus
11
11
  # NOT IN comparison of +v+.
12
12
  class Comparison::NotIn < Comparison
13
- def to_s
14
- "NOT IN (#{v.map { |o| Connection.quote(o)}.join(', ')})"
13
+ def to_sql
14
+ ["NOT IN (#{v.map{'?'}.join(', ')})", *v]
15
15
  end
16
16
 
17
17
  def inverse
@@ -10,13 +10,8 @@
10
10
  module Oedipus
11
11
  # Outside comparison of range.
12
12
  class Comparison::Outside < Comparison
13
- def to_s
14
- [
15
- "NOT BETWEEN",
16
- Connection.quote(v.first),
17
- "AND",
18
- Connection.quote(v.exclude_end? ? v.end - 1 : v.end)
19
- ].join(" ")
13
+ def to_sql
14
+ ["NOT BETWEEN ? AND ?", v.first, v.exclude_end? ? v.end - 1 : v.end]
20
15
  end
21
16
 
22
17
  def inverse
@@ -77,12 +77,12 @@ module Oedipus
77
77
  raise NotImplementedError, "Comparison#inverse must be defined by subclasses"
78
78
  end
79
79
 
80
- # Represent the comparison as a string.
80
+ # Represent the comparison as SQL arguments.
81
81
  #
82
- # @return [String]
83
- # an expression to compare a LHS against v
84
- def to_s
85
- raise NotImplementedError, "Comparison#to_s must be defined by subclasses"
82
+ # @return [Array]
83
+ # an SQL expression to compare a LHS against v
84
+ def to_sql
85
+ raise NotImplementedError, "Comparison#to_sql must be defined by subclasses"
86
86
  end
87
87
  end
88
88
  end
@@ -12,48 +12,6 @@ module Oedipus
12
12
  #
13
13
  # Currently this class wraps a native mysql extension.
14
14
  class Connection
15
- class << self
16
- # Quote a value (of any type) for use in SphinxQL.
17
- #
18
- # @param [Object] v
19
- # the value to quote
20
- #
21
- # @return [Object]
22
- # the safe value
23
- #
24
- # Note that single quotes are added to strings.
25
- def quote(v)
26
- require "bigdecimal" unless defined? BigDecimal
27
- case v
28
- when BigDecimal, Rational, Complex
29
- v.to_f
30
- when Numeric
31
- v
32
- else
33
- "'#{escape_str(v.to_s)}'"
34
- end
35
- end
36
-
37
- # Escape a string, without adding enclosing quotes.
38
- #
39
- # @param [String] str
40
- # the unsafe input string
41
- #
42
- # @return [String]
43
- # a safe string for use in SphinxQL
44
- def escape_str(str)
45
- str.gsub(/[\0\n\r\\\'\"\x1a]/) do |s|
46
- case s
47
- when "\0" then "\\0"
48
- when "\n" then "\\n"
49
- when "\r" then "\\r"
50
- when "\x1a" then "\\Z"
51
- else "\\#{s}"
52
- end
53
- end
54
- end
55
- end
56
-
57
15
  # Instantiate a new Connection to a SphinxQL host.
58
16
  #
59
17
  # @param [String] server
@@ -99,12 +57,15 @@ module Oedipus
99
57
  # @param [String] sql
100
58
  # one or more SphinxQL statements, separated by semicolons
101
59
  #
60
+ # @param [Object...] bind_values
61
+ # values to be substituted in place of '?' in the query
62
+ #
102
63
  # @return [Array]
103
64
  # an array of arrays, containing the returned records
104
65
  #
105
66
  # Note that SphinxQL does not support prepared statements.
106
- def multi_query(sql)
107
- @pool.acquire { |conn| conn.query(sql) }
67
+ def multi_query(sql, *bind_values)
68
+ @pool.acquire { |conn| conn.query(sql, *bind_values) }
108
69
  end
109
70
 
110
71
  # Execute a single read query.
@@ -112,12 +73,15 @@ module Oedipus
112
73
  # @param [String] sql
113
74
  # a single SphinxQL statement
114
75
  #
76
+ # @param [Object...] bind_values
77
+ # values to be substituted in place of '?' in the query
78
+ #
115
79
  # @return [Array]
116
80
  # an array of Hashes containing the matched records
117
81
  #
118
82
  # Note that SphinxQL does not support prepared statements.
119
- def query(sql)
120
- @pool.acquire { |conn| conn.query(sql).first }
83
+ def query(sql, *bind_values)
84
+ @pool.acquire { |conn| conn.query(sql, *bind_values).first }
121
85
  end
122
86
 
123
87
  # Execute a non-read query.
@@ -125,12 +89,15 @@ module Oedipus
125
89
  # @param [String] sql
126
90
  # a SphinxQL query, such as INSERT or REPLACE
127
91
  #
92
+ # @param [Object...] bind_values
93
+ # values to be substituted in place of '?' in the query
94
+ #
128
95
  # @return [Fixnum]
129
96
  # the number of affected rows
130
97
  #
131
98
  # Note that SphinxQL does not support prepared statements.
132
- def execute(sql)
133
- @pool.acquire { |conn| conn.execute(sql) }
99
+ def execute(sql, *bind_values)
100
+ @pool.acquire { |conn| conn.execute(sql, *bind_values) }
134
101
  end
135
102
 
136
103
  private
data/lib/oedipus/index.rb CHANGED
@@ -39,7 +39,7 @@ module Oedipus
39
39
  # @return [Fixnum]
40
40
  # the number of rows inserted (currently always 1)
41
41
  def insert(id, hash)
42
- @conn.execute(@builder.insert(id, hash))
42
+ @conn.execute(*@builder.insert(id, hash))
43
43
  end
44
44
 
45
45
  # Update the record with the ID +id+.
@@ -56,7 +56,7 @@ module Oedipus
56
56
  # @return [Fixnum]
57
57
  # the number of rows updated (1 or 0)
58
58
  def update(id, hash)
59
- @conn.execute(@builder.update(id, hash))
59
+ @conn.execute(*@builder.update(id, hash))
60
60
  end
61
61
 
62
62
  # Completely replace the record with the ID +id+.
@@ -73,7 +73,7 @@ module Oedipus
73
73
  # @return [Fixnum]
74
74
  # the number of rows inserted (currentl always 1)
75
75
  def replace(id, hash)
76
- @conn.execute(@builder.replace(id, hash))
76
+ @conn.execute(*@builder.replace(id, hash))
77
77
  end
78
78
 
79
79
  # Delete the record with the ID +id+.
@@ -87,7 +87,7 @@ module Oedipus
87
87
  # @return [Fixnum]
88
88
  # the number of rows deleted (currently always 1 or 0)
89
89
  def delete(id)
90
- @conn.execute(@builder.delete(id))
90
+ @conn.execute(*@builder.delete(id))
91
91
  end
92
92
 
93
93
  # Fetch a single document by its ID.
@@ -220,11 +220,16 @@ module Oedipus
220
220
  raise ArgumentError, "Argument must be a Hash of named queries (#{queries.class} given)"
221
221
  end
222
222
 
223
- rs = @conn.multi_query(
224
- queries.map { |key, args|
225
- [@builder.select(*extract_query_data(args)), "SHOW META"]
226
- }.flatten.join(";\n")
227
- )
223
+ stmts = []
224
+ bind_values = []
225
+
226
+ queries.each do |key, args|
227
+ str, *values = @builder.select(*extract_query_data(args))
228
+ stmts.push(str, "SHOW META")
229
+ bind_values.push(*values)
230
+ end
231
+
232
+ rs = @conn.multi_query(stmts.join(";\n"), *bind_values)
228
233
 
229
234
  Hash[].tap do |result|
230
235
  queries.keys.each do |key|
@@ -29,12 +29,16 @@ module Oedipus
29
29
  # @return [String]
30
30
  # a SphinxQL query
31
31
  def select(query, filters)
32
+ where, *bind_values = conditions(query, filters)
32
33
  [
33
- from(filters),
34
- conditions(query, filters),
35
- order_by(filters),
36
- limits(filters)
37
- ].join(" ")
34
+ [
35
+ from(filters),
36
+ where,
37
+ order_by(filters),
38
+ limits(filters)
39
+ ].join(" "),
40
+ *bind_values
41
+ ]
38
42
  end
39
43
 
40
44
  # Build a SphinxQL query to insert the record identified by +id+ with the given attributes.
@@ -62,11 +66,15 @@ module Oedipus
62
66
  # @return [String]
63
67
  # the SphinxQL to update the record
64
68
  def update(id, attributes)
69
+ set_attrs, *bind_values = update_attributes(attributes)
65
70
  [
66
- "UPDATE #{@index_name} SET",
67
- update_attributes(attributes),
68
- "WHERE id = #{Connection.quote(id)}"
69
- ].join(" ")
71
+ [
72
+ "UPDATE #{@index_name} SET",
73
+ set_attrs,
74
+ "WHERE id = ?"
75
+ ].join(" "),
76
+ *bind_values.push(id)
77
+ ]
70
78
  end
71
79
 
72
80
  # Build a SphinxQL query to replace the record identified by +id+ with the given attributes.
@@ -91,11 +99,13 @@ module Oedipus
91
99
  # @return [String]
92
100
  # the SphinxQL to delete the record
93
101
  def delete(id)
94
- "DELETE FROM #{@index_name} WHERE id = #{Connection.quote(id)}"
102
+ ["DELETE FROM #{@index_name} WHERE id = ?", id]
95
103
  end
96
104
 
97
105
  private
98
106
 
107
+ RESERVED = [:attrs, :limit, :offset, :order]
108
+
99
109
  def fields(filters)
100
110
  filters.fetch(:attrs, [:*]).dup.tap do |fields|
101
111
  if fields.none? { |a| /\brelevance\n/ === a } && normalize_order(filters).key?(:relevance)
@@ -114,32 +124,45 @@ module Oedipus
114
124
  end
115
125
 
116
126
  def into(type, id, attributes)
127
+ attrs, values = attributes.inject([[:id], [id]]) do |(a, b), (k, v)|
128
+ [a.push(k), b.push(v)]
129
+ end
130
+
117
131
  [
118
- type,
119
- "INTO #{@index_name}",
120
- "(#{([:id] + attributes.keys).join(', ')})",
121
- "VALUES",
122
- "(#{([id] + attributes.values).map { |v| Connection.quote(v) }.join(', ')})"
123
- ].join(" ")
132
+ [
133
+ type,
134
+ "INTO #{@index_name}",
135
+ "(#{attrs.join(', ')})",
136
+ "VALUES",
137
+ "(#{(['?'] * attrs.size).join(', ')})"
138
+ ].join(" "),
139
+ *values
140
+ ]
124
141
  end
125
142
 
126
143
  def conditions(query, filters)
127
- exprs = []
128
- exprs << "MATCH(#{Connection.quote(query)})" unless query.empty?
129
- exprs += attribute_conditions(filters)
130
- "WHERE " << exprs.join(" AND ") if exprs.any?
144
+ sql = []
145
+ sql << ["MATCH(?)", query] unless query.empty?
146
+ sql.push(*attribute_conditions(filters))
147
+
148
+ exprs, bind_values = sql.inject([[], []]) do |(strs, values), v|
149
+ [strs.push(v.shift), values.push(*v)]
150
+ end
151
+
152
+ ["WHERE " << exprs.join(" AND "), *bind_values] if exprs.any?
131
153
  end
132
154
 
133
155
  def attribute_conditions(filters)
134
- filters \
135
- .reject { |k, v| [:attrs, :limit, :offset, :order].include?(k.to_sym) } \
136
- .map { |k, v| "#{k} #{Comparison.of(v)}" }
156
+ filters.reject{ |k, v| RESERVED.include?(k.to_sym) }.map do |k, v|
157
+ Comparison.of(v).to_sql.tap { |c| c[0].insert(0, "#{k} ") }
158
+ end
137
159
  end
138
160
 
139
161
  def update_attributes(attributes)
140
- attributes \
141
- .map { |k, v| "#{k} = #{Connection.quote(v)}" } \
142
- .join(", ")
162
+ [
163
+ attributes.keys.map{ |k| "#{k} = ?" }.join(", "),
164
+ *attributes.values
165
+ ]
143
166
  end
144
167
 
145
168
  def order_by(filters)
@@ -8,5 +8,5 @@
8
8
  ##
9
9
 
10
10
  module Oedipus
11
- VERSION = "0.0.8"
11
+ VERSION = "0.0.9"
12
12
  end
@@ -23,6 +23,8 @@ describe Oedipus::Connection do
23
23
 
24
24
  before(:each) { empty_indexes }
25
25
 
26
+ let(:conn) { Oedipus::Connection.new(searchd_host) }
27
+
26
28
  describe "#initialize" do
27
29
  context "with a hosname:port string" do
28
30
  context "on successful connection" do
@@ -58,10 +60,65 @@ describe Oedipus::Connection do
58
60
  end
59
61
 
60
62
  describe "#[]" do
61
- let(:conn) { Oedipus::Connection.new(searchd_host) }
62
-
63
63
  it "returns an index" do
64
64
  conn[:posts_rt].should be_a_kind_of(Oedipus::Index)
65
65
  end
66
66
  end
67
+
68
+ describe "#query" do
69
+ it "accepts integer bind parameters" do
70
+ conn.query("SELECT * FROM posts_rt WHERE views = ? AND user_id = ?", 1, 7)
71
+ end
72
+
73
+ it "accepts float bind parameters" do
74
+ conn.query("SELECT * FROM posts_rt WHERE views = ? AND user_id = ?", 1.2, 7.2)
75
+ end
76
+
77
+ it "accepts decimal bind parameters" do
78
+ require "bigdecimal"
79
+ conn.query("SELECT * FROM posts_rt WHERE views = ? AND user_id = ?", BigDecimal("1.2"), BigDecimal("7.2"))
80
+ end
81
+
82
+ xit "accepts string bind parameters" do
83
+ conn.query("SELECT * FROM posts_rt WHERE state = ?", "something")
84
+ end
85
+ end
86
+
87
+ describe "#multi_query" do
88
+ it "accepts integer bind parameters" do
89
+ conn.multi_query("SELECT * FROM posts_rt WHERE views = ? AND user_id = ?", 1, 7)
90
+ end
91
+
92
+ it "accepts float bind parameters" do
93
+ conn.multi_query("SELECT * FROM posts_rt WHERE views = ? AND user_id = ?", 1.2, 7.2)
94
+ end
95
+
96
+ it "accepts decimal bind parameters" do
97
+ require "bigdecimal"
98
+ conn.multi_query("SELECT * FROM posts_rt WHERE views = ? AND user_id = ?", BigDecimal("1.2"), BigDecimal("7.2"))
99
+ end
100
+
101
+ xit "accepts string bind parameters" do
102
+ conn.multi_query("SELECT * FROM posts_rt WHERE state = ?", "something")
103
+ end
104
+ end
105
+
106
+ describe "#execute" do
107
+ it "accepts integer bind parameters" do
108
+ conn.execute("REPLACE INTO posts_rt (id, views) VALUES (?, ?)", 1, 7)
109
+ end
110
+
111
+ it "accepts float bind parameters" do
112
+ conn.execute("REPLACE INTO posts_rt (id, views) VALUES (?, ?)", 1, 7.2)
113
+ end
114
+
115
+ it "accepts decimal bind parameters" do
116
+ require "bigdecimal"
117
+ conn.execute("REPLACE INTO posts_rt (id, views) VALUES (?, ?)", 1, BigDecimal("7.2"))
118
+ end
119
+
120
+ it "accepts string bind parameters" do
121
+ conn.execute("REPLACE INTO posts_rt (id, title) VALUES (?, ?)", 1, "an example with `\"this (quoted) string\\'")
122
+ end
123
+ end
67
124
  end
@@ -14,11 +14,11 @@ describe Oedipus::Comparison::Between do
14
14
  let(:comparison) { Oedipus::Comparison::Between.new(42..100) }
15
15
 
16
16
  it "draws as BETWEEN x AND y" do
17
- comparison.to_s.should == "BETWEEN 42 AND 100"
17
+ comparison.to_sql.should == ["BETWEEN ? AND ?", 42, 100]
18
18
  end
19
19
 
20
20
  it "inverses as NOT BETWEEN x AND y" do
21
- comparison.inverse.to_s.should == "NOT BETWEEN 42 AND 100"
21
+ comparison.inverse.to_sql.should == ["NOT BETWEEN ? AND ?", 42, 100]
22
22
  end
23
23
  end
24
24
 
@@ -26,11 +26,11 @@ describe Oedipus::Comparison::Between do
26
26
  let(:comparison) { Oedipus::Comparison::Between.new(42...100) }
27
27
 
28
28
  it "draws as BETWEEN x AND y-1" do
29
- comparison.to_s.should == "BETWEEN 42 AND 99"
29
+ comparison.to_sql.should == ["BETWEEN ? AND ?", 42, 99]
30
30
  end
31
31
 
32
32
  it "inverses as NOT BETWEEN x AND y-1" do
33
- comparison.inverse.to_s.should == "NOT BETWEEN 42 AND 99"
33
+ comparison.inverse.to_sql.should == ["NOT BETWEEN ? AND ?", 42, 99]
34
34
  end
35
35
  end
36
36
  end
@@ -13,10 +13,10 @@ describe Oedipus::Comparison::Equal do
13
13
  let(:comparison) { Oedipus::Comparison::Equal.new('test') }
14
14
 
15
15
  it "draws as = v" do
16
- comparison.to_s.should == "= 'test'"
16
+ comparison.to_sql.should == ["= ?", "test"]
17
17
  end
18
18
 
19
19
  it "inverses as != v" do
20
- comparison.inverse.to_s.should == "!= 'test'"
20
+ comparison.inverse.to_sql.should == ["!= ?", "test"]
21
21
  end
22
22
  end
@@ -13,10 +13,10 @@ describe Oedipus::Comparison::GT do
13
13
  let(:comparison) { Oedipus::Comparison::GT.new(42) }
14
14
 
15
15
  it "draws as > v" do
16
- comparison.to_s.should == "> 42"
16
+ comparison.to_sql.should == ["> ?", 42]
17
17
  end
18
18
 
19
19
  it "inverses as <= v" do
20
- comparison.inverse.to_s.should == "<= 42"
20
+ comparison.inverse.to_sql.should == ["<= ?", 42]
21
21
  end
22
22
  end
@@ -13,10 +13,10 @@ describe Oedipus::Comparison::GTE do
13
13
  let(:comparison) { Oedipus::Comparison::GTE.new(42) }
14
14
 
15
15
  it "draws as >= v" do
16
- comparison.to_s.should == ">= 42"
16
+ comparison.to_sql.should == [">= ?", 42]
17
17
  end
18
18
 
19
19
  it "inverses as < v" do
20
- comparison.inverse.to_s.should == "< 42"
20
+ comparison.inverse.to_sql.should == ["< ?", 42]
21
21
  end
22
22
  end
@@ -13,10 +13,10 @@ describe Oedipus::Comparison::In do
13
13
  let(:comparison) { Oedipus::Comparison::In.new([1, 2, 3]) }
14
14
 
15
15
  it "draws as IN (x, y, z)" do
16
- comparison.to_s.should == "IN (1, 2, 3)"
16
+ comparison.to_sql.should == ["IN (?, ?, ?)", 1, 2, 3]
17
17
  end
18
18
 
19
19
  it "inverses as NOT IN (x, y, z)" do
20
- comparison.inverse.to_s.should == "NOT IN (1, 2, 3)"
20
+ comparison.inverse.to_sql.should == ["NOT IN (?, ?, ?)", 1, 2, 3]
21
21
  end
22
22
  end
@@ -13,10 +13,10 @@ describe Oedipus::Comparison::LT do
13
13
  let(:comparison) { Oedipus::Comparison::LT.new(42) }
14
14
 
15
15
  it "draws as < v" do
16
- comparison.to_s.should == "< 42"
16
+ comparison.to_sql.should == ["< ?", 42]
17
17
  end
18
18
 
19
19
  it "inverses as >= v" do
20
- comparison.inverse.to_s.should == ">= 42"
20
+ comparison.inverse.to_sql.should == [">= ?", 42]
21
21
  end
22
22
  end
@@ -13,10 +13,10 @@ describe Oedipus::Comparison::LTE do
13
13
  let(:comparison) { Oedipus::Comparison::LTE.new(42) }
14
14
 
15
15
  it "draws as <= v" do
16
- comparison.to_s.should == "<= 42"
16
+ comparison.to_sql.should == ["<= ?", 42]
17
17
  end
18
18
 
19
19
  it "inverses as > v" do
20
- comparison.inverse.to_s.should == "> 42"
20
+ comparison.inverse.to_sql.should == ["> ?", 42]
21
21
  end
22
22
  end
@@ -13,10 +13,10 @@ describe Oedipus::Comparison::NotEqual do
13
13
  let(:comparison) { Oedipus::Comparison::NotEqual.new('test') }
14
14
 
15
15
  it "draws as != v" do
16
- comparison.to_s.should == "!= 'test'"
16
+ comparison.to_sql.should == ["!= ?", "test"]
17
17
  end
18
18
 
19
19
  it "inverses as = v" do
20
- comparison.inverse.to_s.should == "= 'test'"
20
+ comparison.inverse.to_sql.should == ["= ?", "test"]
21
21
  end
22
22
  end
@@ -13,10 +13,10 @@ describe Oedipus::Comparison::NotIn do
13
13
  let(:comparison) { Oedipus::Comparison::NotIn.new([1, 2, 3]) }
14
14
 
15
15
  it "draws as NOT IN (x, y, z)" do
16
- comparison.to_s.should == "NOT IN (1, 2, 3)"
16
+ comparison.to_sql.should == ["NOT IN (?, ?, ?)", 1, 2, 3]
17
17
  end
18
18
 
19
19
  it "inverses as IN (x, y, z)" do
20
- comparison.inverse.to_s.should == "IN (1, 2, 3)"
20
+ comparison.inverse.to_sql.should == ["IN (?, ?, ?)", 1, 2, 3]
21
21
  end
22
22
  end
@@ -27,11 +27,11 @@ describe Oedipus::Comparison::In do
27
27
  let(:comparison) { Oedipus::Comparison::Not.new(original) }
28
28
 
29
29
  it "draws as the inverse of the comparison" do
30
- comparison.to_s.should == original.inverse.to_s
30
+ comparison.to_sql.should == original.inverse.to_sql
31
31
  end
32
32
 
33
33
  it "inverses as the original" do
34
- comparison.inverse.to_s == original.to_s
34
+ comparison.inverse.to_sql == original.to_sql
35
35
  end
36
36
  end
37
37
  end
@@ -14,11 +14,11 @@ describe Oedipus::Comparison::Outside do
14
14
  let(:comparison) { Oedipus::Comparison::Outside.new(42..100) }
15
15
 
16
16
  it "draws as NOT BETWEEN x AND y" do
17
- comparison.to_s.should == "NOT BETWEEN 42 AND 100"
17
+ comparison.to_sql.should == ["NOT BETWEEN ? AND ?", 42, 100]
18
18
  end
19
19
 
20
20
  it "inverses as BETWEEN x AND y" do
21
- comparison.inverse.to_s.should == "BETWEEN 42 AND 100"
21
+ comparison.inverse.to_sql.should == ["BETWEEN ? AND ?", 42, 100]
22
22
  end
23
23
  end
24
24
 
@@ -26,11 +26,11 @@ describe Oedipus::Comparison::Outside do
26
26
  let(:comparison) { Oedipus::Comparison::Outside.new(42...100) }
27
27
 
28
28
  it "draws as NOT BETWEEN x AND y-1" do
29
- comparison.to_s.should == "NOT BETWEEN 42 AND 99"
29
+ comparison.to_sql.should == ["NOT BETWEEN ? AND ?", 42, 99]
30
30
  end
31
31
 
32
32
  it "inverses as BETWEEN x AND y-1" do
33
- comparison.inverse.to_s.should == "BETWEEN 42 AND 99"
33
+ comparison.inverse.to_sql.should == ["BETWEEN ? AND ?", 42, 99]
34
34
  end
35
35
  end
36
36
  end
@@ -15,124 +15,157 @@ describe Oedipus::QueryBuilder do
15
15
  describe "#select" do
16
16
  context "with a fulltext search" do
17
17
  it "uses MATCH()" do
18
- builder.select("dogs AND cats", {}).should =~ /SELECT .* FROM posts WHERE MATCH\('dogs AND cats'\)/
18
+ sql = builder.select("dogs AND cats", {})
19
+ sql[0].should =~ /SELECT .* FROM posts WHERE MATCH\(\?\)/
20
+ sql.slice(1..-1).should == ["dogs AND cats"]
19
21
  end
20
22
  end
21
23
 
22
24
  context "without conditions" do
23
25
  it "does not add a where clause" do
24
- builder.select("", {}).should_not =~ /WHERE/
26
+ builder.select("", {})[0].should_not =~ /WHERE/
25
27
  end
26
28
  end
27
29
 
28
30
  context "with equal attribute filters" do
29
31
  it "uses the '=' operator" do
30
- builder.select("dogs", author_id: 7).should =~ /SELECT .* FROM posts WHERE .* author_id = 7/
32
+ sql = builder.select("dogs", author_id: 7)
33
+ sql[0].should =~ /SELECT .* FROM posts WHERE .* author_id = \?/
34
+ sql.slice(1..-1).should == ["dogs", 7]
31
35
  end
32
36
  end
33
37
 
34
38
  context "with not equal attribute filters" do
35
39
  it "uses the '!=' operator" do
36
- builder.select("dogs", author_id: Oedipus.not(7)).should =~ /SELECT .* FROM posts WHERE .* author_id != 7/
40
+ sql = builder.select("dogs", author_id: Oedipus.not(7))
41
+ sql[0].should =~ /SELECT .* FROM posts WHERE .* author_id != \?/
42
+ sql.slice(1..-1).should == ["dogs", 7]
37
43
  end
38
44
  end
39
45
 
40
46
  context "with inclusive range-filtered attribute filters" do
41
47
  it "uses the BETWEEN operator" do
42
- builder.select("cats", views: 10..20).should =~ /SELECT .* FROM posts WHERE .* views BETWEEN 10 AND 20/
48
+ sql = builder.select("cats", views: 10..20)
49
+ sql[0].should =~ /SELECT .* FROM posts WHERE .* views BETWEEN \? AND \?/
50
+ sql.slice(1..-1).should == ["cats", 10, 20]
43
51
  end
44
52
  end
45
53
 
46
54
  context "with exclusive range-filtered attribute filters" do
47
55
  it "uses the BETWEEN operator" do
48
- builder.select("cats", views: 10...20).should =~ /SELECT .* FROM posts WHERE .* views BETWEEN 10 AND 19/
56
+ sql = builder.select("cats", views: 10...20)
57
+ sql[0].should =~ /SELECT .* FROM posts WHERE .* views BETWEEN \? AND \?/
58
+ sql.slice(1..-1).should == ["cats", 10, 19]
49
59
  end
50
60
  end
51
61
 
52
62
  context "with a greater than or equal comparison" do
53
63
  it "uses the >= operator" do
54
- builder.select("cats", views: 50..(1/0.0)).should =~ /SELECT .* FROM posts WHERE .* views >= 50/
64
+ sql = builder.select("cats", views: 50..(1/0.0))
65
+ sql[0].should =~ /SELECT .* FROM posts WHERE .* views >= \?/
66
+ sql.slice(1..-1).should == ["cats", 50]
55
67
  end
56
68
  end
57
69
 
58
70
  context "with a greater than comparison" do
59
71
  it "uses the > operator" do
60
- builder.select("cats", views: 50...(1/0.0)).should =~ /SELECT .* FROM posts WHERE .* views > 50/
72
+ sql = builder.select("cats", views: 50...(1/0.0))
73
+ sql[0].should =~ /SELECT .* FROM posts WHERE .* views > \?/
74
+ sql.slice(1..-1).should == ["cats", 50]
61
75
  end
62
76
  end
63
77
 
64
78
  context "with a less than or equal comparison" do
65
79
  it "uses the <= operator" do
66
- builder.select("cats", views: -(1/0.0)..50).should =~ /SELECT .* FROM posts WHERE .* views <= 50/
80
+ sql = builder.select("cats", views: -(1/0.0)..50)
81
+ sql[0].should =~ /SELECT .* FROM posts WHERE .* views <= \?/
82
+ sql.slice(1..-1).should == ["cats", 50]
67
83
  end
68
84
  end
69
85
 
70
86
  context "with a less than comparison" do
71
87
  it "uses the < operator" do
72
- builder.select("cats", views: -(1/0.0)...50).should =~ /SELECT .* FROM posts WHERE .* views < 50/
88
+ sql = builder.select("cats", views: -(1/0.0)...50)
89
+ sql[0].should =~ /SELECT .* FROM posts WHERE .* views < \?/
90
+ sql.slice(1..-1).should == ["cats", 50]
73
91
  end
74
92
  end
75
93
 
76
94
  context "with a negated range comparison" do
77
95
  it "uses the NOT BETWEEN operator" do
78
- builder.select("cats", views: Oedipus.not(50..100)).should =~ /SELECT .* FROM posts WHERE .* views NOT BETWEEN 50 AND 100/
96
+ sql = builder.select("cats", views: Oedipus.not(50..100))
97
+ sql[0].should =~ /SELECT .* FROM posts WHERE .* views NOT BETWEEN \? AND \?/
98
+ sql.slice(1..-1).should == ["cats", 50, 100]
79
99
  end
80
100
  end
81
101
 
82
102
  context "with an attributed filtered by collection" do
83
103
  it "uses the IN operator" do
84
- builder.select("cats", author_id: [7, 11]).should =~ /SELECT .* FROM posts WHERE .* author_id IN \(7, 11\)/
104
+ sql = builder.select("cats", author_id: [7, 11])
105
+ sql[0].should =~ /SELECT .* FROM posts WHERE .* author_id IN \(\?, \?\)/
106
+ sql.slice(1..-1).should == ["cats", 7, 11]
85
107
  end
86
108
  end
87
109
 
88
110
  context "with an attributed filtered by negated collection" do
89
111
  it "uses the NOT IN operator" do
90
- builder.select("cats", author_id: Oedipus.not([7, 11])).should =~ /SELECT .* FROM posts WHERE .* author_id NOT IN \(7, 11\)/
112
+ sql = builder.select("cats", author_id: Oedipus.not([7, 11]))
113
+ sql[0].should =~ /SELECT .* FROM posts WHERE .* author_id NOT IN \(\?, \?\)/
114
+ sql.slice(1..-1).should == ["cats", 7, 11]
91
115
  end
92
116
  end
93
117
 
94
118
  context "with explicit attributes" do
95
119
  it "puts the attributes in the select clause" do
96
- builder.select("cats", attrs: [:*, "FOO() AS f"]).should =~ /SELECT \*, FOO\(\) AS f FROM posts/
120
+ sql = builder.select("cats", attrs: [:*, "FOO() AS f"])
121
+ sql[0].should =~ /SELECT \*, FOO\(\) AS f FROM posts/
97
122
  end
98
123
  end
99
124
 
100
125
  context "with a limit" do
101
126
  it "applies a LIMIT with an offset of 0" do
102
- builder.select("dogs", limit: 50).should =~ /SELECT .* FROM posts WHERE .* LIMIT 0, 50/
127
+ sql = builder.select("dogs", limit: 50)
128
+ sql[0].should =~ /SELECT .* FROM posts WHERE .* LIMIT 0, 50/
103
129
  end
104
130
 
105
131
  it "is not considered an attribute" do
106
- builder.select("dogs", limit: 50).should_not =~ /limit = 50/
132
+ sql = builder.select("dogs", limit: 50)
133
+ sql.slice(1..-1).should == ["dogs"]
107
134
  end
108
135
  end
109
136
 
110
137
  context "with an offset" do
111
138
  it "applies a LIMIT with an offset" do
112
- builder.select("dogs", limit: 50, offset: 200).should =~ /SELECT .* FROM posts WHERE .* LIMIT 200, 50/
139
+ sql = builder.select("dogs", limit: 50, offset: 200)
140
+ sql[0].should =~ /SELECT .* FROM posts WHERE .* LIMIT 200, 50/
113
141
  end
114
142
 
115
143
  it "is not considered an attribute" do
116
- builder.select("dogs", limit: 50, offset: 200).should_not =~ /offset = 200/
144
+ sql = builder.select("dogs", limit: 50, offset: 200)
145
+ sql.slice(1..-1).should == ["dogs"]
117
146
  end
118
147
  end
119
148
 
120
149
  context "with an order clause" do
121
150
  it "applies an ORDER BY" do
122
- builder.select("cats", order: {views: :desc}).should =~ /SELECT .* FROM posts WHERE .* ORDER BY views DESC/
151
+ sql = builder.select("cats", order: {views: :desc})
152
+ sql[0].should =~ /SELECT .* FROM posts WHERE .* ORDER BY views DESC/
123
153
  end
124
154
 
125
155
  it "defaults to ASC" do
126
- builder.select("cats", order: :views).should =~ /SELECT .* FROM posts WHERE .* ORDER BY views ASC/
156
+ sql = builder.select("cats", order: :views)
157
+ sql[0].should =~ /SELECT .* FROM posts WHERE .* ORDER BY views ASC/
127
158
  end
128
159
 
129
160
  it "supports multiple orders" do
130
- builder.select("cats", order: {views: :asc, author_id: :desc}).should =~ /SELECT .* FROM posts WHERE .* ORDER BY views ASC, author_id DESC/
161
+ sql = builder.select("cats", order: {views: :asc, author_id: :desc})
162
+ sql[0].should =~ /SELECT .* FROM posts WHERE .* ORDER BY views ASC, author_id DESC/
131
163
  end
132
164
 
133
165
  context "by relevance" do
134
166
  it "injects a weight() attribute" do
135
- builder.select("cats", order: {relevance: :desc}).should =~ /SELECT \*, WEIGHT\(\) AS relevance FROM posts WHERE .* ORDER BY relevance DESC/
167
+ sql = builder.select("cats", order: {relevance: :desc})
168
+ sql[0].should =~ /SELECT \*, WEIGHT\(\) AS relevance FROM posts WHERE .* ORDER BY relevance DESC/
136
169
  end
137
170
  end
138
171
  end
@@ -140,29 +173,33 @@ describe Oedipus::QueryBuilder do
140
173
 
141
174
  describe "#insert" do
142
175
  it "includes the ID and the attributes" do
143
- builder.insert(3, title: "example", views: 9).should == "INSERT INTO posts (id, title, views) VALUES (3, 'example', 9)"
176
+ builder.insert(3, title: "example", views: 9).should == ["INSERT INTO posts (id, title, views) VALUES (?, ?, ?)", 3, "example", 9]
144
177
  end
145
178
  end
146
179
 
147
180
  describe "#update" do
148
181
  it "includes the ID in the WHERE clause" do
149
- builder.update(3, title: "example", views: 9).should =~ /UPDATE posts SET .* WHERE id = 3/
182
+ sql = builder.update(3, title: "example", views: 9)
183
+ sql[0].should =~ /UPDATE posts SET .* WHERE id = \?/
184
+ sql.last.should == 3
150
185
  end
151
186
 
152
187
  it "includes the changed attributes" do
153
- builder.update(3, title: "example", views: 9).should =~ /UPDATE posts SET title = 'example', views = 9/
188
+ sql = builder.update(3, title: "example", views: 9)
189
+ sql[0].should =~ /UPDATE posts SET title = \?, views = \?/
190
+ sql.slice(1..-1).should == ["example", 9, 3]
154
191
  end
155
192
  end
156
193
 
157
194
  describe "#replace" do
158
195
  it "includes the ID and the attributes" do
159
- builder.replace(3, title: "example", views: 9).should == "REPLACE INTO posts (id, title, views) VALUES (3, 'example', 9)"
196
+ builder.replace(3, title: "example", views: 9).should == ["REPLACE INTO posts (id, title, views) VALUES (?, ?, ?)", 3, "example", 9]
160
197
  end
161
198
  end
162
199
 
163
200
  describe "#delete" do
164
201
  it "includes the ID" do
165
- builder.delete(3).should == "DELETE FROM posts WHERE id = 3"
202
+ builder.delete(3).should == ["DELETE FROM posts WHERE id = ?", 3]
166
203
  end
167
204
  end
168
205
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oedipus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.0.9
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-04 00:00:00.000000000 Z
12
+ date: 2012-05-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &11159640 !ruby/object:Gem::Requirement
16
+ requirement: &20663420 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *11159640
24
+ version_requirements: *20663420
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rake-compiler
27
- requirement: &11157840 !ruby/object:Gem::Requirement
27
+ requirement: &20662600 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *11157840
35
+ version_requirements: *20662600
36
36
  description: ! "== Sphinx 2 Comes to Ruby\n\nOedipus brings full support for Sphinx
37
37
  2 to Ruby:\n\n - real-time indexes (insert, replace, update, delete)\n - faceted
38
38
  search (variations on a base query)\n - multi-queries (multiple queries executed
@@ -109,7 +109,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
109
109
  version: '0'
110
110
  segments:
111
111
  - 0
112
- hash: 2937819901066625379
112
+ hash: -658671341905469446
113
113
  required_rubygems_version: !ruby/object:Gem::Requirement
114
114
  none: false
115
115
  requirements:
@@ -118,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
118
118
  version: '0'
119
119
  segments:
120
120
  - 0
121
- hash: 2937819901066625379
121
+ hash: -658671341905469446
122
122
  requirements: []
123
123
  rubyforge_project: oedipus
124
124
  rubygems_version: 1.8.11