extralite-bundle 1.26 → 2.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.
@@ -46,7 +46,7 @@ module Extralite
46
46
  SQLITE_LIMIT_VARIABLE_NUMBER = 9
47
47
  SQLITE_LIMIT_TRIGGER_DEPTH = 10
48
48
  SQLITE_LIMIT_WORKER_THREADS = 11
49
-
49
+
50
50
  SQLITE_OK = 0
51
51
  SQLITE_ERROR = 1
52
52
  SQLITE_INTERNAL = 2
@@ -1,4 +1,4 @@
1
1
  module Extralite
2
2
  # Extralite version
3
- VERSION = '1.26'
3
+ VERSION = '2.0'
4
4
  end
@@ -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 SQLite types.
72
+ # Hash with string keys and callable values for converting SQLITE types.
81
73
  SQLITE_TYPES = {}
82
74
  {
83
75
  %w'date' => date,
@@ -92,10 +84,6 @@ 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
 
@@ -112,7 +100,12 @@ module Sequel
112
100
  # The conversion procs to use for this database
113
101
  attr_reader :conversion_procs
114
102
 
115
- # Connect to the database. Since SQLite is a file based database,
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,16 +116,19 @@ module Sequel
123
116
  def connect(server)
124
117
  opts = server_opts(server)
125
118
  opts[:database] = ':memory:' if blank_object?(opts[:database])
126
- # sqlite3_opts = {}
127
- # sqlite3_opts[:readonly] = typecast_value_boolean(opts[:readonly]) if opts.has_key?(:readonly)
128
- db = ::Extralite::Database.new(opts[:database].to_s)#, sqlite3_opts)
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)}}
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
136
132
 
137
133
  class << db
138
134
  attr_reader :prepared_statements
@@ -142,9 +138,14 @@ module Sequel
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
 
@@ -163,24 +164,22 @@ module Sequel
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
 
172
- # @!visibility private
173
173
  def execute_insert(sql, opts=OPTS)
174
174
  _execute(:insert, sql, opts)
175
175
  end
176
176
 
177
- # @!visibility private
178
177
  def freeze
179
178
  @conversion_procs.freeze
180
179
  super
181
180
  end
182
181
 
183
- # Handle Integer and Float arguments, since SQLite can store timestamps as integers and floats.
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
@@ -202,32 +201,31 @@ module Sequel
202
201
  set_integer_booleans
203
202
  end
204
203
 
205
- # Yield an available connection. Rescue any Extralite::Error and turn
206
- # them into DatabaseErrors.
204
+ # Yield an available connection. Rescue
205
+ # any Extralite::Errors and turn them into DatabaseErrors.
207
206
  def _execute(type, sql, opts, &block)
208
- begin
209
- synchronize(opts[:server]) do |conn|
210
- return execute_prepared_statement(conn, type, sql, opts, &block) if sql.is_a?(Symbol)
211
- log_args = opts[:arguments]
212
- args = {}
213
- opts.fetch(:arguments, OPTS).each{|k, v| args[k] = prepared_statement_argument(v) }
214
- case type
215
- when :select
216
- log_connection_yield(sql, conn, log_args){conn.query(sql, args, &block)}
217
- when :insert
218
- log_connection_yield(sql, conn, log_args){conn.query(sql, args)}
219
- conn.last_insert_rowid
220
- when :update
221
- log_connection_yield(sql, conn, log_args){conn.query(sql, args)}
222
- conn.changes
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
227
 
230
- # The SQLite adapter does not need the pool to convert exceptions.
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
@@ -281,9 +279,10 @@ module Sequel
281
279
  log_sql << ")"
282
280
  end
283
281
  if block
284
- log_connection_yield(log_sql, conn, args){cps.execute(ps_args, &block)}
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.execute!(ps_args){|r|}}
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
@@ -293,10 +292,7 @@ module Sequel
293
292
  end
294
293
  end
295
294
 
296
- # # SQLite3 raises ArgumentError in addition to SQLite3::Exception in
297
- # # some cases, such as operations on a closed database.
298
295
  def database_error_classes
