oedipus 0.0.8 → 0.0.9

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