extralite 1.27 → 2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/Gemfile.lock +1 -1
- data/LICENSE +1 -1
- data/README.md +40 -14
- data/TODO.md +21 -0
- data/ext/extralite/common.c +80 -58
- data/ext/extralite/database.c +138 -78
- data/ext/extralite/extconf.rb +16 -16
- data/ext/extralite/extralite.h +63 -17
- data/ext/extralite/extralite_ext.c +4 -2
- data/ext/extralite/iterator.c +208 -0
- data/ext/extralite/query.c +534 -0
- data/lib/extralite/sqlite3_constants.rb +1 -1
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +0 -2
- data/lib/sequel/adapters/extralite.rb +104 -106
- data/test/perf_prepared.rb +2 -2
- data/test/test_database.rb +35 -9
- data/test/test_extralite.rb +1 -1
- data/test/test_iterator.rb +104 -0
- data/test/test_query.rb +519 -0
- data/test/test_sequel.rb +23 -4
- metadata +6 -4
- data/ext/extralite/prepared_statement.c +0 -333
- data/test/test_prepared_statement.rb +0 -225
@@ -11,24 +11,20 @@ require 'sequel/adapters/shared/sqlite'
|
|
11
11
|
module Sequel
|
12
12
|
# Extralite Sequel adapter
|
13
13
|
module Extralite
|
14
|
-
# @!visibility private
|
15
14
|
FALSE_VALUES = (%w'0 false f no n'.each(&:freeze) + [0]).freeze
|
16
15
|
|
17
16
|
blob = Object.new
|
18
|
-
# @!visibility private
|
19
17
|
def blob.call(s)
|
20
18
|
Sequel::SQL::Blob.new(s.to_s)
|
21
19
|
end
|
22
20
|
|
23
21
|
boolean = Object.new
|
24
|
-
# @!visibility private
|
25
22
|
def boolean.call(s)
|
26
23
|
s = s.downcase if s.is_a?(String)
|
27
24
|
!FALSE_VALUES.include?(s)
|
28
25
|
end
|
29
26
|
|
30
27
|
date = Object.new
|
31
|
-
# @!visibility private
|
32
28
|
def date.call(s)
|
33
29
|
case s
|
34
30
|
when String
|
@@ -43,26 +39,22 @@ module Sequel
|
|
43
39
|
end
|
44
40
|
|
45
41
|
integer = Object.new
|
46
|
-
# @!visibility private
|
47
42
|
def integer.call(s)
|
48
43
|
s.to_i
|
49
44
|
end
|
50
45
|
|
51
46
|
float = Object.new
|
52
|
-
# @!visibility private
|
53
47
|
def float.call(s)
|
54
48
|
s.to_f
|
55
49
|
end
|
56
50
|
|
57
51
|
numeric = Object.new
|
58
|
-
# @!visibility private
|
59
52
|
def numeric.call(s)
|
60
53
|
s = s.to_s unless s.is_a?(String)
|
61
54
|
BigDecimal(s) rescue s
|
62
55
|
end
|
63
56
|
|
64
57
|
time = Object.new
|
65
|
-
# @!visibility private
|
66
58
|
def time.call(s)
|
67
59
|
case s
|
68
60
|
when String
|
@@ -77,7 +69,7 @@ module Sequel
|
|
77
69
|
end
|
78
70
|
end
|
79
71
|
|
80
|
-
# Hash with string keys and callable values for converting
|
72
|
+
# Hash with string keys and callable values for converting SQLITE types.
|
81
73
|
SQLITE_TYPES = {}
|
82
74
|
{
|
83
75
|
%w'date' => date,
|
@@ -92,27 +84,28 @@ module Sequel
|
|
92
84
|
end
|
93
85
|
SQLITE_TYPES.freeze
|
94
86
|
|
95
|
-
# @!visibility private
|
96
|
-
USE_EXTENDED_RESULT_CODES = false
|
97
|
-
|
98
|
-
# Database adapter for Sequel
|
99
87
|
class Database < Sequel::Database
|
100
88
|
include ::Sequel::SQLite::DatabaseMethods
|
101
|
-
|
89
|
+
|
102
90
|
set_adapter_scheme :extralite
|
103
|
-
|
91
|
+
|
104
92
|
# Mimic the file:// uri, by having 2 preceding slashes specify a relative
|
105
93
|
# path, and 3 preceding slashes specify an absolute path.
|
106
94
|
def self.uri_to_options(uri) # :nodoc:
|
107
95
|
{ :database => (uri.host.nil? && uri.path == '/') ? nil : "#{uri.host}#{uri.path}" }
|
108
96
|
end
|
109
|
-
|
97
|
+
|
110
98
|
private_class_method :uri_to_options
|
111
99
|
|
112
100
|
# The conversion procs to use for this database
|
113
101
|
attr_reader :conversion_procs
|
114
102
|
|
115
|
-
|
103
|
+
def initialize(opts = OPTS)
|
104
|
+
super
|
105
|
+
@allow_regexp = typecast_value_boolean(opts[:setup_regexp_function])
|
106
|
+
end
|
107
|
+
|
108
|
+
# Connect to the database. Since Extralite is a file based database,
|
116
109
|
# available options are limited:
|
117
110
|
#
|
118
111
|
# :database :: database name (filename or ':memory:' or file: URI)
|
@@ -123,31 +116,39 @@ module Sequel
|
|
123
116
|
def connect(server)
|
124
117
|
opts = server_opts(server)
|
125
118
|
opts[:database] = ':memory:' if blank_object?(opts[:database])
|
126
|
-
#
|
127
|
-
|
128
|
-
|
119
|
+
# db opts may be used in a future version of Extralite
|
120
|
+
db_opts = {}
|
121
|
+
db_opts[:readonly] = typecast_value_boolean(opts[:readonly]) if opts.has_key?(:readonly)
|
122
|
+
db = ::Extralite::Database.new(opts[:database].to_s)
|
129
123
|
# db.busy_timeout(typecast_value_integer(opts.fetch(:timeout, 5000)))
|
130
124
|
|
131
|
-
# if USE_EXTENDED_RESULT_CODES
|
132
|
-
# db.extended_result_codes = true
|
133
|
-
# end
|
134
|
-
|
135
125
|
connection_pragmas.each{|s| log_connection_yield(s, db){db.query(s)}}
|
136
126
|
|
127
|
+
if typecast_value_boolean(opts[:setup_regexp_function])
|
128
|
+
db.create_function("regexp", 2) do |func, regexp_str, string|
|
129
|
+
func.result = Regexp.new(regexp_str).match(string) ? 1 : 0
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
137
133
|
class << db
|
138
134
|
attr_reader :prepared_statements
|
139
135
|
end
|
140
136
|
db.instance_variable_set(:@prepared_statements, {})
|
141
|
-
|
137
|
+
|
142
138
|
db
|
143
139
|
end
|
144
140
|
|
141
|
+
# Whether this Database instance is setup to allow regexp matching.
|
142
|
+
# True if the :setup_regexp_function option was passed when creating the Database.
|
143
|
+
def allow_regexp?
|
144
|
+
@allow_regexp
|
145
|
+
end
|
146
|
+
|
145
147
|
# Disconnect given connections from the database.
|
146
148
|
def disconnect_connection(c)
|
147
|
-
c.prepared_statements.each_value{|v| v.first.close }
|
148
149
|
c.close
|
149
150
|
end
|
150
|
-
|
151
|
+
|
151
152
|
# Run the given SQL with the given arguments and yield each row.
|
152
153
|
def execute(sql, opts=OPTS, &block)
|
153
154
|
_execute(:select, sql, opts, &block)
|
@@ -157,30 +158,28 @@ module Sequel
|
|
157
158
|
def execute_dui(sql, opts=OPTS)
|
158
159
|
_execute(:update, sql, opts)
|
159
160
|
end
|
160
|
-
|
161
|
+
|
161
162
|
# Drop any prepared statements on the connection when executing DDL. This is because
|
162
163
|
# prepared statements lock the table in such a way that you can't drop or alter the
|
163
164
|
# table while a prepared statement that references it still exists.
|
164
165
|
def execute_ddl(sql, opts=OPTS)
|
165
166
|
synchronize(opts[:server]) do |conn|
|
166
|
-
conn.prepared_statements.values.each{|cps, s| cps.close}
|
167
|
-
conn.prepared_statements.clear
|
167
|
+
# conn.prepared_statements.values.each{|cps, s| cps.close}
|
168
|
+
# conn.prepared_statements.clear
|
168
169
|
super
|
169
170
|
end
|
170
171
|
end
|
171
|
-
|
172
|
-
# @!visibility private
|
172
|
+
|
173
173
|
def execute_insert(sql, opts=OPTS)
|
174
174
|
_execute(:insert, sql, opts)
|
175
175
|
end
|
176
|
-
|
177
|
-
# @!visibility private
|
176
|
+
|
178
177
|
def freeze
|
179
178
|
@conversion_procs.freeze
|
180
179
|
super
|
181
180
|
end
|
182
181
|
|
183
|
-
# Handle Integer and Float arguments, since
|
182
|
+
# Handle Integer and Float arguments, since Extralite can store timestamps as integers and floats.
|
184
183
|
def to_application_timestamp(s)
|
185
184
|
case s
|
186
185
|
when String
|
@@ -195,39 +194,38 @@ module Sequel
|
|
195
194
|
end
|
196
195
|
|
197
196
|
private
|
198
|
-
|
197
|
+
|
199
198
|
def adapter_initialize
|
200
199
|
@conversion_procs = SQLITE_TYPES.dup
|
201
200
|
@conversion_procs['datetime'] = @conversion_procs['timestamp'] = method(:to_application_timestamp)
|
202
201
|
set_integer_booleans
|
203
202
|
end
|
204
|
-
|
205
|
-
# Yield an available connection.
|
206
|
-
# them into DatabaseErrors.
|
203
|
+
|
204
|
+
# Yield an available connection. Rescue
|
205
|
+
# any Extralite::Errors and turn them into DatabaseErrors.
|
207
206
|
def _execute(type, sql, opts, &block)
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
end
|
207
|
+
synchronize(opts[:server]) do |conn|
|
208
|
+
return execute_prepared_statement(conn, type, sql, opts, &block) if sql.is_a?(Symbol)
|
209
|
+
log_args = opts[:arguments]
|
210
|
+
args = {}
|
211
|
+
opts.fetch(:arguments, OPTS).each{|k, v| args[k] = prepared_statement_argument(v)}
|
212
|
+
case type
|
213
|
+
when :select
|
214
|
+
query = conn.prepare(sql, args)
|
215
|
+
log_connection_yield(sql, conn, log_args){block.call(query.each, query.columns)}
|
216
|
+
when :insert
|
217
|
+
log_connection_yield(sql, conn, log_args){conn.query(sql, args)}
|
218
|
+
conn.last_insert_rowid
|
219
|
+
when :update
|
220
|
+
log_connection_yield(sql, conn, log_args){conn.query(sql, args)}
|
221
|
+
conn.changes
|
224
222
|
end
|
225
|
-
rescue ::Extralite::Error => e
|
226
|
-
raise_error(e)
|
227
223
|
end
|
224
|
+
rescue ::Extralite::Error => e
|
225
|
+
raise_error(e)
|
228
226
|
end
|
229
|
-
|
230
|
-
# The
|
227
|
+
|
228
|
+
# The Extralite adapter does not need the pool to convert exceptions.
|
231
229
|
# Also, force the max connections to 1 if a memory database is being
|
232
230
|
# used, as otherwise each connection gets a separate database.
|
233
231
|
def connection_pool_default_options
|
@@ -237,7 +235,7 @@ module Sequel
|
|
237
235
|
o[:max_connections] = 1 if @opts[:database] == ':memory:' || blank_object?(@opts[:database])
|
238
236
|
o
|
239
237
|
end
|
240
|
-
|
238
|
+
|
241
239
|
def prepared_statement_argument(arg)
|
242
240
|
case arg
|
243
241
|
when Date, DateTime, Time
|
@@ -281,9 +279,10 @@ module Sequel
|
|
281
279
|
log_sql << ")"
|
282
280
|
end
|
283
281
|
if block
|
284
|
-
|
282
|
+
cps.bind(ps_args)
|
283
|
+
log_connection_yield(log_sql, conn, args){block.call(cps.each, cps.columns)}
|
285
284
|
else
|
286
|
-
log_connection_yield(log_sql, conn, args){cps.
|
285
|
+
log_connection_yield(log_sql, conn, args){cps.bind(ps_args).to_a}
|
287
286
|
case type
|
288
287
|
when :insert
|
289
288
|
conn.last_insert_rowid
|
@@ -292,11 +291,8 @@ module Sequel
|
|
292
291
|
end
|
293
292
|
end
|
294
293
|
end
|
295
|
-
|
296
|
-
# # SQLite3 raises ArgumentError in addition to SQLite3::Exception in
|
297
|
-
# # some cases, such as operations on a closed database.
|
294
|
+
|
298
295
|
def database_error_classes
|
299
|
-
#[Extralite::Error, ArgumentError]
|
300
296
|
[::Extralite::Error]
|
301
297
|
end
|
302
298
|
|
@@ -304,24 +300,19 @@ module Sequel
|
|
304
300
|
Dataset
|
305
301
|
end
|
306
302
|
|
307
|
-
|
308
|
-
|
309
|
-
def sqlite_error_code(exception)
|
310
|
-
exception.code if exception.respond_to?(:code)
|
311
|
-
end
|
303
|
+
def extralite_error_code(exception)
|
304
|
+
exception.code if exception.respond_to?(:code)
|
312
305
|
end
|
313
306
|
end
|
314
|
-
|
315
|
-
# Dataset adapter for Sequel
|
307
|
+
|
316
308
|
class Dataset < Sequel::Dataset
|
317
309
|
include ::Sequel::SQLite::DatasetMethods
|
318
310
|
|
319
|
-
# @!visibility private
|
320
311
|
module ArgumentMapper
|
321
312
|
include Sequel::Dataset::ArgumentMapper
|
322
|
-
|
313
|
+
|
323
314
|
protected
|
324
|
-
|
315
|
+
|
325
316
|
# Return a hash with the same values as the given hash,
|
326
317
|
# but with the keys converted to strings.
|
327
318
|
def map_to_prepared_args(hash)
|
@@ -329,49 +320,56 @@ module Sequel
|
|
329
320
|
hash.each{|k,v| args[k.to_s.gsub('.', '__')] = v}
|
330
321
|
args
|
331
322
|
end
|
332
|
-
|
323
|
+
|
333
324
|
private
|
334
|
-
|
335
|
-
#
|
325
|
+
|
326
|
+
# Extralite uses a : before the name of the argument for named
|
336
327
|
# arguments.
|
337
328
|
def prepared_arg(k)
|
338
329
|
LiteralString.new("#{prepared_arg_placeholder}#{k.to_s.gsub('.', '__')}")
|
339
330
|
end
|
340
331
|
end
|
341
|
-
|
342
|
-
# @!visibility private
|
332
|
+
|
343
333
|
BindArgumentMethods = prepared_statements_module(:bind, ArgumentMapper)
|
344
|
-
# @!visibility private
|
345
334
|
PreparedStatementMethods = prepared_statements_module(:prepare, BindArgumentMethods)
|
346
335
|
|
347
|
-
#
|
336
|
+
# Support regexp functions if using :setup_regexp_function Database option.
|
337
|
+
def complex_expression_sql_append(sql, op, args)
|
338
|
+
case op
|
339
|
+
when :~, :'!~', :'~*', :'!~*'
|
340
|
+
return super unless supports_regexp?
|
341
|
+
|
342
|
+
case_insensitive = [:'~*', :'!~*'].include?(op)
|
343
|
+
sql << 'NOT ' if [:'!~', :'!~*'].include?(op)
|
344
|
+
sql << '('
|
345
|
+
sql << 'LOWER(' if case_insensitive
|
346
|
+
literal_append(sql, args[0])
|
347
|
+
sql << ')' if case_insensitive
|
348
|
+
sql << ' REGEXP '
|
349
|
+
sql << 'LOWER(' if case_insensitive
|
350
|
+
literal_append(sql, args[1])
|
351
|
+
sql << ')' if case_insensitive
|
352
|
+
sql << ')'
|
353
|
+
else
|
354
|
+
super
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
348
358
|
def fetch_rows(sql, &block)
|
349
|
-
execute(sql,
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
# cols = result.columns.map{|c| [output_identifier(c), type_procs[(j+=1)]]}
|
355
|
-
# self.columns = cols.map(&:first)
|
356
|
-
# max = cols.length
|
357
|
-
# result.each do |values|
|
358
|
-
# row = {}
|
359
|
-
# i = -1
|
360
|
-
# while (i += 1) < max
|
361
|
-
# name, type_proc = cols[i]
|
362
|
-
# v = values[i]
|
363
|
-
# if type_proc && v
|
364
|
-
# v = type_proc.call(v)
|
365
|
-
# end
|
366
|
-
# row[name] = v
|
367
|
-
# end
|
368
|
-
# yield row
|
369
|
-
# end
|
370
|
-
# end
|
359
|
+
execute(sql) do |result, columns|
|
360
|
+
self.columns = columns
|
361
|
+
max = columns.size
|
362
|
+
result.each(&block)
|
363
|
+
end
|
371
364
|
end
|
372
365
|
|
366
|
+
# Support regexp if using :setup_regexp_function Database option.
|
367
|
+
def supports_regexp?
|
368
|
+
db.allow_regexp?
|
369
|
+
end
|
370
|
+
|
373
371
|
private
|
374
|
-
|
372
|
+
|
375
373
|
# The base type name for a given type, without any parenthetical part.
|
376
374
|
def base_type_name(t)
|
377
375
|
(t =~ /^(.*?)\(/ ? $1 : t).downcase if t
|
data/test/perf_prepared.rb
CHANGED
@@ -39,9 +39,9 @@ def extralite_prepare
|
|
39
39
|
db.prepare('select * from foo')
|
40
40
|
end
|
41
41
|
|
42
|
-
def extralite_run(
|
42
|
+
def extralite_run(query, count)
|
43
43
|
# db = Extralite::Database.new(DB_PATH)
|
44
|
-
results =
|
44
|
+
results = query.to_a
|
45
45
|
raise unless results.size == count
|
46
46
|
end
|
47
47
|
|
data/test/test_database.rb
CHANGED
@@ -176,7 +176,7 @@ end
|
|
176
176
|
when /darwin/
|
177
177
|
@db.load_extension(File.join(__dir__, 'extensions/text.dylib'))
|
178
178
|
end
|
179
|
-
|
179
|
+
|
180
180
|
r = @db.query_single_value("select reverse('abcd')")
|
181
181
|
assert_equal 'dcba', r
|
182
182
|
end
|
@@ -206,6 +206,17 @@ end
|
|
206
206
|
assert_equal [{recursive_triggers: 1}], @db.pragma(:recursive_triggers)
|
207
207
|
end
|
208
208
|
|
209
|
+
def test_execute
|
210
|
+
changes = @db.execute('update t set x = 42')
|
211
|
+
assert_equal 2, changes
|
212
|
+
end
|
213
|
+
|
214
|
+
def test_execute_with_params
|
215
|
+
changes = @db.execute('update t set x = ? where z = ?', 42, 6)
|
216
|
+
assert_equal 1, changes
|
217
|
+
assert_equal [[1, 2, 3], [42, 5, 6]], @db.query_ary('select * from t order by x')
|
218
|
+
end
|
219
|
+
|
209
220
|
def test_execute_multi
|
210
221
|
@db.query('create table foo (a, b, c)')
|
211
222
|
assert_equal [], @db.query('select * from foo')
|
@@ -285,7 +296,7 @@ end
|
|
285
296
|
fn = "/tmp/extralite-#{rand(10000)}.db"
|
286
297
|
db1 = Extralite::Database.new(fn)
|
287
298
|
db2 = Extralite::Database.new(fn)
|
288
|
-
|
299
|
+
|
289
300
|
db1.query('begin exclusive')
|
290
301
|
assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
|
291
302
|
|
@@ -349,7 +360,7 @@ end
|
|
349
360
|
end
|
350
361
|
|
351
362
|
@db.query('create table t2 (v not null)')
|
352
|
-
|
363
|
+
|
353
364
|
assert_raises(Extralite::Error) { @db.query('insert into t2 values (null)') }
|
354
365
|
assert_equal Extralite::SQLITE_CONSTRAINT_NOTNULL, @db.errcode
|
355
366
|
assert_equal 'NOT NULL constraint failed: t2.v', @db.errmsg
|
@@ -357,10 +368,25 @@ end
|
|
357
368
|
|
358
369
|
|
359
370
|
def test_close_with_open_prepared_statement
|
360
|
-
|
361
|
-
|
371
|
+
query = @db.prepare('select * from t')
|
372
|
+
query.next
|
362
373
|
@db.close
|
363
374
|
end
|
375
|
+
|
376
|
+
def test_read_only_database
|
377
|
+
db = Extralite::Database.new(':memory:')
|
378
|
+
db.query('create table foo (bar)')
|
379
|
+
assert_equal false, db.read_only?
|
380
|
+
|
381
|
+
db = Extralite::Database.new(':memory:', read_only: true)
|
382
|
+
assert_raises(Extralite::Error) { db.query('create table foo (bar)') }
|
383
|
+
assert_equal true, db.read_only?
|
384
|
+
end
|
385
|
+
|
386
|
+
def test_database_inspect
|
387
|
+
db = Extralite::Database.new(':memory:')
|
388
|
+
assert_match /^\#\<Extralite::Database:0x[0-9a-f]+ :memory:\>$/, db.inspect
|
389
|
+
end
|
364
390
|
end
|
365
391
|
|
366
392
|
class ScenarioTest < MiniTest::Test
|
@@ -439,15 +465,15 @@ class ScenarioTest < MiniTest::Test
|
|
439
465
|
@db.query('select 2')
|
440
466
|
assert_equal ['select 1', 'select 2'], sqls
|
441
467
|
|
442
|
-
|
443
|
-
|
444
|
-
|
468
|
+
query = @db.prepare('select 3')
|
469
|
+
|
470
|
+
query.to_a
|
445
471
|
assert_equal ['select 1', 'select 2', 'select 3'], sqls
|
446
472
|
|
447
473
|
# turn off
|
448
474
|
@db.trace
|
449
475
|
|
450
|
-
|
476
|
+
query.to_a
|
451
477
|
|
452
478
|
@db.query('select 4')
|
453
479
|
assert_equal ['select 1', 'select 2', 'select 3'], sqls
|
data/test/test_extralite.rb
CHANGED
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'helper'
|
4
|
+
|
5
|
+
class IteratorTest < MiniTest::Test
|
6
|
+
def setup
|
7
|
+
@db = Extralite::Database.new(':memory:')
|
8
|
+
@db.query('create table if not exists t (x,y,z)')
|
9
|
+
@db.query('delete from t')
|
10
|
+
@db.query('insert into t values (1, 2, 3)')
|
11
|
+
@db.query('insert into t values (4, 5, 6)')
|
12
|
+
@db.query('insert into t values (7, 8, 9)')
|
13
|
+
|
14
|
+
@query = @db.prepare('select * from t')
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_iterator_each_idempotency
|
18
|
+
iter = @query.each
|
19
|
+
assert_equal iter, iter.each
|
20
|
+
assert_equal iter, iter.each.each
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_iterator_hash
|
24
|
+
iter = @query.each
|
25
|
+
assert_kind_of Extralite::Iterator, iter
|
26
|
+
|
27
|
+
buf = []
|
28
|
+
v = iter.each { |r| buf << r }
|
29
|
+
assert_equal iter, v
|
30
|
+
assert_equal [{x: 1, y: 2, z: 3},{ x: 4, y: 5, z: 6 }, { x: 7, y: 8, z: 9 }], buf
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_iterator_ary
|
34
|
+
iter = @query.each_ary
|
35
|
+
assert_kind_of Extralite::Iterator, iter
|
36
|
+
|
37
|
+
buf = []
|
38
|
+
v = iter.each { |r| buf << r }
|
39
|
+
assert_equal iter, v
|
40
|
+
assert_equal [[1, 2, 3], [4, 5, 6], [7, 8, 9]], buf
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_iterator_single_column
|
44
|
+
query = @db.prepare('select x from t')
|
45
|
+
iter = query.each_single_column
|
46
|
+
assert_kind_of Extralite::Iterator, iter
|
47
|
+
|
48
|
+
buf = []
|
49
|
+
v = iter.each { |r| buf << r }
|
50
|
+
assert_equal iter, v
|
51
|
+
assert_equal [1, 4, 7], buf
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_iterator_next
|
55
|
+
iter = @query.each
|
56
|
+
assert_equal({x: 1, y: 2, z: 3}, iter.next)
|
57
|
+
assert_equal({x: 4, y: 5, z: 6}, iter.next)
|
58
|
+
assert_equal({x: 7, y: 8, z: 9}, iter.next)
|
59
|
+
assert_nil iter.next
|
60
|
+
assert_nil iter.next
|
61
|
+
|
62
|
+
iter = @query.reset.each_ary
|
63
|
+
assert_equal([1, 2, 3], iter.next)
|
64
|
+
assert_equal([4, 5, 6], iter.next)
|
65
|
+
assert_equal([7, 8, 9], iter.next)
|
66
|
+
assert_nil iter.next
|
67
|
+
assert_nil iter.next
|
68
|
+
|
69
|
+
iter = @db.prepare('select y from t').each_single_column
|
70
|
+
assert_equal(2, iter.next)
|
71
|
+
assert_equal(5, iter.next)
|
72
|
+
assert_equal(8, iter.next)
|
73
|
+
assert_nil iter.next
|
74
|
+
assert_nil iter.next
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_iterator_to_a
|
78
|
+
iter = @query.each
|
79
|
+
assert_equal [{x: 1, y: 2, z: 3},{ x: 4, y: 5, z: 6 }, { x: 7, y: 8, z: 9 }], iter.to_a
|
80
|
+
|
81
|
+
iter = @query.each_ary
|
82
|
+
assert_equal [[1, 2, 3], [4, 5, 6], [7, 8, 9]], iter.to_a
|
83
|
+
|
84
|
+
iter = @db.prepare('select x from t').each_single_column
|
85
|
+
assert_equal [1, 4, 7], iter.to_a
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_iterator_enumerable_methods
|
89
|
+
mapped = @query.each.map { |row| row[:x] * 10 }
|
90
|
+
assert_equal [10, 40, 70], mapped
|
91
|
+
|
92
|
+
mapped = @query.each_ary.map { |row| row[1] * 10 }
|
93
|
+
assert_equal [20, 50, 80], mapped
|
94
|
+
|
95
|
+
query = @db.prepare('select z from t')
|
96
|
+
mapped = query.each_single_column.map { |v| v * 10 }
|
97
|
+
assert_equal [30, 60, 90], mapped
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_iterator_inspect
|
101
|
+
i = @query.each_ary
|
102
|
+
assert_match /^\#\<Extralite::Iterator:0x[0-9a-f]+ ary\>$/, i.inspect
|
103
|
+
end
|
104
|
+
end
|