oedipus 0.0.8 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/ext/oedipus/oedipus.c +69 -8
- data/ext/oedipus/oedipus.h +14 -5
- data/lib/oedipus/comparison/between.rb +2 -7
- data/lib/oedipus/comparison/equal.rb +2 -2
- data/lib/oedipus/comparison/gt.rb +2 -2
- data/lib/oedipus/comparison/gte.rb +2 -2
- data/lib/oedipus/comparison/in.rb +2 -2
- data/lib/oedipus/comparison/lt.rb +2 -2
- data/lib/oedipus/comparison/lte.rb +2 -2
- data/lib/oedipus/comparison/not.rb +2 -2
- data/lib/oedipus/comparison/not_equal.rb +2 -2
- data/lib/oedipus/comparison/not_in.rb +2 -2
- data/lib/oedipus/comparison/outside.rb +2 -7
- data/lib/oedipus/comparison.rb +5 -5
- data/lib/oedipus/connection.rb +15 -48
- data/lib/oedipus/index.rb +14 -9
- data/lib/oedipus/query_builder.rb +49 -26
- data/lib/oedipus/version.rb +1 -1
- data/spec/integration/connection_spec.rb +59 -2
- data/spec/unit/comparison/between_spec.rb +4 -4
- data/spec/unit/comparison/equal_spec.rb +2 -2
- data/spec/unit/comparison/gt_spec.rb +2 -2
- data/spec/unit/comparison/gte_spec.rb +2 -2
- data/spec/unit/comparison/in_spec.rb +2 -2
- data/spec/unit/comparison/lt_spec.rb +2 -2
- data/spec/unit/comparison/lte_spec.rb +2 -2
- data/spec/unit/comparison/not_equal_spec.rb +2 -2
- data/spec/unit/comparison/not_in_spec.rb +2 -2
- data/spec/unit/comparison/not_spec.rb +2 -2
- data/spec/unit/comparison/outside_spec.rb +4 -4
- data/spec/unit/query_builder_spec.rb +64 -27
- metadata +8 -8
data/ext/oedipus/oedipus.c
CHANGED
@@ -85,10 +85,15 @@ static VALUE odp_close(VALUE self) {
|
|
85
85
|
return Qtrue;
|
86
86
|
}
|
87
87
|
|
88
|
-
static VALUE odp_execute(VALUE
|
88
|
+
static VALUE odp_execute(int argc, VALUE * args, VALUE self) {
|
89
|
+
VALUE sql;
|
89
90
|
OdpMysql * conn;
|
90
91
|
|
91
|
-
|
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
|
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
|
-
|
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
|
-
|
231
|
-
|
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,
|
237
|
-
rb_define_method(cMysql, "query", odp_query,
|
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
|
}
|
data/ext/oedipus/oedipus.h
CHANGED
@@ -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
|
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
|
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
|
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,13 +10,8 @@
|
|
10
10
|
module Oedipus
|
11
11
|
# Outside comparison of range.
|
12
12
|
class Comparison::Outside < Comparison
|
13
|
-
def
|
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
|
data/lib/oedipus/comparison.rb
CHANGED
@@ -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
|
80
|
+
# Represent the comparison as SQL arguments.
|
81
81
|
#
|
82
|
-
# @return [
|
83
|
-
# an expression to compare a LHS against v
|
84
|
-
def
|
85
|
-
raise NotImplementedError, "Comparison#
|
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
|
data/lib/oedipus/connection.rb
CHANGED
@@ -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(
|
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(
|
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(
|
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(
|
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
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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 =
|
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
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
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
|
-
.
|
136
|
-
|
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
|
-
|
141
|
-
.map
|
142
|
-
.
|
162
|
+
[
|
163
|
+
attributes.keys.map{ |k| "#{k} = ?" }.join(", "),
|
164
|
+
*attributes.values
|
165
|
+
]
|
143
166
|
end
|
144
167
|
|
145
168
|
def order_by(filters)
|
data/lib/oedipus/version.rb
CHANGED
@@ -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.
|
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.
|
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.
|
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.
|
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.
|
16
|
+
comparison.to_sql.should == ["= ?", "test"]
|
17
17
|
end
|
18
18
|
|
19
19
|
it "inverses as != v" do
|
20
|
-
comparison.inverse.
|
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.
|
16
|
+
comparison.to_sql.should == ["> ?", 42]
|
17
17
|
end
|
18
18
|
|
19
19
|
it "inverses as <= v" do
|
20
|
-
comparison.inverse.
|
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.
|
16
|
+
comparison.to_sql.should == [">= ?", 42]
|
17
17
|
end
|
18
18
|
|
19
19
|
it "inverses as < v" do
|
20
|
-
comparison.inverse.
|
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.
|
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.
|
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.
|
16
|
+
comparison.to_sql.should == ["< ?", 42]
|
17
17
|
end
|
18
18
|
|
19
19
|
it "inverses as >= v" do
|
20
|
-
comparison.inverse.
|
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.
|
16
|
+
comparison.to_sql.should == ["<= ?", 42]
|
17
17
|
end
|
18
18
|
|
19
19
|
it "inverses as > v" do
|
20
|
-
comparison.inverse.
|
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.
|
16
|
+
comparison.to_sql.should == ["!= ?", "test"]
|
17
17
|
end
|
18
18
|
|
19
19
|
it "inverses as = v" do
|
20
|
-
comparison.inverse.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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", {})
|
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)
|
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))
|
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)
|
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)
|
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))
|
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))
|
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)
|
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)
|
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))
|
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])
|
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]))
|
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"])
|
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)
|
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)
|
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)
|
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)
|
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})
|
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)
|
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})
|
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})
|
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,
|
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)
|
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)
|
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,
|
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.
|
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-
|
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: &
|
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: *
|
24
|
+
version_requirements: *20663420
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rake-compiler
|
27
|
-
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: *
|
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:
|
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:
|
121
|
+
hash: -658671341905469446
|
122
122
|
requirements: []
|
123
123
|
rubyforge_project: oedipus
|
124
124
|
rubygems_version: 1.8.11
|