extralite 1.27 → 2.1
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/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
|