sqlite3 0.0.2 → 0.0.3
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.
- data/Rakefile +7 -4
- data/VERSION +1 -1
- data/lib/sqlite3.rb +15 -0
- data/lib/sqlite3/database.rb +16 -339
- data/lib/sqlite3/driver/ffi/api.rb +65 -218
- data/lib/sqlite3/driver/ffi/driver.rb +141 -194
- data/lib/sqlite3/encoding.rb +43 -0
- data/lib/sqlite3/errors.rb +0 -2
- data/lib/sqlite3/pragmas.rb +0 -2
- data/lib/sqlite3/resultset.rb +8 -8
- data/lib/sqlite3/statement.rb +4 -18
- data/lib/sqlite3/translator.rb +0 -3
- data/lib/sqlite3/value.rb +0 -2
- data/test/fixtures/SQLite.gif +0 -0
- data/test/test_database_initialization.rb +25 -0
- data/test/test_database_queries_utf_16.rb +80 -0
- data/test/test_database_queries_utf_8.rb +80 -0
- metadata +18 -10
- data/test/test_database_queries.rb +0 -29
data/Rakefile
CHANGED
@@ -16,12 +16,15 @@ begin
|
|
16
16
|
gem.add_development_dependency "test-unit", ">= 2.0"
|
17
17
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
18
18
|
gem.post_install_message = <<-EOM
|
19
|
-
|
20
|
-
WARNING!
|
21
|
-
|
19
|
+
==== WARNING ===================================================================
|
22
20
|
This is an early alpha version of SQLite3/Ruby FFI bindings!
|
23
|
-
|
21
|
+
Currently we support Ruby 1.9 ONLY.
|
22
|
+
|
23
|
+
If you need native bindings for Ruby 1.8 - install sqlite3-ruby instead.
|
24
|
+
You may need to uninstall this sqlite3 gem as well.
|
24
25
|
|
26
|
+
Thank you for installing sqlite3 gem! Suggestions: qoobaa@gmail.com
|
27
|
+
================================================================================
|
25
28
|
EOM
|
26
29
|
end
|
27
30
|
rescue LoadError
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.3
|
data/lib/sqlite3.rb
CHANGED
@@ -1 +1,16 @@
|
|
1
|
+
# start (required by translator)
|
2
|
+
require "time"
|
3
|
+
require "date"
|
4
|
+
# end
|
5
|
+
|
6
|
+
require "ffi"
|
7
|
+
|
8
|
+
require "sqlite3/constants"
|
9
|
+
require "sqlite3/errors"
|
10
|
+
require "sqlite3/pragmas"
|
11
|
+
require "sqlite3/statement"
|
12
|
+
require "sqlite3/translator"
|
13
|
+
require "sqlite3/resultset"
|
14
|
+
require "sqlite3/value"
|
15
|
+
require "sqlite3/encoding"
|
1
16
|
require "sqlite3/database"
|
data/lib/sqlite3/database.rb
CHANGED
@@ -1,10 +1,3 @@
|
|
1
|
-
require "sqlite3/constants"
|
2
|
-
require "sqlite3/errors"
|
3
|
-
require "sqlite3/pragmas"
|
4
|
-
require "sqlite3/statement"
|
5
|
-
require "sqlite3/translator"
|
6
|
-
require "sqlite3/value"
|
7
|
-
|
8
1
|
module SQLite3
|
9
2
|
|
10
3
|
# The Database class encapsulates a single connection to a SQLite3 database.
|
@@ -64,38 +57,41 @@ module SQLite3
|
|
64
57
|
# database.
|
65
58
|
attr_accessor :type_translation
|
66
59
|
|
60
|
+
# Encoding used to comunicate with database.
|
61
|
+
attr_reader :encoding
|
62
|
+
|
67
63
|
# Create a new Database object that opens the given file. If utf16
|
68
64
|
# is +true+, the filename is interpreted as a UTF-16 encoded string.
|
69
65
|
#
|
70
66
|
# By default, the new database will return result rows as arrays
|
71
67
|
# (#results_as_hash) and has type translation disabled (#type_translation=).
|
72
68
|
def initialize(file_name, options = {})
|
73
|
-
|
69
|
+
@encoding = Encoding.find(options.fetch(:encoding, "utf-8"))
|
70
|
+
|
74
71
|
load_driver(options[:driver])
|
75
72
|
|
76
73
|
@statement_factory = options[:statement_factory] || Statement
|
77
74
|
|
78
|
-
result, @handle = @driver.open(file_name,
|
75
|
+
result, @handle = @driver.open(file_name, Encoding.utf_16?(@encoding))
|
79
76
|
Error.check(result, self, "could not open database")
|
80
77
|
|
81
78
|
@closed = false
|
82
|
-
@results_as_hash = options.fetch(:results_as_hash,false)
|
83
|
-
@type_translation = options.fetch(:type_translation,false)
|
79
|
+
@results_as_hash = options.fetch(:results_as_hash, false)
|
80
|
+
@type_translation = options.fetch(:type_translation, false)
|
84
81
|
@translator = nil
|
85
82
|
@transaction_active = false
|
86
83
|
end
|
87
84
|
|
88
85
|
# Return +true+ if the string is a valid (ie, parsable) SQL statement, and
|
89
|
-
# +false+ otherwise
|
90
|
-
|
91
|
-
|
92
|
-
@driver.complete?(string, utf16)
|
86
|
+
# +false+ otherwise
|
87
|
+
def complete?(string)
|
88
|
+
@driver.complete?(string)
|
93
89
|
end
|
94
90
|
|
95
91
|
# Return a string describing the last error to have occurred with this
|
96
92
|
# database.
|
97
|
-
def errmsg
|
98
|
-
@driver.errmsg(@handle
|
93
|
+
def errmsg
|
94
|
+
@driver.errmsg(@handle)
|
99
95
|
end
|
100
96
|
|
101
97
|
# Return an integer representing the last error to have occurred with this
|
@@ -128,30 +124,13 @@ module SQLite3
|
|
128
124
|
@closed
|
129
125
|
end
|
130
126
|
|
131
|
-
# Installs (or removes) a block that will be invoked for every SQL
|
132
|
-
# statement executed. The block receives a two parameters: the +data+
|
133
|
-
# argument, and the SQL statement executed. If the block is +nil+,
|
134
|
-
# any existing tracer will be uninstalled.
|
135
|
-
def trace(data = nil, &block)
|
136
|
-
@driver.trace(@handle, data, &block)
|
137
|
-
end
|
138
|
-
|
139
|
-
# Installs (or removes) a block that will be invoked for every access
|
140
|
-
# to the database. If the block returns 0 (or +nil+), the statement
|
141
|
-
# is allowed to proceed. Returning 1 causes an authorization error to
|
142
|
-
# occur, and returning 2 causes the access to be silently denied.
|
143
|
-
def authorizer(data = nil, &block)
|
144
|
-
result = @driver.set_authorizer(@handle, data, &block)
|
145
|
-
Error.check(result, self)
|
146
|
-
end
|
147
|
-
|
148
127
|
# Returns a Statement object representing the given SQL. This does not
|
149
128
|
# execute the statement; it merely prepares the statement for execution.
|
150
129
|
#
|
151
130
|
# The Statement can then be executed using Statement#execute.
|
152
131
|
#
|
153
132
|
def prepare(sql)
|
154
|
-
stmt = @statement_factory.new(self, sql)
|
133
|
+
stmt = @statement_factory.new(self, sql, Encoding.utf_16?(@encoding))
|
155
134
|
if block_given?
|
156
135
|
begin
|
157
136
|
yield stmt
|
@@ -296,20 +275,6 @@ module SQLite3
|
|
296
275
|
@driver.interrupt(@handle)
|
297
276
|
end
|
298
277
|
|
299
|
-
# Register a busy handler with this database instance. When a requested
|
300
|
-
# resource is busy, this handler will be invoked. If the handler returns
|
301
|
-
# +false+, the operation will be aborted; otherwise, the resource will
|
302
|
-
# be requested again.
|
303
|
-
#
|
304
|
-
# The handler will be invoked with the name of the resource that was
|
305
|
-
# busy, and the number of times it has been retried.
|
306
|
-
#
|
307
|
-
# See also the mutually exclusive #busy_timeout.
|
308
|
-
def busy_handler(data = nil, &block) # :yields: data, retries
|
309
|
-
result = @driver.busy_handler(@handle, data, &block)
|
310
|
-
Error.check(result, self)
|
311
|
-
end
|
312
|
-
|
313
278
|
# Indicates that if a request for a resource terminates because that
|
314
279
|
# resource is busy, SQLite should sleep and retry for up to the indicated
|
315
280
|
# number of milliseconds. By default, SQLite does not retry
|
@@ -322,210 +287,6 @@ module SQLite3
|
|
322
287
|
Error.check(result, self)
|
323
288
|
end
|
324
289
|
|
325
|
-
# Creates a new function for use in SQL statements. It will be added as
|
326
|
-
# +name+, with the given +arity+. (For variable arity functions, use
|
327
|
-
# -1 for the arity.)
|
328
|
-
#
|
329
|
-
# The block should accept at least one parameter--the FunctionProxy
|
330
|
-
# instance that wraps this function invocation--and any other
|
331
|
-
# arguments it needs (up to its arity).
|
332
|
-
#
|
333
|
-
# The block does not return a value directly. Instead, it will invoke
|
334
|
-
# the FunctionProxy#set_result method on the +func+ parameter and
|
335
|
-
# indicate the return value that way.
|
336
|
-
#
|
337
|
-
# Example:
|
338
|
-
#
|
339
|
-
# db.create_function("maim", 1) do |func, value|
|
340
|
-
# if value.nil?
|
341
|
-
# func.result = nil
|
342
|
-
# else
|
343
|
-
# func.result = value.split(//).sort.join
|
344
|
-
# end
|
345
|
-
# end
|
346
|
-
#
|
347
|
-
# puts db.get_first_value("select maim(name) from table")
|
348
|
-
def create_function(name, arity, text_rep = Constants::TextRep::ANY, &block) # :yields: func, *args
|
349
|
-
# begin
|
350
|
-
callback = proc do |func, *args|
|
351
|
-
begin
|
352
|
-
block.call(FunctionProxy.new(@driver, func), *args.map { |v| Value.new(self, v) })
|
353
|
-
rescue StandardError, Exception => e
|
354
|
-
@driver.result_error(func, "#{e.message} (#{e.class})", -1)
|
355
|
-
end
|
356
|
-
end
|
357
|
-
|
358
|
-
result = @driver.create_function(@handle, name, arity, text_rep, nil, callback, nil, nil)
|
359
|
-
Error.check(result, self)
|
360
|
-
|
361
|
-
self
|
362
|
-
end
|
363
|
-
|
364
|
-
# Creates a new aggregate function for use in SQL statements. Aggregate
|
365
|
-
# functions are functions that apply over every row in the result set,
|
366
|
-
# instead of over just a single row. (A very common aggregate function
|
367
|
-
# is the "count" function, for determining the number of rows that match
|
368
|
-
# a query.)
|
369
|
-
#
|
370
|
-
# The new function will be added as +name+, with the given +arity+. (For
|
371
|
-
# variable arity functions, use -1 for the arity.)
|
372
|
-
#
|
373
|
-
# The +step+ parameter must be a proc object that accepts as its first
|
374
|
-
# parameter a FunctionProxy instance (representing the function
|
375
|
-
# invocation), with any subsequent parameters (up to the function's arity).
|
376
|
-
# The +step+ callback will be invoked once for each row of the result set.
|
377
|
-
#
|
378
|
-
# The +finalize+ parameter must be a +proc+ object that accepts only a
|
379
|
-
# single parameter, the FunctionProxy instance representing the current
|
380
|
-
# function invocation. It should invoke FunctionProxy#set_result to
|
381
|
-
# store the result of the function.
|
382
|
-
#
|
383
|
-
# Example:
|
384
|
-
#
|
385
|
-
# db.create_aggregate("lengths", 1) do
|
386
|
-
# step do |func, value|
|
387
|
-
# func[:total] ||= 0
|
388
|
-
# func[:total] += (value ? value.length : 0)
|
389
|
-
# end
|
390
|
-
#
|
391
|
-
# finalize do |func|
|
392
|
-
# func.set_result(func[:total] || 0)
|
393
|
-
# end
|
394
|
-
# end
|
395
|
-
#
|
396
|
-
# puts db.get_first_value("select lengths(name) from table")
|
397
|
-
#
|
398
|
-
# See also #create_aggregate_handler for a more object-oriented approach to
|
399
|
-
# aggregate functions.
|
400
|
-
def create_aggregate(name, arity, step = nil, finalize = nil, text_rep = Constants::TextRep::ANY, &block)
|
401
|
-
# begin
|
402
|
-
if block
|
403
|
-
proxy = AggregateDefinitionProxy.new
|
404
|
-
proxy.instance_eval(&block)
|
405
|
-
step ||= proxy.step_callback
|
406
|
-
finalize ||= proxy.finalize_callback
|
407
|
-
end
|
408
|
-
|
409
|
-
step_callback = proc do |func, *args|
|
410
|
-
ctx = @driver.aggregate_context(func)
|
411
|
-
unless ctx[:__error]
|
412
|
-
begin
|
413
|
-
step.call(FunctionProxy.new(@driver, func, ctx), *args.map { |v| Value.new(self, v) })
|
414
|
-
rescue Exception => e
|
415
|
-
ctx[:__error] = e
|
416
|
-
end
|
417
|
-
end
|
418
|
-
end
|
419
|
-
|
420
|
-
finalize_callback = proc do |func|
|
421
|
-
ctx = @driver.aggregate_context(func)
|
422
|
-
unless ctx[:__error]
|
423
|
-
begin
|
424
|
-
finalize.call(FunctionProxy.new(@driver, func, ctx))
|
425
|
-
rescue Exception => e
|
426
|
-
@driver.result_error(func, "#{e.message} (#{e.class})", -1)
|
427
|
-
end
|
428
|
-
else
|
429
|
-
e = ctx[:__error]
|
430
|
-
@driver.result_error(func, "#{e.message} (#{e.class})", -1)
|
431
|
-
end
|
432
|
-
end
|
433
|
-
|
434
|
-
result = @driver.create_function(@handle, name, arity, text_rep, nil, nil, step_callback, finalize_callback)
|
435
|
-
Error.check(result, self)
|
436
|
-
|
437
|
-
self
|
438
|
-
end
|
439
|
-
|
440
|
-
# This is another approach to creating an aggregate function (see
|
441
|
-
# #create_aggregate). Instead of explicitly specifying the name,
|
442
|
-
# callbacks, arity, and type, you specify a factory object
|
443
|
-
# (the "handler") that knows how to obtain all of that information. The
|
444
|
-
# handler should respond to the following messages:
|
445
|
-
#
|
446
|
-
# +arity+:: corresponds to the +arity+ parameter of #create_aggregate. This
|
447
|
-
# message is optional, and if the handler does not respond to it,
|
448
|
-
# the function will have an arity of -1.
|
449
|
-
# +name+:: this is the name of the function. The handler _must_ implement
|
450
|
-
# this message.
|
451
|
-
# +new+:: this must be implemented by the handler. It should return a new
|
452
|
-
# instance of the object that will handle a specific invocation of
|
453
|
-
# the function.
|
454
|
-
#
|
455
|
-
# The handler instance (the object returned by the +new+ message, described
|
456
|
-
# above), must respond to the following messages:
|
457
|
-
#
|
458
|
-
# +step+:: this is the method that will be called for each step of the
|
459
|
-
# aggregate function's evaluation. It should implement the same
|
460
|
-
# signature as the +step+ callback for #create_aggregate.
|
461
|
-
# +finalize+:: this is the method that will be called to finalize the
|
462
|
-
# aggregate function's evaluation. It should implement the
|
463
|
-
# same signature as the +finalize+ callback for
|
464
|
-
# #create_aggregate.
|
465
|
-
#
|
466
|
-
# Example:
|
467
|
-
#
|
468
|
-
# class LengthsAggregateHandler
|
469
|
-
# def self.arity; 1; end
|
470
|
-
#
|
471
|
-
# def initialize
|
472
|
-
# @total = 0
|
473
|
-
# end
|
474
|
-
#
|
475
|
-
# def step(ctx, name)
|
476
|
-
# @total += (name ? name.length : 0)
|
477
|
-
# end
|
478
|
-
#
|
479
|
-
# def finalize(ctx)
|
480
|
-
# ctx.set_result(@total)
|
481
|
-
# end
|
482
|
-
# end
|
483
|
-
#
|
484
|
-
# db.create_aggregate_handler(LengthsAggregateHandler)
|
485
|
-
# puts db.get_first_value("select lengths(name) from A")
|
486
|
-
def create_aggregate_handler(handler)
|
487
|
-
arity = -1
|
488
|
-
text_rep = Constants::TextRep::ANY
|
489
|
-
|
490
|
-
arity = handler.arity if handler.respond_to?(:arity)
|
491
|
-
text_rep = handler.text_rep if handler.respond_to?(:text_rep)
|
492
|
-
name = handler.name
|
493
|
-
|
494
|
-
step = proc do |func,*args|
|
495
|
-
ctx = @driver.aggregate_context(func)
|
496
|
-
unless ctx[:__error]
|
497
|
-
ctx[:handler] ||= handler.new
|
498
|
-
begin
|
499
|
-
ctx[:handler].step(FunctionProxy.new(@driver, func, ctx), *args.map{|v| Value.new(self,v)})
|
500
|
-
rescue Exception, StandardError => e
|
501
|
-
ctx[:__error] = e
|
502
|
-
end
|
503
|
-
end
|
504
|
-
end
|
505
|
-
|
506
|
-
finalize = proc do |func|
|
507
|
-
ctx = @driver.aggregate_context(func)
|
508
|
-
unless ctx[:__error]
|
509
|
-
ctx[:handler] ||= handler.new
|
510
|
-
begin
|
511
|
-
ctx[:handler].finalize(FunctionProxy.new(@driver, func, ctx))
|
512
|
-
rescue Exception => e
|
513
|
-
ctx[:__error] = e
|
514
|
-
end
|
515
|
-
end
|
516
|
-
|
517
|
-
if ctx[:__error]
|
518
|
-
e = ctx[:__error]
|
519
|
-
@driver.sqlite3_result_error(func, "#{e.message} (#{e.class})", -1)
|
520
|
-
end
|
521
|
-
end
|
522
|
-
|
523
|
-
result = @driver.create_function(@handle, name, arity, text_rep, nil, nil, step, finalize)
|
524
|
-
Error.check(result, self)
|
525
|
-
|
526
|
-
self
|
527
|
-
end
|
528
|
-
|
529
290
|
# Begins a new transaction. Note that nested transactions are not allowed
|
530
291
|
# by SQLite, so attempting to nest a transaction will result in a runtime
|
531
292
|
# exception.
|
@@ -586,6 +347,8 @@ module SQLite3
|
|
586
347
|
@transaction_active
|
587
348
|
end
|
588
349
|
|
350
|
+
private
|
351
|
+
|
589
352
|
# Loads the corresponding driver, or if it is nil, attempts to locate a
|
590
353
|
# suitable driver.
|
591
354
|
def load_driver(driver)
|
@@ -611,93 +374,7 @@ module SQLite3
|
|
611
374
|
|
612
375
|
@driver = driver.new
|
613
376
|
end
|
614
|
-
private :load_driver
|
615
|
-
|
616
|
-
# A helper class for dealing with custom functions (see #create_function,
|
617
|
-
# #create_aggregate, and #create_aggregate_handler). It encapsulates the
|
618
|
-
# opaque function object that represents the current invocation. It also
|
619
|
-
# provides more convenient access to the API functions that operate on
|
620
|
-
# the function object.
|
621
|
-
#
|
622
|
-
# This class will almost _always_ be instantiated indirectly, by working
|
623
|
-
# with the create methods mentioned above.
|
624
|
-
class FunctionProxy
|
625
|
-
|
626
|
-
# Create a new FunctionProxy that encapsulates the given +func+ object.
|
627
|
-
# If context is non-nil, the functions context will be set to that. If
|
628
|
-
# it is non-nil, it must quack like a Hash. If it is nil, then none of
|
629
|
-
# the context functions will be available.
|
630
|
-
def initialize(driver, func, context = nil)
|
631
|
-
@driver = driver
|
632
|
-
@func = func
|
633
|
-
@context = context
|
634
|
-
end
|
635
|
-
|
636
|
-
# Calls #set_result to set the result of this function.
|
637
|
-
def result=(result)
|
638
|
-
set_result(result)
|
639
|
-
end
|
640
|
-
|
641
|
-
# Set the result of the function to the given value. The function will
|
642
|
-
# then return this value.
|
643
|
-
def set_result(result, utf16 = false)
|
644
|
-
@driver.result_text(@func, result, utf16)
|
645
|
-
end
|
646
|
-
|
647
|
-
# Set the result of the function to the given error message.
|
648
|
-
# The function will then return that error.
|
649
|
-
def set_error(error)
|
650
|
-
@driver.result_error(@func, error.to_s, -1)
|
651
|
-
end
|
652
|
-
|
653
|
-
# (Only available to aggregate functions.) Returns the number of rows
|
654
|
-
# that the aggregate has processed so far. This will include the current
|
655
|
-
# row, and so will always return at least 1.
|
656
|
-
def count
|
657
|
-
ensure_aggregate!
|
658
|
-
@driver.aggregate_count(@func)
|
659
|
-
end
|
660
|
-
|
661
|
-
# Returns the value with the given key from the context. This is only
|
662
|
-
# available to aggregate functions.
|
663
|
-
def [](key)
|
664
|
-
ensure_aggregate!
|
665
|
-
@context[key]
|
666
|
-
end
|
667
|
-
|
668
|
-
# Sets the value with the given key in the context. This is only
|
669
|
-
# available to aggregate functions.
|
670
|
-
def []=(key, value)
|
671
|
-
ensure_aggregate!
|
672
|
-
@context[key] = value
|
673
|
-
end
|
674
|
-
|
675
|
-
# A function for performing a sanity check, to ensure that the function
|
676
|
-
# being invoked is an aggregate function. This is implied by the
|
677
|
-
# existence of the context variable.
|
678
|
-
def ensure_aggregate!
|
679
|
-
unless @context
|
680
|
-
raise MisuseException, "function is not an aggregate"
|
681
|
-
end
|
682
|
-
end
|
683
|
-
private :ensure_aggregate!
|
684
|
-
|
685
|
-
end
|
686
|
-
|
687
|
-
# A proxy used for defining the callbacks to an aggregate function.
|
688
|
-
class AggregateDefinitionProxy # :nodoc:
|
689
|
-
attr_reader :step_callback, :finalize_callback
|
690
|
-
|
691
|
-
def step(&block)
|
692
|
-
@step_callback = block
|
693
|
-
end
|
694
|
-
|
695
|
-
def finalize(&block)
|
696
|
-
@finalize_callback = block
|
697
|
-
end
|
698
|
-
end
|
699
377
|
|
700
378
|
end
|
701
|
-
|
702
379
|
end
|
703
380
|
|