mini_sql 0.2.4 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +66 -0
- data/.rubocop-https---raw-githubusercontent-com-discourse-discourse-master--rubocop-yml +355 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG +22 -0
- data/Gemfile +3 -1
- data/Guardfile +2 -0
- data/README.md +125 -1
- data/Rakefile +3 -1
- data/bench/builder_perf.rb +138 -0
- data/bench/decorator_perf.rb +143 -0
- data/bench/mini_sql_methods_perf.rb +80 -0
- data/bench/prepared_perf.rb +59 -0
- data/bench/shared/generate_data.rb +133 -0
- data/bench/timestamp_perf.rb +22 -21
- data/bench/topic_mysql_perf.rb +1 -7
- data/bench/topic_perf.rb +27 -169
- data/bench/topic_wide_perf.rb +92 -0
- data/bin/console +1 -0
- data/lib/mini_sql.rb +20 -8
- data/lib/mini_sql/abstract/prepared_binds.rb +74 -0
- data/lib/mini_sql/abstract/prepared_cache.rb +45 -0
- data/lib/mini_sql/builder.rb +64 -24
- data/lib/mini_sql/connection.rb +15 -3
- data/lib/mini_sql/decoratable.rb +22 -0
- data/lib/mini_sql/deserializer_cache.rb +2 -0
- data/lib/mini_sql/inline_param_encoder.rb +12 -13
- data/lib/mini_sql/mysql/connection.rb +18 -3
- data/lib/mini_sql/mysql/deserializer_cache.rb +14 -16
- data/lib/mini_sql/mysql/prepared_binds.rb +15 -0
- data/lib/mini_sql/mysql/prepared_cache.rb +21 -0
- data/lib/mini_sql/mysql/prepared_connection.rb +44 -0
- data/lib/mini_sql/postgres/coders.rb +2 -0
- data/lib/mini_sql/postgres/connection.rb +89 -0
- data/lib/mini_sql/postgres/deserializer_cache.rb +36 -16
- data/lib/mini_sql/postgres/prepared_binds.rb +15 -0
- data/lib/mini_sql/postgres/prepared_cache.rb +25 -0
- data/lib/mini_sql/postgres/prepared_connection.rb +36 -0
- data/lib/mini_sql/postgres_jdbc/connection.rb +8 -1
- data/lib/mini_sql/postgres_jdbc/deserializer_cache.rb +43 -43
- data/lib/mini_sql/result.rb +30 -0
- data/lib/mini_sql/serializer.rb +84 -0
- data/lib/mini_sql/sqlite/connection.rb +20 -2
- data/lib/mini_sql/sqlite/deserializer_cache.rb +14 -16
- data/lib/mini_sql/sqlite/prepared_binds.rb +15 -0
- data/lib/mini_sql/sqlite/prepared_cache.rb +21 -0
- data/lib/mini_sql/sqlite/prepared_connection.rb +40 -0
- data/lib/mini_sql/version.rb +1 -1
- data/mini_sql.gemspec +7 -2
- metadata +75 -11
- data/.travis.yml +0 -26
data/bench/topic_perf.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'bundler/inline'
|
2
4
|
|
3
5
|
gemfile do
|
@@ -11,7 +13,9 @@ gemfile do
|
|
11
13
|
gem 'benchmark-ips'
|
12
14
|
gem 'sequel', github: 'jeremyevans/sequel'
|
13
15
|
gem 'sequel_pg', github: 'jeremyevans/sequel_pg', require: 'sequel'
|
14
|
-
gem 'swift-db-postgres', github: 'deepfryed/swift-db-postgres'
|
16
|
+
gem 'swift-db-postgres', github: 'deepfryed/swift-db-postgres' # sudo apt-get install uuid-dev
|
17
|
+
gem 'draper'
|
18
|
+
gem 'pry'
|
15
19
|
end
|
16
20
|
|
17
21
|
require 'sequel'
|
@@ -20,68 +24,14 @@ require 'memory_profiler'
|
|
20
24
|
require 'benchmark/ips'
|
21
25
|
require 'mini_sql'
|
22
26
|
|
23
|
-
|
24
|
-
:adapter => "postgresql",
|
25
|
-
:database => "test_db"
|
26
|
-
)
|
27
|
-
|
28
|
-
DB = Sequel.postgres('test_db')
|
29
|
-
|
30
|
-
pg = ActiveRecord::Base.connection.raw_connection
|
31
|
-
|
32
|
-
pg.async_exec <<SQL
|
33
|
-
drop table if exists topics
|
34
|
-
SQL
|
27
|
+
require '../mini_sql/bench/shared/generate_data'
|
35
28
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
updated_at timestamp without time zone NOT NULL,
|
43
|
-
views integer DEFAULT 0 NOT NULL,
|
44
|
-
posts_count integer DEFAULT 0 NOT NULL,
|
45
|
-
user_id integer,
|
46
|
-
last_post_user_id integer NOT NULL,
|
47
|
-
reply_count integer DEFAULT 0 NOT NULL,
|
48
|
-
featured_user1_id integer,
|
49
|
-
featured_user2_id integer,
|
50
|
-
featured_user3_id integer,
|
51
|
-
avg_time integer,
|
52
|
-
deleted_at timestamp without time zone,
|
53
|
-
highest_post_number integer DEFAULT 0 NOT NULL,
|
54
|
-
image_url character varying,
|
55
|
-
like_count integer DEFAULT 0 NOT NULL,
|
56
|
-
incoming_link_count integer DEFAULT 0 NOT NULL,
|
57
|
-
category_id integer,
|
58
|
-
visible boolean DEFAULT true NOT NULL,
|
59
|
-
moderator_posts_count integer DEFAULT 0 NOT NULL,
|
60
|
-
closed boolean DEFAULT false NOT NULL,
|
61
|
-
archived boolean DEFAULT false NOT NULL,
|
62
|
-
bumped_at timestamp without time zone NOT NULL,
|
63
|
-
has_summary boolean DEFAULT false NOT NULL,
|
64
|
-
vote_count integer DEFAULT 0 NOT NULL,
|
65
|
-
archetype character varying DEFAULT 'regular'::character varying NOT NULL,
|
66
|
-
featured_user4_id integer,
|
67
|
-
notify_moderators_count integer DEFAULT 0 NOT NULL,
|
68
|
-
spam_count integer DEFAULT 0 NOT NULL,
|
69
|
-
pinned_at timestamp without time zone,
|
70
|
-
score double precision,
|
71
|
-
percent_rank double precision DEFAULT 1.0 NOT NULL,
|
72
|
-
subtype character varying,
|
73
|
-
slug character varying,
|
74
|
-
deleted_by_id integer,
|
75
|
-
participant_count integer DEFAULT 1,
|
76
|
-
word_count integer,
|
77
|
-
excerpt character varying(1000),
|
78
|
-
pinned_globally boolean DEFAULT false NOT NULL,
|
79
|
-
pinned_until timestamp without time zone,
|
80
|
-
fancy_title character varying(400),
|
81
|
-
highest_staff_post_number integer DEFAULT 0 NOT NULL,
|
82
|
-
featured_link character varying
|
83
|
-
)
|
84
|
-
SQL
|
29
|
+
ar_connection, conn_config = GenerateData.new(count_records: 1_000).call
|
30
|
+
PG_CONN = ar_connection.raw_connection
|
31
|
+
MINI_SQL = MiniSql::Connection.get(PG_CONN)
|
32
|
+
DB = Sequel.connect(ar_connection.instance_variable_get(:@config).slice(:database, :user, :password, :host, :adapter))
|
33
|
+
# connects over unix socket
|
34
|
+
SWIFT = Swift::DB::Postgres.new(db: conn_config[:database], user: conn_config[:user], password: conn_config[:password], host: conn_config[:host])
|
85
35
|
|
86
36
|
class Topic < ActiveRecord::Base
|
87
37
|
end
|
@@ -89,27 +39,6 @@ end
|
|
89
39
|
class TopicSequel < Sequel::Model(:topics)
|
90
40
|
end
|
91
41
|
|
92
|
-
|
93
|
-
Topic.transaction do
|
94
|
-
topic = {
|
95
|
-
}
|
96
|
-
Topic.columns.each do |c|
|
97
|
-
topic[c.name.to_sym] = case c.type
|
98
|
-
when :integer then 1
|
99
|
-
when :datetime then Time.now
|
100
|
-
when :boolean then false
|
101
|
-
else "HELLO WORLD" * 2
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
1000.times do |id|
|
106
|
-
topic[:id] = id
|
107
|
-
Topic.create!(topic)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
$conn = ActiveRecord::Base.connection.raw_connection
|
112
|
-
|
113
42
|
def ar_title_id_pluck
|
114
43
|
s = +""
|
115
44
|
Topic.limit(1000).order(:id).pluck(:id, :title).each do |id, title|
|
@@ -131,7 +60,7 @@ end
|
|
131
60
|
def pg_title_id
|
132
61
|
s = +""
|
133
62
|
# use the safe pattern here
|
134
|
-
r =
|
63
|
+
r = PG_CONN.async_exec(-"select id, title from topics order by id limit 1000")
|
135
64
|
|
136
65
|
# this seems fastest despite extra arrays, cause array of arrays is generated
|
137
66
|
# in c code
|
@@ -148,11 +77,9 @@ def pg_title_id
|
|
148
77
|
s
|
149
78
|
end
|
150
79
|
|
151
|
-
$mini_sql = MiniSql::Connection.get($conn)
|
152
|
-
|
153
80
|
def mini_sql_title_id
|
154
81
|
s = +""
|
155
|
-
|
82
|
+
MINI_SQL.query(-"select id, title from topics order by id limit 1000").each do |t|
|
156
83
|
s << t.id.to_s
|
157
84
|
s << t.title
|
158
85
|
end
|
@@ -181,22 +108,19 @@ end
|
|
181
108
|
def mini_sql_title_id_query_single
|
182
109
|
s = +""
|
183
110
|
i = 0
|
184
|
-
r =
|
111
|
+
r = MINI_SQL.query_single(-"select id, title from topics order by id limit 1000")
|
185
112
|
while i < r.length
|
186
113
|
s << r[i].to_s
|
187
|
-
s << r[i+1]
|
114
|
+
s << r[i + 1]
|
188
115
|
i += 2
|
189
116
|
end
|
190
117
|
s
|
191
118
|
end
|
192
119
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
def swift_select_title_id(l=1000)
|
197
|
-
s = ""
|
120
|
+
def swift_select_title_id(l = 1000)
|
121
|
+
s = +''
|
198
122
|
i = 0
|
199
|
-
r =
|
123
|
+
r = SWIFT.execute("select id, title from topics order by id limit 1000")
|
200
124
|
while i < r.selected_rows
|
201
125
|
s << r.get(i, 0).to_s
|
202
126
|
s << r.get(i, 1)
|
@@ -218,7 +142,6 @@ results = [
|
|
218
142
|
|
219
143
|
exit(-1) unless results.uniq.length == 1
|
220
144
|
|
221
|
-
|
222
145
|
Benchmark.ips do |r|
|
223
146
|
r.report("ar select title id") do |n|
|
224
147
|
while n > 0
|
@@ -271,77 +194,12 @@ Benchmark.ips do |r|
|
|
271
194
|
r.compare!
|
272
195
|
end
|
273
196
|
|
274
|
-
|
275
|
-
|
276
|
-
def wide_topic_ar
|
277
|
-
Topic.first
|
278
|
-
end
|
279
|
-
|
280
|
-
def wide_topic_pg
|
281
|
-
r = $conn.async_exec("select * from topics limit 1")
|
282
|
-
row = r.first
|
283
|
-
r.clear
|
284
|
-
row
|
285
|
-
end
|
286
|
-
|
287
|
-
def wide_topic_sequel
|
288
|
-
TopicSequel.first
|
289
|
-
end
|
290
|
-
|
291
|
-
def wide_topic_mini_sql
|
292
|
-
$conn.query("select * from topics limit 1").first
|
293
|
-
end
|
294
|
-
|
295
|
-
Benchmark.ips do |r|
|
296
|
-
r.report("wide topic ar") do |n|
|
297
|
-
while n > 0
|
298
|
-
wide_topic_ar
|
299
|
-
n -= 1
|
300
|
-
end
|
301
|
-
end
|
302
|
-
r.report("wide topic sequel") do |n|
|
303
|
-
while n > 0
|
304
|
-
wide_topic_sequel
|
305
|
-
n -= 1
|
306
|
-
end
|
307
|
-
end
|
308
|
-
r.report("wide topic pg") do |n|
|
309
|
-
while n > 0
|
310
|
-
wide_topic_pg
|
311
|
-
n -= 1
|
312
|
-
end
|
313
|
-
end
|
314
|
-
r.report("wide topic mini sql") do |n|
|
315
|
-
while n > 0
|
316
|
-
wide_topic_mini_sql
|
317
|
-
n -= 1
|
318
|
-
end
|
319
|
-
end
|
320
|
-
r.compare!
|
321
|
-
end
|
322
|
-
|
323
|
-
|
324
|
-
# Comparison:
|
325
|
-
# pg select title id: 1519.7 i/s
|
326
|
-
# mini_sql query_single title id: 1335.0 i/s - 1.14x slower
|
327
|
-
# sequel title id pluck: 1261.6 i/s - 1.20x slower
|
328
|
-
# mini_sql select title id: 1188.6 i/s - 1.28x slower
|
329
|
-
# swift title id: 1077.5 i/s - 1.41x slower
|
330
|
-
# sequel title id select: 969.7 i/s - 1.57x slower
|
331
|
-
# ar select title id pluck: 738.7 i/s - 2.06x slower
|
332
|
-
# ar select title id: 149.6 i/s - 10.16x slower
|
333
|
-
#
|
334
|
-
#
|
335
197
|
# Comparison:
|
336
|
-
#
|
337
|
-
#
|
338
|
-
#
|
339
|
-
#
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
#
|
344
|
-
# MemoryProfiler.report do
|
345
|
-
# ar
|
346
|
-
# end.pretty_print
|
347
|
-
|
198
|
+
# pg select title id: 1315.1 i/s
|
199
|
+
# swift title id: 1268.4 i/s - same-ish: difference falls within error
|
200
|
+
# mini_sql query_single title id: 1206.3 i/s - same-ish: difference falls within error
|
201
|
+
# mini_sql select title id: 1063.8 i/s - 1.24x (± 0.00) slower
|
202
|
+
# sequel title id pluck: 1054.5 i/s - 1.25x (± 0.00) slower
|
203
|
+
# sequel title id select: 814.1 i/s - 1.62x (± 0.00) slower
|
204
|
+
# ar select title id pluck: 667.7 i/s - 1.97x (± 0.00) slower
|
205
|
+
# ar select title id: 215.8 i/s - 6.09x (± 0.00) slower
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/inline'
|
4
|
+
|
5
|
+
gemfile do
|
6
|
+
source 'https://rubygems.org'
|
7
|
+
gem 'pg', github: 'ged/ruby-pg'
|
8
|
+
gem 'mini_sql', path: '../'
|
9
|
+
gem 'activesupport'
|
10
|
+
gem 'activerecord'
|
11
|
+
gem 'activemodel'
|
12
|
+
gem 'memory_profiler'
|
13
|
+
gem 'benchmark-ips'
|
14
|
+
gem 'sequel', github: 'jeremyevans/sequel'
|
15
|
+
gem 'sequel_pg', github: 'jeremyevans/sequel_pg', require: 'sequel'
|
16
|
+
gem 'draper'
|
17
|
+
gem 'pry'
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'sequel'
|
21
|
+
require 'active_record'
|
22
|
+
require 'memory_profiler'
|
23
|
+
require 'benchmark/ips'
|
24
|
+
require 'mini_sql'
|
25
|
+
|
26
|
+
require '../mini_sql/bench/shared/generate_data'
|
27
|
+
|
28
|
+
ar_connection, conn_config = GenerateData.new(count_records: 1_000).call
|
29
|
+
PG_CONN = ar_connection.raw_connection
|
30
|
+
MINI_SQL = MiniSql::Connection.get(PG_CONN)
|
31
|
+
DB = Sequel.connect(ar_connection.instance_variable_get(:@config).slice(:database, :user, :password, :host, :adapter))
|
32
|
+
|
33
|
+
class Topic < ActiveRecord::Base
|
34
|
+
end
|
35
|
+
|
36
|
+
class TopicSequel < Sequel::Model(:topics)
|
37
|
+
end
|
38
|
+
|
39
|
+
def wide_topic_ar
|
40
|
+
Topic.first
|
41
|
+
end
|
42
|
+
|
43
|
+
def wide_topic_pg
|
44
|
+
r = PG_CONN.async_exec("select * from topics limit 1")
|
45
|
+
row = r.first
|
46
|
+
r.clear
|
47
|
+
row
|
48
|
+
end
|
49
|
+
|
50
|
+
def wide_topic_sequel
|
51
|
+
TopicSequel.first
|
52
|
+
end
|
53
|
+
|
54
|
+
def wide_topic_mini_sql
|
55
|
+
PG_CONN.query("select * from topics limit 1").first
|
56
|
+
end
|
57
|
+
|
58
|
+
Benchmark.ips do |r|
|
59
|
+
r.report("wide topic ar") do |n|
|
60
|
+
while n > 0
|
61
|
+
wide_topic_ar
|
62
|
+
n -= 1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
r.report("wide topic sequel") do |n|
|
66
|
+
while n > 0
|
67
|
+
wide_topic_sequel
|
68
|
+
n -= 1
|
69
|
+
end
|
70
|
+
end
|
71
|
+
r.report("wide topic pg") do |n|
|
72
|
+
while n > 0
|
73
|
+
wide_topic_pg
|
74
|
+
n -= 1
|
75
|
+
end
|
76
|
+
end
|
77
|
+
r.report("wide topic mini sql") do |n|
|
78
|
+
while n > 0
|
79
|
+
wide_topic_mini_sql
|
80
|
+
n -= 1
|
81
|
+
end
|
82
|
+
end
|
83
|
+
r.compare!
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
#
|
88
|
+
# Comparison:
|
89
|
+
# wide topic pg: 6974.6 i/s
|
90
|
+
# wide topic mini sql: 6760.9 i/s - same-ish: difference falls within error
|
91
|
+
# wide topic sequel: 5050.5 i/s - 1.38x (± 0.00) slower
|
92
|
+
# wide topic ar: 1565.4 i/s - 4.46x (± 0.00) slower
|
data/bin/console
CHANGED
data/lib/mini_sql.rb
CHANGED
@@ -8,28 +8,40 @@ require_relative "mini_sql/connection"
|
|
8
8
|
require_relative "mini_sql/deserializer_cache"
|
9
9
|
require_relative "mini_sql/builder"
|
10
10
|
require_relative "mini_sql/inline_param_encoder"
|
11
|
+
require_relative "mini_sql/decoratable"
|
12
|
+
require_relative "mini_sql/serializer"
|
13
|
+
require_relative "mini_sql/result"
|
11
14
|
|
12
15
|
module MiniSql
|
13
16
|
if RUBY_ENGINE == 'jruby'
|
14
17
|
module Postgres
|
15
|
-
autoload :Connection,
|
18
|
+
autoload :Connection, "mini_sql/postgres_jdbc/connection"
|
16
19
|
autoload :DeserializerCache, "mini_sql/postgres_jdbc/deserializer_cache"
|
17
20
|
end
|
18
21
|
else
|
19
22
|
module Postgres
|
20
|
-
autoload :Coders,
|
21
|
-
autoload :Connection,
|
22
|
-
autoload :DeserializerCache,
|
23
|
+
autoload :Coders, "mini_sql/postgres/coders"
|
24
|
+
autoload :Connection, "mini_sql/postgres/connection"
|
25
|
+
autoload :DeserializerCache, "mini_sql/postgres/deserializer_cache"
|
26
|
+
autoload :PreparedConnection, "mini_sql/postgres/prepared_connection"
|
27
|
+
autoload :PreparedCache, "mini_sql/postgres/prepared_cache"
|
28
|
+
autoload :PreparedBinds, "mini_sql/postgres/prepared_binds"
|
23
29
|
end
|
24
30
|
|
25
31
|
module Sqlite
|
26
|
-
autoload :Connection,
|
27
|
-
autoload :DeserializerCache,
|
32
|
+
autoload :Connection, "mini_sql/sqlite/connection"
|
33
|
+
autoload :DeserializerCache, "mini_sql/sqlite/deserializer_cache"
|
34
|
+
autoload :PreparedCache, "mini_sql/sqlite/prepared_cache"
|
35
|
+
autoload :PreparedBinds, "mini_sql/sqlite/prepared_binds"
|
36
|
+
autoload :PreparedConnection, "mini_sql/sqlite/prepared_connection"
|
28
37
|
end
|
29
38
|
|
30
39
|
module Mysql
|
31
|
-
autoload :Connection,
|
32
|
-
autoload :DeserializerCache,
|
40
|
+
autoload :Connection, "mini_sql/mysql/connection"
|
41
|
+
autoload :DeserializerCache, "mini_sql/mysql/deserializer_cache"
|
42
|
+
autoload :PreparedCache, "mini_sql/mysql/prepared_cache"
|
43
|
+
autoload :PreparedBinds, "mini_sql/mysql/prepared_binds"
|
44
|
+
autoload :PreparedConnection, "mini_sql/mysql/prepared_connection"
|
33
45
|
end
|
34
46
|
end
|
35
47
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MiniSql
|
4
|
+
module Abstract
|
5
|
+
class PreparedBinds
|
6
|
+
|
7
|
+
# For compatibility with Active Record
|
8
|
+
BindName = Struct.new(:name)
|
9
|
+
|
10
|
+
def bind(sql, *params)
|
11
|
+
if Hash === (hash = params[0])
|
12
|
+
bind_hash(sql, hash)
|
13
|
+
else
|
14
|
+
bind_array(sql, params)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def bind_hash(sql, hash)
|
19
|
+
sql = sql.dup
|
20
|
+
binds = []
|
21
|
+
bind_names = []
|
22
|
+
i = 0
|
23
|
+
|
24
|
+
hash.each do |k, v|
|
25
|
+
sql.gsub!(":#{k}") do
|
26
|
+
# ignore ::int and stuff like that
|
27
|
+
# $` is previous to match
|
28
|
+
if $` && $`[-1] != ":"
|
29
|
+
array_wrap(v).map do |vv|
|
30
|
+
binds << vv
|
31
|
+
bind_names << [BindName.new(k)]
|
32
|
+
bind_output(i += 1)
|
33
|
+
end.join(', ')
|
34
|
+
else
|
35
|
+
":#{k}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
[sql, binds, bind_names]
|
40
|
+
end
|
41
|
+
|
42
|
+
def bind_array(sql, array)
|
43
|
+
sql = sql.dup
|
44
|
+
param_i = 0
|
45
|
+
i = 0
|
46
|
+
binds = []
|
47
|
+
bind_names = []
|
48
|
+
sql.gsub!("?") do
|
49
|
+
param_i += 1
|
50
|
+
array_wrap(array[param_i - 1]).map do |vv|
|
51
|
+
binds << vv
|
52
|
+
i += 1
|
53
|
+
bind_names << [BindName.new("$#{i}")]
|
54
|
+
bind_output(i)
|
55
|
+
end.join(', ')
|
56
|
+
end
|
57
|
+
[sql, binds, bind_names]
|
58
|
+
end
|
59
|
+
|
60
|
+
def array_wrap(object)
|
61
|
+
if object.respond_to?(:to_ary)
|
62
|
+
object.to_ary || [object]
|
63
|
+
else
|
64
|
+
[object]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def bind_output(_)
|
69
|
+
raise NotImplementedError, "must be implemented by specific database driver"
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|