299
- #[Extralite::Error, ArgumentError]
300
296
  [::Extralite::Error]
301
297
  end
302
298
 
@@ -304,19 +300,14 @@ module Sequel
304
300
  Dataset
305
301
  end
306
302
 
307
- if USE_EXTENDED_RESULT_CODES
308
- # Support SQLite exception codes if ruby-sqlite3 supports them.
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
307
 
315
- # Dataset adapter for Sequel
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
 
@@ -332,42 +323,49 @@ module Sequel
332
323
 
333
324
  private
334
325
 
335
- # SQLite uses a : before the name of the argument for named
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
332
 
342
- # @!visibility private
343
333
  BindArgumentMethods = prepared_statements_module(:bind, ArgumentMapper)
344
- # @!visibility private
345
334
  PreparedStatementMethods = prepared_statements_module(:prepare, BindArgumentMethods)
346
335
 
347
- # @!visibility private
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, &block)
350
- # execute(sql) do |result|
351
- # cps = db.conversion_procs
352
- # type_procs = result.types.map{|t| cps[base_type_name(t)]}
353
- # j = -1
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
364
+ end
365
+
366
+ # Support regexp if using :setup_regexp_function Database option.
367
+ def supports_regexp?
368
+ db.allow_regexp?
371
369
  end
372
370
 
373
371
  private
@@ -39,9 +39,9 @@ def extralite_prepare
39
39
  db.prepare('select * from foo')
40
40
  end
41
41
 
42
- def extralite_run(stmt, count)
42
+ def extralite_run(query, count)
43
43
  # db = Extralite::Database.new(DB_PATH)
44
- results = stmt.query
44
+ results = query.to_a
45
45
  raise unless results.size == count
46
46
  end
47
47
 
@@ -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
@@ -285,7 +285,7 @@ end
285
285
  fn = "/tmp/extralite-#{rand(10000)}.db"
286
286
  db1 = Extralite::Database.new(fn)
287
287
  db2 = Extralite::Database.new(fn)
288
-
288
+
289
289
  db1.query('begin exclusive')
290
290
  assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
291
291
 
@@ -349,7 +349,7 @@ end
349
349
  end
350
350
 
351
351
  @db.query('create table t2 (v not null)')
352
-
352
+
353
353
  assert_raises(Extralite::Error) { @db.query('insert into t2 values (null)') }
354
354
  assert_equal Extralite::SQLITE_CONSTRAINT_NOTNULL, @db.errcode
355
355
  assert_equal 'NOT NULL constraint failed: t2.v', @db.errmsg
@@ -357,8 +357,8 @@ end
357
357
 
358
358
 
359
359
  def test_close_with_open_prepared_statement
360
- stmt = @db.prepare('select * from t')
361
- stmt.query
360
+ query = @db.prepare('select * from t')
361
+ query.next
362
362
  @db.close
363
363
  end
364
364
  end
@@ -439,15 +439,15 @@ class ScenarioTest < MiniTest::Test
439
439
  @db.query('select 2')
440
440
  assert_equal ['select 1', 'select 2'], sqls
441
441
 
442
- stmt = @db.prepare('select 3')
443
-
444
- stmt.query
442
+ query = @db.prepare('select 3')
443
+
444
+ query.to_a
445
445
  assert_equal ['select 1', 'select 2', 'select 3'], sqls
446
446
 
447
447
  # turn off
448
448
  @db.trace
449
449
 
450
- stmt.query
450
+ query.to_a
451
451
 
452
452
  @db.query('select 4')
453
453
  assert_equal ['select 1', 'select 2', 'select 3'], sqls
@@ -20,7 +20,7 @@ class ExtraliteTest < MiniTest::Test
20
20
 
21
21
  assert_operator 0, :<, a[0]
22
22
  assert_operator a[0], :<=, a[1]
23
-
23
+
24
24
  assert_equal a, b
25
25
  assert_equal a, c
26
26
 
@@ -0,0 +1,99 @@
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
+ end