dbldots_oedipus 0.0.16
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +10 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +435 -0
- data/Rakefile +26 -0
- data/ext/oedipus/extconf.rb +72 -0
- data/ext/oedipus/lexing.c +96 -0
- data/ext/oedipus/lexing.h +20 -0
- data/ext/oedipus/oedipus.c +339 -0
- data/ext/oedipus/oedipus.h +58 -0
- data/lib/oedipus.rb +40 -0
- data/lib/oedipus/comparison.rb +88 -0
- data/lib/oedipus/comparison/between.rb +21 -0
- data/lib/oedipus/comparison/equal.rb +21 -0
- data/lib/oedipus/comparison/gt.rb +21 -0
- data/lib/oedipus/comparison/gte.rb +21 -0
- data/lib/oedipus/comparison/in.rb +21 -0
- data/lib/oedipus/comparison/lt.rb +21 -0
- data/lib/oedipus/comparison/lte.rb +21 -0
- data/lib/oedipus/comparison/not.rb +25 -0
- data/lib/oedipus/comparison/not_equal.rb +21 -0
- data/lib/oedipus/comparison/not_in.rb +21 -0
- data/lib/oedipus/comparison/outside.rb +21 -0
- data/lib/oedipus/comparison/shortcuts.rb +144 -0
- data/lib/oedipus/connection.rb +124 -0
- data/lib/oedipus/connection/pool.rb +133 -0
- data/lib/oedipus/connection/registry.rb +56 -0
- data/lib/oedipus/connection_error.rb +14 -0
- data/lib/oedipus/index.rb +320 -0
- data/lib/oedipus/query_builder.rb +185 -0
- data/lib/oedipus/rspec/test_rig.rb +132 -0
- data/lib/oedipus/version.rb +12 -0
- data/oedipus.gemspec +42 -0
- data/spec/data/.gitkeep +0 -0
- data/spec/integration/connection/registry_spec.rb +50 -0
- data/spec/integration/connection_spec.rb +156 -0
- data/spec/integration/index_spec.rb +442 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/unit/comparison/between_spec.rb +36 -0
- data/spec/unit/comparison/equal_spec.rb +22 -0
- data/spec/unit/comparison/gt_spec.rb +22 -0
- data/spec/unit/comparison/gte_spec.rb +22 -0
- data/spec/unit/comparison/in_spec.rb +22 -0
- data/spec/unit/comparison/lt_spec.rb +22 -0
- data/spec/unit/comparison/lte_spec.rb +22 -0
- data/spec/unit/comparison/not_equal_spec.rb +22 -0
- data/spec/unit/comparison/not_in_spec.rb +22 -0
- data/spec/unit/comparison/not_spec.rb +37 -0
- data/spec/unit/comparison/outside_spec.rb +36 -0
- data/spec/unit/comparison/shortcuts_spec.rb +125 -0
- data/spec/unit/comparison_spec.rb +109 -0
- data/spec/unit/query_builder_spec.rb +205 -0
- metadata +164 -0
@@ -0,0 +1,185 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
##
|
4
|
+
# Oedipus Sphinx 2 Search.
|
5
|
+
# Copyright © 2012 Chris Corbyn.
|
6
|
+
#
|
7
|
+
# See LICENSE file for details.
|
8
|
+
##
|
9
|
+
|
10
|
+
module Oedipus
|
11
|
+
# Constructs SphinxQL queries from the internal Hash format.
|
12
|
+
class QueryBuilder
|
13
|
+
# Initialize a new QueryBuilder for +index_name+.
|
14
|
+
#
|
15
|
+
# @param [Symbol] index_name
|
16
|
+
# the name of the index being queried
|
17
|
+
def initialize(index_name)
|
18
|
+
@index_name = index_name
|
19
|
+
end
|
20
|
+
|
21
|
+
# Build a SphinxQL query for the fulltext search +query+ and filters in +filters+.
|
22
|
+
#
|
23
|
+
# @param [String] query
|
24
|
+
# the fulltext query to execute (may be empty)
|
25
|
+
#
|
26
|
+
# @param [Hash] filters
|
27
|
+
# additional attribute filters and other options
|
28
|
+
#
|
29
|
+
# @return [String]
|
30
|
+
# a SphinxQL query
|
31
|
+
def select(query, filters)
|
32
|
+
where, *bind_values = conditions(query, filters)
|
33
|
+
[
|
34
|
+
[
|
35
|
+
from(filters),
|
36
|
+
where,
|
37
|
+
order_by(filters),
|
38
|
+
limits(filters)
|
39
|
+
].join(" "),
|
40
|
+
*bind_values
|
41
|
+
]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Build a SphinxQL query to insert the record identified by +id+ with the given attributes.
|
45
|
+
#
|
46
|
+
# @param [Fixnum] id
|
47
|
+
# the unique ID of the document to insert
|
48
|
+
#
|
49
|
+
# @param [Hash] attributes
|
50
|
+
# a Hash of attributes
|
51
|
+
#
|
52
|
+
# @return [String]
|
53
|
+
# the SphinxQL to insert the record
|
54
|
+
def insert(id, attributes)
|
55
|
+
into("INSERT", id, attributes)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Build a SphinxQL query to update the record identified by +id+ with the given attributes.
|
59
|
+
#
|
60
|
+
# @param [Fixnum] id
|
61
|
+
# the unique ID of the document to update
|
62
|
+
#
|
63
|
+
# @param [Hash] attributes
|
64
|
+
# a Hash of attributes
|
65
|
+
#
|
66
|
+
# @return [String]
|
67
|
+
# the SphinxQL to update the record
|
68
|
+
def update(id, attributes)
|
69
|
+
set_attrs, *bind_values = update_attributes(attributes)
|
70
|
+
[
|
71
|
+
[
|
72
|
+
"UPDATE #{@index_name} SET",
|
73
|
+
set_attrs,
|
74
|
+
"WHERE id = ?"
|
75
|
+
].join(" "),
|
76
|
+
*bind_values.push(id)
|
77
|
+
]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Build a SphinxQL query to replace the record identified by +id+ with the given attributes.
|
81
|
+
#
|
82
|
+
# @param [Fixnum] id
|
83
|
+
# the unique ID of the document to replace
|
84
|
+
#
|
85
|
+
# @param [Hash] attributes
|
86
|
+
# a Hash of attributes
|
87
|
+
#
|
88
|
+
# @return [String]
|
89
|
+
# the SphinxQL to replace the record
|
90
|
+
def replace(id, attributes)
|
91
|
+
into("REPLACE", id, attributes)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Build a SphinxQL query to delete the record identified by +id+.
|
95
|
+
#
|
96
|
+
# @param [Fixnum] id
|
97
|
+
# the unique ID of the document to delete
|
98
|
+
#
|
99
|
+
# @return [String]
|
100
|
+
# the SphinxQL to delete the record
|
101
|
+
def delete(id)
|
102
|
+
["DELETE FROM #{@index_name} WHERE id = ?", id]
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
RESERVED = [:attrs, :limit, :offset, :order]
|
108
|
+
|
109
|
+
def fields(filters)
|
110
|
+
filters.fetch(:attrs, [:*]).dup.tap do |fields|
|
111
|
+
if fields.none? { |a| /\brelevance\n/ === a } && normalize_order(filters).key?(:relevance)
|
112
|
+
fields << "WEIGHT() AS relevance"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def from(filters)
|
118
|
+
[
|
119
|
+
"SELECT",
|
120
|
+
fields(filters).join(", "),
|
121
|
+
"FROM",
|
122
|
+
@index_name
|
123
|
+
].join(" ")
|
124
|
+
end
|
125
|
+
|
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
|
+
|
131
|
+
[
|
132
|
+
[
|
133
|
+
type,
|
134
|
+
"INTO #{@index_name}",
|
135
|
+
"(#{attrs.join(', ')})",
|
136
|
+
"VALUES",
|
137
|
+
"(#{(['?'] * attrs.size).join(', ')})"
|
138
|
+
].join(" "),
|
139
|
+
*values
|
140
|
+
]
|
141
|
+
end
|
142
|
+
|
143
|
+
def conditions(query, filters)
|
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?
|
153
|
+
end
|
154
|
+
|
155
|
+
def attribute_conditions(filters)
|
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
|
159
|
+
end
|
160
|
+
|
161
|
+
def update_attributes(attributes)
|
162
|
+
[
|
163
|
+
attributes.keys.map{ |k| "#{k} = ?" }.join(", "),
|
164
|
+
*attributes.values
|
165
|
+
]
|
166
|
+
end
|
167
|
+
|
168
|
+
def order_by(filters)
|
169
|
+
return unless (order = normalize_order(filters)).any?
|
170
|
+
|
171
|
+
[
|
172
|
+
"ORDER BY",
|
173
|
+
order.map { |k, dir| "#{k} #{dir.to_s.upcase}" }.join(", ")
|
174
|
+
].join(" ")
|
175
|
+
end
|
176
|
+
|
177
|
+
def normalize_order(filters)
|
178
|
+
Hash[Array(filters[:order]).map { |k, v| [k.to_sym, v || :asc] }]
|
179
|
+
end
|
180
|
+
|
181
|
+
def limits(filters)
|
182
|
+
"LIMIT #{filters[:offset].to_i}, #{filters[:limit].to_i}" if filters.key?(:limit)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
##
|
4
|
+
# Oedipus Sphinx 2 Search.
|
5
|
+
# Copyright © 2012 Chris Corbyn.
|
6
|
+
#
|
7
|
+
# See LICENSE file for details.
|
8
|
+
##
|
9
|
+
|
10
|
+
require "rspec/core"
|
11
|
+
require "tmpdir"
|
12
|
+
|
13
|
+
# RSpec shared context that may be included to provide a pre-defined index.
|
14
|
+
shared_context "oedipus posts_rt" do
|
15
|
+
def sphinx_indexes
|
16
|
+
<<-STR
|
17
|
+
index posts_rt
|
18
|
+
{
|
19
|
+
type = rt
|
20
|
+
path = #{data_dir}/posts_rt
|
21
|
+
|
22
|
+
rt_field = title
|
23
|
+
rt_field = body
|
24
|
+
|
25
|
+
rt_attr_uint = user_id
|
26
|
+
rt_attr_uint = views
|
27
|
+
rt_attr_string = state
|
28
|
+
}
|
29
|
+
STR
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# RSpec shared context that may be included to start and stop sphinx between example groups.
|
34
|
+
shared_context "oedipus test rig" do
|
35
|
+
before(:all) do
|
36
|
+
write_sphinx_config
|
37
|
+
start_sphinx
|
38
|
+
end
|
39
|
+
|
40
|
+
after(:all) do
|
41
|
+
stop_sphinx
|
42
|
+
clean_data_dir
|
43
|
+
end
|
44
|
+
|
45
|
+
after(:each) { empty_indexes }
|
46
|
+
|
47
|
+
# It is intended that any or all of the following be overridden in tests.
|
48
|
+
def connection
|
49
|
+
@connection ||= Oedipus::Connection.new(host: "127.0.0.1", port: 9399, verify: false)
|
50
|
+
end
|
51
|
+
|
52
|
+
def data_dir
|
53
|
+
@data_dir ||= Dir.mktmpdir("oedipus")
|
54
|
+
end
|
55
|
+
|
56
|
+
def searchd
|
57
|
+
ENV["SEARCHD"] || "searchd"
|
58
|
+
end
|
59
|
+
|
60
|
+
def sphinx_indexes
|
61
|
+
<<-IDX.strip.gsub(/^ {4}/, "")
|
62
|
+
index test_rt {
|
63
|
+
type = rt
|
64
|
+
path = #{data_dir}/test_rt
|
65
|
+
rt_field = test"
|
66
|
+
}
|
67
|
+
IDX
|
68
|
+
end
|
69
|
+
|
70
|
+
def sphinx_conf
|
71
|
+
<<-CONF.strip.gsub(/^ {4}/, "")
|
72
|
+
##
|
73
|
+
# This file is automatically generated during tests
|
74
|
+
##
|
75
|
+
|
76
|
+
#{sphinx_indexes}
|
77
|
+
|
78
|
+
searchd
|
79
|
+
{
|
80
|
+
compat_sphinxql_magics = 0
|
81
|
+
|
82
|
+
max_matches = 2000
|
83
|
+
pid_file = #{data_dir}/searchd.pid
|
84
|
+
listen = #{connection.options[:host]}:#{connection.options[:port]}:mysql41
|
85
|
+
workers = threads
|
86
|
+
log = #{data_dir}/searchd.log
|
87
|
+
query_log = #{data_dir}/searchd.log
|
88
|
+
binlog_path = #{data_dir}
|
89
|
+
}
|
90
|
+
CONF
|
91
|
+
end
|
92
|
+
|
93
|
+
def write_sphinx_config
|
94
|
+
File.open("#{data_dir}/sphinx.conf", "w") do |f|
|
95
|
+
f << sphinx_conf
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def start_sphinx
|
100
|
+
@pid = Process.spawn(
|
101
|
+
searchd, "-c", "#{data_dir}/sphinx.conf",
|
102
|
+
out: "#{data_dir}/searchd.out",
|
103
|
+
err: "#{data_dir}/searchd.err"
|
104
|
+
)
|
105
|
+
Process.wait(@pid)
|
106
|
+
raise "Couldn't start sphinx" unless $? == 0
|
107
|
+
end
|
108
|
+
|
109
|
+
def stop_sphinx
|
110
|
+
@pid = Process.spawn(
|
111
|
+
searchd, "-c", "#{data_dir}/sphinx.conf", "--stopwait",
|
112
|
+
out: "#{data_dir}/searchd.out",
|
113
|
+
err: "#{data_dir}/searchd.err"
|
114
|
+
)
|
115
|
+
Process.wait(@pid)
|
116
|
+
raise "Couldn't stop sphinx" unless $? == 0
|
117
|
+
end
|
118
|
+
|
119
|
+
def clean_data_dir
|
120
|
+
Dir["#{data_dir}/**/*"].each do |path|
|
121
|
+
File.delete(path)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def empty_indexes
|
126
|
+
connection.query("SHOW TABLES").each do |idx|
|
127
|
+
connection.query("SELECT id FROM #{idx['Index']}").each do |hash|
|
128
|
+
connection.execute("DELETE FROM #{idx['Index']} WHERE id = #{hash['id']}")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
data/oedipus.gemspec
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "oedipus/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "dbldots_oedipus"
|
7
|
+
s.version = Oedipus::VERSION
|
8
|
+
s.authors = ["d11wtq", "dbldots"]
|
9
|
+
s.email = ["dbldots@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/d11wtq/oedipus"
|
11
|
+
s.summary = "Sphinx 2 Search Client for Ruby"
|
12
|
+
s.description = <<-DESC.gsub(/^ {4}/m, "")
|
13
|
+
== Sphinx 2 Comes to Ruby
|
14
|
+
|
15
|
+
Oedipus brings full support for Sphinx 2 to Ruby:
|
16
|
+
|
17
|
+
- real-time indexes (insert, replace, update, delete)
|
18
|
+
- faceted search (variations on a base query)
|
19
|
+
- multi-queries (multiple queries executed in a batch)
|
20
|
+
- full attribute filtering support
|
21
|
+
|
22
|
+
It works with 'stable' versions of Sphinx 2 (>= 2.0.2). All
|
23
|
+
features are implemented entirely through the SphinxQL interface.
|
24
|
+
|
25
|
+
-- dbldots: --
|
26
|
+
this gem release in general shouldn't be used. it fixes an issue
|
27
|
+
on mac os x where multi queries are broken.
|
28
|
+
|
29
|
+
this gem will be deleted as soon as the bug is fixed in the
|
30
|
+
original version.
|
31
|
+
DESC
|
32
|
+
|
33
|
+
s.rubyforge_project = "dbldots_oedipus"
|
34
|
+
|
35
|
+
s.files = `git ls-files`.split("\n")
|
36
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
37
|
+
s.extensions = ["ext/oedipus/extconf.rb"]
|
38
|
+
s.require_paths = ["lib"]
|
39
|
+
|
40
|
+
s.add_development_dependency "rspec"
|
41
|
+
s.add_development_dependency "rake-compiler"
|
42
|
+
end
|
data/spec/data/.gitkeep
ADDED
File without changes
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
##
|
4
|
+
# Oedipus Sphinx 2 Search.
|
5
|
+
# Copyright © 2012 Chris Corbyn.
|
6
|
+
#
|
7
|
+
# See LICENSE file for details.
|
8
|
+
##
|
9
|
+
|
10
|
+
require "spec_helper"
|
11
|
+
require "oedipus/rspec/test_rig"
|
12
|
+
|
13
|
+
describe Oedipus::Connection::Registry do
|
14
|
+
include_context "oedipus test rig"
|
15
|
+
include_context "oedipus posts_rt"
|
16
|
+
|
17
|
+
let(:registry) do
|
18
|
+
Object.new.tap { |o| o.send(:extend, Oedipus::Connection::Registry) }
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#connect" do
|
22
|
+
it "makes a new connection to a SphinxQL host" do
|
23
|
+
registry.connect(connection.options).should be_a_kind_of(Oedipus::Connection)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#connection" do
|
28
|
+
context "without a name" do
|
29
|
+
let(:conn) { registry.connect(connection.options) }
|
30
|
+
|
31
|
+
it "returns an existing connection" do
|
32
|
+
conn.should equal registry.connection
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "with a name" do
|
37
|
+
let(:conn) { registry.connect(connection.options, :bob) }
|
38
|
+
|
39
|
+
it "returns the named connection" do
|
40
|
+
conn.should equal registry.connection(:bob)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "with a bad name" do
|
45
|
+
it "raises an ArgumentError" do
|
46
|
+
expect { registry.connection(:wrong) }.to raise_error(ArgumentError)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
##
|
4
|
+
# Oedipus Sphinx 2 Search.
|
5
|
+
# Copyright © 2012 Chris Corbyn.
|
6
|
+
#
|
7
|
+
# See LICENSE file for details.
|
8
|
+
##
|
9
|
+
|
10
|
+
require "spec_helper"
|
11
|
+
require "oedipus/rspec/test_rig"
|
12
|
+
|
13
|
+
describe Oedipus::Connection do
|
14
|
+
include_context "oedipus test rig"
|
15
|
+
include_context "oedipus posts_rt"
|
16
|
+
|
17
|
+
let(:conn) { Oedipus::Connection.new(connection.options) }
|
18
|
+
|
19
|
+
describe "#initialize" do
|
20
|
+
context "with a hosname:port string" do
|
21
|
+
context "on successful connection" do
|
22
|
+
it "returns the connection" do
|
23
|
+
Oedipus::Connection.new(
|
24
|
+
"#{connection.options[:host]}:#{connection.options[:port]}"
|
25
|
+
).should be_a_kind_of(Oedipus::Connection)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "on failed connection" do
|
30
|
+
it "raises an error" do
|
31
|
+
expect {
|
32
|
+
Oedipus::Connection.new("127.0.0.1:45346138")
|
33
|
+
}.to raise_error(Oedipus::ConnectionError)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "with an options Hash" do
|
39
|
+
context "on successful connection" do
|
40
|
+
it "returns the connection" do
|
41
|
+
Oedipus::Connection.new(connection.options).should be_a_kind_of(Oedipus::Connection)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "on failed connection" do
|
46
|
+
it "raises an error" do
|
47
|
+
expect {
|
48
|
+
Oedipus::Connection.new(:host => "127.0.0.1", :port => 45346138)
|
49
|
+
}.to raise_error(Oedipus::ConnectionError)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "#[]" do
|
56
|
+
it "returns an index" do
|
57
|
+
conn[:posts_rt].should be_a_kind_of(Oedipus::Index)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "#query" do
|
62
|
+
it "accepts integer bind parameters" do
|
63
|
+
conn.query("SELECT * FROM posts_rt WHERE views = ? AND user_id = ?", 1, 7)
|
64
|
+
end
|
65
|
+
|
66
|
+
xit "accepts float bind parameters" do
|
67
|
+
conn.query("SELECT * FROM posts_rt WHERE views = ? AND user_id = ?", 1.2, 7.2)
|
68
|
+
end
|
69
|
+
|
70
|
+
xit "accepts decimal bind parameters" do
|
71
|
+
require "bigdecimal"
|
72
|
+
conn.query("SELECT * FROM posts_rt WHERE views = ? AND user_id = ?", BigDecimal("1.2"), BigDecimal("7.2"))
|
73
|
+
end
|
74
|
+
|
75
|
+
xit "accepts string bind parameters" do
|
76
|
+
conn.query("SELECT * FROM posts_rt WHERE state = ?", "something")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "#multi_query" do
|
81
|
+
it "accepts integer bind parameters" do
|
82
|
+
conn.multi_query("SELECT * FROM posts_rt WHERE views = ? AND user_id = ?", 1, 7)
|
83
|
+
end
|
84
|
+
|
85
|
+
xit "accepts float bind parameters" do
|
86
|
+
conn.multi_query("SELECT * FROM posts_rt WHERE views = ? AND user_id = ?", 1.2, 7.2)
|
87
|
+
end
|
88
|
+
|
89
|
+
xit "accepts decimal bind parameters" do
|
90
|
+
require "bigdecimal"
|
91
|
+
conn.multi_query("SELECT * FROM posts_rt WHERE views = ? AND user_id = ?", BigDecimal("1.2"), BigDecimal("7.2"))
|
92
|
+
end
|
93
|
+
|
94
|
+
xit "accepts string bind parameters" do
|
95
|
+
conn.multi_query("SELECT * FROM posts_rt WHERE state = ?", "something")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "#execute" do
|
100
|
+
it "accepts integer bind parameters" do
|
101
|
+
conn.execute("REPLACE INTO posts_rt (id, views) VALUES (?, ?)", 1, 7)
|
102
|
+
end
|
103
|
+
|
104
|
+
it "accepts float bind parameters" do
|
105
|
+
conn.execute("REPLACE INTO posts_rt (id, views) VALUES (?, ?)", 1, 7.2)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "accepts decimal bind parameters" do
|
109
|
+
require "bigdecimal"
|
110
|
+
conn.execute("REPLACE INTO posts_rt (id, views) VALUES (?, ?)", 1, BigDecimal("7.2"))
|
111
|
+
end
|
112
|
+
|
113
|
+
it "accepts string bind parameters" do
|
114
|
+
conn.execute("REPLACE INTO posts_rt (id, title) VALUES (?, ?)", 1, "an example with `\"this (quoted) string\\'")
|
115
|
+
end
|
116
|
+
|
117
|
+
it "doesn't confuse bind parameters in replacements" do
|
118
|
+
conn.execute("REPLACE INTO posts_rt (id, title, body) VALUES (?, ?, ?)", 1, "question?", "I think not")
|
119
|
+
end
|
120
|
+
|
121
|
+
it "ignores bind markers inside strings" do
|
122
|
+
conn.execute("REPLACE INTO posts_rt (id, title, state, body) VALUES (?, 'question?', 'other?', ?)", 1, "I think not")
|
123
|
+
end
|
124
|
+
|
125
|
+
it "ignores bind markers inside comments" do
|
126
|
+
conn.execute <<-SQL, 1, "A string"
|
127
|
+
/* is this a comment? *//* another comment ? */
|
128
|
+
REPLACE INTO posts_rt (id, title) VALUES (?, ?)
|
129
|
+
SQL
|
130
|
+
end
|
131
|
+
|
132
|
+
it "handles nil" do
|
133
|
+
conn.execute("REPLACE INTO posts_rt (id, title, state, body) VALUES (?, 'question?', 'other?', ?)", 1, nil)
|
134
|
+
end
|
135
|
+
|
136
|
+
it "handles true" do
|
137
|
+
conn.execute("REPLACE INTO posts_rt (id, views) VALUES (?, ?)", 1, true)
|
138
|
+
end
|
139
|
+
|
140
|
+
it "handles false" do
|
141
|
+
conn.execute("REPLACE INTO posts_rt (id, views) VALUES (?, ?)", 1, false)
|
142
|
+
end
|
143
|
+
|
144
|
+
it "handles really long strings" do
|
145
|
+
conn.execute("REPLACE INTO posts_rt (id, title, state, body) VALUES (?, 'question?', 'other?', ?)", 1, 'a' * 2_000_000)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe "#close" do
|
150
|
+
before(:each) { conn.close }
|
151
|
+
|
152
|
+
it "disposes the internal pool" do
|
153
|
+
conn.instance_variable_get(:@pool).should be_empty
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|