sqlite3-fiddle 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6ba7e0a8636f61d28a0de1123961db35824219ce
4
+ data.tar.gz: 02191a8cd05e7ae34bda8da30d2fd0205f41afe4
5
+ SHA512:
6
+ metadata.gz: ddb4071faf99e88c08b1011a8c7d997dc12504efc42ef7f1d6ea750bbe1243dfab9f7012f7181a7d3e8e6405fdc3448ffb1feb16b4dac92e01c3c8bd22d17e6f
7
+ data.tar.gz: 044093ee3121e9a557093dff4df8e2484a02797e41bbb744bede21b91a3a632032f1c1612542b111356a27fc60b8471bf19aac2e94f6a0a7616a5047ed8af76a
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
@@ -0,0 +1 @@
1
+ 2.1
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.5
4
+ before_install:
5
+ - sudo apt-get update -qq
6
+ - sudo apt-get install -qq libsqlite3-0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sqlite3-fiddle.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ Copyright (c) 2015, Vincent Landgraf (vilandgr+sqlite@googlemail.com)
2
+ Copyright (c) 2004, Jamis Buck (jamis@jamisbuck.org) - original author
3
+ All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ * Redistributions of source code must retain the above copyright notice,
9
+ this list of conditions and the following disclaimer.
10
+
11
+ * Redistributions in binary form must reproduce the above copyright
12
+ notice, this list of conditions and the following disclaimer in the
13
+ documentation and/or other materials provided with the distribution.
14
+
15
+ * The names of its contributors may not be used to endorse or promote
16
+ products derived from this software without specific prior written
17
+ permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,46 @@
1
+ # Sqlite3 Fiddle [![Build Status](https://travis-ci.org/threez/sqlite3-fiddle.svg)](https://travis-ci.org/threez/sqlite3-fiddle)
2
+
3
+ This is a fork of the original sqlite3-ruby gem. It replaces the original C extension with a fiddle based replacement.
4
+
5
+ Sometimes the problem is, that one has a system like a synology NAS, that has a ruby interpreter and a recent version
6
+ of libsqlite3, but no c compiler and headers installed. To not manually compile the extension i replaced its
7
+ base functionality with ruby fiddle. A stdlib to dynamically use shared libraries.
8
+
9
+ I copied a lot of the original code, so that this replacement behaves exactly like the original. There is however
10
+ no guarantee, that it actually behaves in all cases exactly the same. The problem is, that there is a long history
11
+ in the c code and it is in parts not very clear or easy to understand (no offence). Especially the function and
12
+ aggregate interface is very unituitively implemented. The test suite used is the original unchanged.
13
+
14
+ So far all test are passing with ruby 2.1.x and a recent version of libsqlite3.
15
+
16
+ ## Installation
17
+
18
+ Add this line to your application's Gemfile:
19
+
20
+ ```ruby
21
+ gem 'sqlite3-fiddle'
22
+ ```
23
+
24
+ And then execute:
25
+
26
+ $ bundle
27
+
28
+ Or install it yourself as:
29
+
30
+ $ gem install sqlite3-fiddle
31
+
32
+ ## Usage
33
+
34
+ Should be used like the [sqlite3-ruby](https://github.com/sparklemotion/sqlite3-ruby) gem.
35
+
36
+ By default the libsqlite3 is searched in the regular way that shares libraries are found. If one wants to explicitly change the location to the libsqlite3, do it either using the global `$LIBSQLITE3` or using the environment variable `LIBSQLITE3`. In both cases the full path inclusive extension has to be used to specify the lib to use. Example:
37
+
38
+ LIBSQLITE3=/usr/local/Cellar/sqlite/3.8.8.3/lib/libsqlite3.dylib rake
39
+
40
+ ## Contributing
41
+
42
+ 1. Fork it ( https://github.com/[my-github-username]/sqlite3-fiddle/fork )
43
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
44
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
45
+ 4. Push to the branch (`git push origin my-new-feature`)
46
+ 5. Create a new Pull Request
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.test_files = Dir.glob('test/**/test_*.rb')
7
+ end
8
+
9
+ task(default: :test)
Binary file
@@ -0,0 +1,9 @@
1
+ require 'sqlite3/constants'
2
+ require 'sqlite3/driver'
3
+ require 'sqlite3/resultset'
4
+ require 'sqlite3/statement'
5
+ require 'sqlite3/pragmas'
6
+ require 'sqlite3/value'
7
+ require 'sqlite3/database'
8
+ require 'sqlite3/backup'
9
+ require 'sqlite3/version'
@@ -0,0 +1,26 @@
1
+ module SQLite3
2
+ class Backup
3
+ def initialize(dest, destname, source, sourcename)
4
+ fail "Destination db closed" if dest.closed?
5
+ fail "Source db closed" if source.closed?
6
+ @backup = Driver.sqlite3_backup_init(dest.handle, destname,
7
+ source.handle, sourcename)
8
+ end
9
+
10
+ def step(page)
11
+ Driver.sqlite3_backup_step(@backup, page)
12
+ end
13
+
14
+ def finish
15
+ Driver.sqlite3_backup_finish(@backup)
16
+ end
17
+
18
+ def remaining
19
+ Driver.sqlite3_backup_remaining(@backup)
20
+ end
21
+
22
+ def pagecount
23
+ Driver.sqlite3_backup_pagecount(@backup)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,140 @@
1
+ module SQLite3
2
+ SQLITE_OK = 0
3
+ SQLITE_ROW = 100
4
+ SQLITE_DONE = 101
5
+ SQLITE_INTEGER = 1
6
+ SQLITE_FLOAT = 2
7
+ SQLITE_TEXT = 3
8
+ SQLITE_BLOB = 4
9
+ SQLITE_NULL = 5
10
+
11
+ module Constants
12
+ module ErrorCode
13
+ OK = SQLITE_OK
14
+ DONE = SQLITE_DONE
15
+ end
16
+
17
+ module TextRep
18
+ UTF8 = 1
19
+ UTF16LE = 2
20
+ UTF16BE = 3
21
+ UTF16 = 4
22
+ ANY = 5
23
+ end
24
+
25
+ module ColumnType
26
+ INTEGER = 1
27
+ FLOAT = 2
28
+ TEXT = 3
29
+ BLOB = 4
30
+ NULL = 5
31
+ end
32
+ end
33
+
34
+ class Exception < ::StandardError
35
+ @code = 0
36
+
37
+ # The numeric error code that this exception represents.
38
+ def self.code
39
+ @code
40
+ end
41
+
42
+ # A convenience for accessing the error code for this exception.
43
+ def code
44
+ self.class.code
45
+ end
46
+ end
47
+
48
+ ERROR = 1 # SQL error or missing database
49
+ INTERNAL = 2 # An internal logic error in SQLite
50
+ PERM = 3 # Access permission denied
51
+ ABORT = 4 # Callback routine requested an abort
52
+ BUSY = 5 # The database file is locked
53
+ LOCKED = 6 # A table in the database is locked
54
+ NOMEM = 7 # A malloc() failed
55
+ READONLY = 8 # Attempt to write a readonly database
56
+ INTERRUPT = 9 # Operation terminated by sqlite_interrupt()
57
+ IOERR = 10 # Some kind of disk I/O error occurred
58
+ CORRUPT = 11 # The database disk image is malformed
59
+ NOTFOUND = 12 # (Internal Only) Table or record not found
60
+ FULL = 13 # Insertion failed because database is full
61
+ CANTOPEN = 14 # Unable to open the database file
62
+ PROTOCOL = 15 # Database lock protocol error
63
+ EMPTY = 16 # (Internal Only) Database table is empty
64
+ SCHEMA = 17 # The database schema changed
65
+ TOOBIG = 18 # Too much data for one row of a table
66
+ CONSTRAINT = 19 # Abort due to contraint violation
67
+ MISMATCH = 20 # Data type mismatch
68
+ MISUSE = 21 # Library used incorrectly
69
+ NOLFS = 22 # Uses OS features not supported on host
70
+ AUTH = 23 # Authorization denied
71
+
72
+ OK = 0
73
+ DENY = 1 # Abort the SQL statement with an error
74
+ IGNORE = 2 # Don't allow access, but don't generate an error
75
+
76
+ class SQLException < Exception; end
77
+ class InternalException < Exception; end
78
+ class PermissionException < Exception; end
79
+ class AbortException < Exception; end
80
+ class BusyException < Exception; end
81
+ class LockedException < Exception; end
82
+ class MemoryException < Exception; end
83
+ class ReadOnlyException < Exception; end
84
+ class InterruptException < Exception; end
85
+ class IOException < Exception; end
86
+ class CorruptException < Exception; end
87
+ class NotFoundException < Exception; end
88
+ class FullException < Exception; end
89
+ class CantOpenException < Exception; end
90
+ class ProtocolException < Exception; end
91
+ class EmptyException < Exception; end
92
+ class SchemaChangedException < Exception; end
93
+ class TooBigException < Exception; end
94
+ class ConstraintException < Exception; end
95
+ class MismatchException < Exception; end
96
+ class MisuseException < Exception; end
97
+ class UnsupportedException < Exception; end
98
+ class AuthorizationException < Exception; end
99
+ class FormatException < Exception; end
100
+ class RangeException < Exception; end
101
+ class NotADatabaseException < Exception; end
102
+
103
+ ERR_EXEPTION_MAPPING = {
104
+ ERROR => SQLException,
105
+ INTERNAL => InternalException,
106
+ PERM => PermissionException,
107
+ ABORT => AbortException,
108
+ BUSY => BusyException,
109
+ LOCKED => LockedException,
110
+ NOMEM => MemoryException,
111
+ READONLY => ReadOnlyException,
112
+ INTERRUPT => InterruptException,
113
+ IOERR => IOException,
114
+ CORRUPT => CorruptException,
115
+ NOTFOUND => NotFoundException,
116
+ FULL => FullException,
117
+ CANTOPEN => CantOpenException,
118
+ PROTOCOL => ProtocolException,
119
+ EMPTY => EmptyException,
120
+ SCHEMA => SchemaChangedException,
121
+ TOOBIG => TooBigException,
122
+ CONSTRAINT => ConstraintException,
123
+ MISMATCH => MismatchException,
124
+ MISUSE => MisuseException,
125
+ NOLFS => UnsupportedException,
126
+ AUTH => AuthorizationException
127
+ }.freeze
128
+
129
+ ERR_EXEPTION_MAPPING.each do |code, klass|
130
+ klass.instance_variable_set('@code', code)
131
+ end
132
+ end
133
+
134
+ class SQLite3::Blob < String; end
135
+
136
+ class String
137
+ def to_blob
138
+ SQLite3::Blob.new( self )
139
+ end
140
+ end
@@ -0,0 +1,823 @@
1
+ module SQLite3
2
+ class Database
3
+ OPEN_READONLY = 0x00000001 # Ok for sqlite3_open_v2()
4
+ OPEN_READWRITE = 0x00000002 # Ok for sqlite3_open_v2()
5
+ OPEN_CREATE = 0x00000004 # Ok for sqlite3_open_v2()
6
+ OPEN_DELETEONCLOSE = 0x00000008 # VFS only
7
+ OPEN_EXCLUSIVE = 0x00000010 # VFS only
8
+ OPEN_AUTOPROXY = 0x00000020 # VFS only
9
+ OPEN_URI = 0x00000040 # Ok for sqlite3_open_v2()
10
+ OPEN_MEMORY = 0x00000080 # Ok for sqlite3_open_v2()
11
+ OPEN_MAIN_DB = 0x00000100 # VFS only
12
+ OPEN_TEMP_DB = 0x00000200 # VFS only
13
+ OPEN_TRANSIENT_DB = 0x00000400 # VFS only
14
+ OPEN_MAIN_JOURNAL = 0x00000800 # VFS only
15
+ OPEN_TEMP_JOURNAL = 0x00001000 # VFS only
16
+ OPEN_SUBJOURNAL = 0x00002000 # VFS only
17
+ OPEN_MASTER_JOURNAL = 0x00004000 # VFS only
18
+ OPEN_NOMUTEX = 0x00008000 # Ok for sqlite3_open_v2()
19
+ OPEN_FULLMUTEX = 0x00010000 # Ok for sqlite3_open_v2()
20
+ OPEN_SHAREDCACHE = 0x00020000 # Ok for sqlite3_open_v2()
21
+ OPEN_PRIVATECACHE = 0x00040000 # Ok for sqlite3_open_v2()
22
+ OPEN_WAL = 0x00080000 # VFS only
23
+
24
+ SQLITE_UTF8 = 1
25
+
26
+ include Fiddle
27
+ include Pragmas
28
+
29
+ FUNC_PARAMS = [TYPE_VOIDP, TYPE_INT, TYPE_VOIDP].freeze
30
+ TRACE_PARAMS = [TYPE_VOIDP, TYPE_VOIDP].freeze
31
+ AUTH_PARAMS = [
32
+ TYPE_VOIDP, TYPE_INT, TYPE_VOIDP, TYPE_VOIDP, TYPE_VOIDP, TYPE_VOIDP
33
+ ].freeze
34
+ COLLATION_PARAMS = [
35
+ TYPE_VOIDP, TYPE_INT, TYPE_VOIDP, TYPE_INT, TYPE_VOIDP
36
+ ].freeze
37
+ BUSY_PARAMS = [TYPE_VOIDP, TYPE_INT].freeze
38
+
39
+ # A helper class for dealing with custom functions (see #create_function,
40
+ # #create_aggregate, and #create_aggregate_handler). It encapsulates the
41
+ # opaque function object that represents the current invocation. It also
42
+ # provides more convenient access to the API functions that operate on
43
+ # the function object.
44
+ #
45
+ # This class will almost _always_ be instantiated indirectly, by working
46
+ # with the create methods mentioned above.
47
+ class FunctionProxy
48
+ attr_accessor :result
49
+
50
+ def self.proxy(handler)
51
+ proc do |*args|
52
+ fp = new
53
+ args.unshift(fp)
54
+ handler.call(*args)
55
+ fp.result
56
+ end
57
+ end
58
+
59
+ # Create a new FunctionProxy that encapsulates the given +func+ object.
60
+ # If context is non-nil, the functions context will be set to that. If
61
+ # it is non-nil, it must quack like a Hash. If it is nil, then none of
62
+ # the context functions will be available.
63
+ def initialize
64
+ @result = nil
65
+ @context = {}
66
+ end
67
+
68
+ # Returns the value with the given key from the context. This is only
69
+ # available to aggregate functions.
70
+ def []( key )
71
+ @context[ key ]
72
+ end
73
+
74
+ # Sets the value with the given key in the context. This is only
75
+ # available to aggregate functions.
76
+ def []=( key, value )
77
+ @context[ key ] = value
78
+ end
79
+ end
80
+
81
+ class << self
82
+
83
+ alias :open :new
84
+
85
+ # Quotes the given string, making it safe to use in an SQL statement.
86
+ # It replaces all instances of the single-quote character with two
87
+ # single-quote characters. The modified string is returned.
88
+ def quote( string )
89
+ string.gsub( /'/, "''" )
90
+ end
91
+
92
+ end
93
+
94
+ def initialize(uri, opts = {})
95
+ fail TypeError, "invalid uri" unless uri.is_a? String
96
+
97
+ @results_as_hash = opts[:results_as_hash] || false
98
+ @functions = {}
99
+ @collations = {}
100
+ @authorizer = nil
101
+ @tracefunc = nil
102
+ @encoding = nil
103
+ @busy_handler = nil
104
+ @db = Pointer.malloc(SIZEOF_VOIDP)
105
+
106
+ if uri.encoding == Encoding::UTF_16LE ||
107
+ uri.encoding == Encoding::UTF_16BE
108
+ check Driver.sqlite3_open16(uri, @db.ref)
109
+ else
110
+ if opts[:readonly]
111
+ mode = OPEN_READONLY
112
+ else
113
+ mode = OPEN_READWRITE | OPEN_CREATE;
114
+ end
115
+ uri = uri.encode(Encoding::UTF_8)
116
+ check Driver.sqlite3_open_v2(uri, @db.ref, mode, nil)
117
+ end
118
+
119
+ if block_given?
120
+ begin
121
+ yield self
122
+ ensure
123
+ close
124
+ end
125
+ end
126
+ end
127
+
128
+ attr_reader :collations
129
+
130
+ # A boolean that indicates whether rows in result sets should be returned
131
+ # as hashes or not. By default, rows are returned as arrays.
132
+ attr_accessor :results_as_hash
133
+
134
+ def close
135
+ must_be_open!
136
+ check Driver.sqlite3_close(@db)
137
+ @db = nil
138
+ end
139
+
140
+ def closed?
141
+ @db.nil?
142
+ end
143
+
144
+ def handle
145
+ @db
146
+ end
147
+
148
+ def interrupt
149
+ must_be_open!
150
+ Driver.sqlite3_interrupt(@db)
151
+ end
152
+
153
+ def busy_handler(handler = nil, &block)
154
+ must_be_open!
155
+ if handler.nil? and block_given?
156
+ @busy_handler = block
157
+ else
158
+ @busy_handler = nil
159
+ end
160
+
161
+ cb = Closure::BlockCaller.new(TYPE_INT, BUSY_PARAMS) do |_, count|
162
+ @busy_handler.call(self, count) ? 1 : 0
163
+ end if @busy_handler
164
+
165
+ check Driver.sqlite3_busy_handler(@db, cb, nil)
166
+ end
167
+
168
+ # call-seq: db.collation(name, comparator)
169
+ #
170
+ # Add a collation with name +name+, and a +comparator+ object. The
171
+ # +comparator+ object should implement a method called "compare" that takes
172
+ # two parameters and returns an integer less than, equal to, or greater than
173
+ # 0.
174
+ def collation(name, comparator)
175
+ must_be_open!
176
+ cb = Closure::BlockCaller.new(TYPE_INT, COLLATION_PARAMS) do |_, aenc, astr, benc, bstr|
177
+ target = Encoding.default_internal || Encoding::UTF_8
178
+ comparator.compare(sqlite_encoding(astr.to_s, aenc).encode(target),
179
+ sqlite_encoding(bstr.to_s, benc).encode(target)).to_i
180
+ end if comparator
181
+ @collations[name] = comparator
182
+
183
+ check Driver.sqlite3_create_collation(@db, name,
184
+ Constants::TextRep::UTF8, nil, cb);
185
+ end
186
+
187
+ # call-seq: set_authorizer = auth
188
+ #
189
+ # Set the authorizer for this database. +auth+ must respond to +call+, and
190
+ # +call+ must take 5 arguments.
191
+ #
192
+ # Installs (or removes) a block that will be invoked for every access
193
+ # to the database. If the block returns 0 (or +true+), the statement
194
+ # is allowed to proceed. Returning 1 or false causes an authorization error to
195
+ # occur, and returning 2 or nil causes the access to be silently denied.
196
+ #
197
+ def authorizer=(handler)
198
+ must_be_open!
199
+ @authorizer = handler
200
+
201
+ if handler
202
+ auth = Closure::BlockCaller.new(TYPE_VOIDP, AUTH_PARAMS) do |*args|
203
+ args.shift # remove nil
204
+ ruby_args = [args.shift]
205
+ args.each do |ptr|
206
+ ruby_args << ptr.null? ? nil : ptr.to_s
207
+ end
208
+ ret = @authorizer.call(*ruby_args)
209
+ if ret.is_a? Fixnum
210
+ ret
211
+ elsif ret == false || ret == true
212
+ ret == true ? OK : DENY
213
+ else
214
+ IGNORE
215
+ end
216
+ end
217
+ else
218
+ auth = nil
219
+ end
220
+
221
+ check Driver.sqlite3_set_authorizer(@db, auth, nil)
222
+ end
223
+
224
+ def authorizer(&block)
225
+ self.authorizer = block if block_given?
226
+ @authorizer
227
+ end
228
+
229
+ def busy_timeout(ms)
230
+ must_be_open!
231
+ Driver.sqlite3_busy_timeout(@db, ms)
232
+ end
233
+
234
+ # call-seq: define_function(name) { |args,...| }
235
+ #
236
+ # Define a function named +name+ with +args+. The arity of the block
237
+ # will be used as the arity for the function defined.
238
+ #
239
+ def define_function(name, &handler)
240
+ must_be_open!
241
+ @functions[name] = handler
242
+ check Driver.sqlite3_create_function(@db, name, handler.arity,
243
+ Constants::TextRep::UTF8, nil, compile_function(handler), nil, nil)
244
+ end
245
+
246
+ # call-seq: define_aggregator(name, aggregator)
247
+ #
248
+ # Define an aggregate function named +name+ using the object +aggregator+.
249
+ # +aggregator+ must respond to +step+ and +finalize+. +step+ will be called
250
+ # with row information and +finalize+ must return the return value for the
251
+ # aggregator function.
252
+ #
253
+ def define_aggregator(name, aggregator)
254
+ must_be_open!
255
+ @functions[name] = aggregator
256
+
257
+ step = Closure::BlockCaller.new(TYPE_VOIDP, FUNC_PARAMS) do |_, argc, argv|
258
+ aggregator.step(*native_to_ruby_args(argc, argv))
259
+ 0 # return something
260
+ end
261
+
262
+ fin = Closure::BlockCaller.new(TYPE_VOIDP, FUNC_PARAMS) do |ctx, _, _|
263
+ Driver.set_context_result(ctx, aggregator.finalize())
264
+ 0 # return something
265
+ end
266
+
267
+ check Driver.sqlite3_create_function(@db, name,
268
+ aggregator.method(:step).arity,
269
+ Constants::TextRep::UTF8, nil, nil, step, fin)
270
+ end
271
+
272
+ # Creates a new aggregate function for use in SQL statements. Aggregate
273
+ # functions are functions that apply over every row in the result set,
274
+ # instead of over just a single row. (A very common aggregate function
275
+ # is the "count" function, for determining the number of rows that match
276
+ # a query.)
277
+ #
278
+ # The new function will be added as +name+, with the given +arity+. (For
279
+ # variable arity functions, use -1 for the arity.)
280
+ #
281
+ # The +step+ parameter must be a proc object that accepts as its first
282
+ # parameter a FunctionProxy instance (representing the function
283
+ # invocation), with any subsequent parameters (up to the function's arity).
284
+ # The +step+ callback will be invoked once for each row of the result set.
285
+ #
286
+ # The +finalize+ parameter must be a +proc+ object that accepts only a
287
+ # single parameter, the FunctionProxy instance representing the current
288
+ # function invocation. It should invoke FunctionProxy#result= to
289
+ # store the result of the function.
290
+ #
291
+ # Example:
292
+ #
293
+ # db.create_aggregate( "lengths", 1 ) do
294
+ # step do |func, value|
295
+ # func[ :total ] ||= 0
296
+ # func[ :total ] += ( value ? value.length : 0 )
297
+ # end
298
+ #
299
+ # finalize do |func|
300
+ # func.result = func[ :total ] || 0
301
+ # end
302
+ # end
303
+ #
304
+ # puts db.get_first_value( "select lengths(name) from table" )
305
+ #
306
+ # See also #create_aggregate_handler for a more object-oriented approach to
307
+ # aggregate functions.
308
+ def create_aggregate( name, arity, step=nil, finalize=nil,
309
+ text_rep=Constants::TextRep::ANY, &block )
310
+
311
+ factory = Class.new do
312
+ def self.step( &block )
313
+ define_method(:step, &block)
314
+ end
315
+
316
+ def self.finalize( &block )
317
+ define_method(:finalize, &block)
318
+ end
319
+ end
320
+
321
+ if block_given?
322
+ factory.instance_eval(&block)
323
+ else
324
+ factory.class_eval do
325
+ define_method(:step, step)
326
+ define_method(:finalize, finalize)
327
+ end
328
+ end
329
+
330
+ proxy = factory.new
331
+ proxy.extend(Module.new {
332
+ attr_accessor :ctx
333
+
334
+ def step( *args )
335
+ super(@ctx, *args)
336
+ end
337
+
338
+ def finalize
339
+ super(@ctx)
340
+ end
341
+ })
342
+ proxy.ctx = FunctionProxy.new
343
+ define_aggregator(name, proxy)
344
+ end
345
+
346
+ # This is another approach to creating an aggregate function (see
347
+ # #create_aggregate). Instead of explicitly specifying the name,
348
+ # callbacks, arity, and type, you specify a factory object
349
+ # (the "handler") that knows how to obtain all of that information. The
350
+ # handler should respond to the following messages:
351
+ #
352
+ # +arity+:: corresponds to the +arity+ parameter of #create_aggregate. This
353
+ # message is optional, and if the handler does not respond to it,
354
+ # the function will have an arity of -1.
355
+ # +name+:: this is the name of the function. The handler _must_ implement
356
+ # this message.
357
+ # +new+:: this must be implemented by the handler. It should return a new
358
+ # instance of the object that will handle a specific invocation of
359
+ # the function.
360
+ #
361
+ # The handler instance (the object returned by the +new+ message, described
362
+ # above), must respond to the following messages:
363
+ #
364
+ # +step+:: this is the method that will be called for each step of the
365
+ # aggregate function's evaluation. It should implement the same
366
+ # signature as the +step+ callback for #create_aggregate.
367
+ # +finalize+:: this is the method that will be called to finalize the
368
+ # aggregate function's evaluation. It should implement the
369
+ # same signature as the +finalize+ callback for
370
+ # #create_aggregate.
371
+ #
372
+ # Example:
373
+ #
374
+ # class LengthsAggregateHandler
375
+ # def self.arity; 1; end
376
+ # def self.name; 'lengths'; end
377
+ #
378
+ # def initialize
379
+ # @total = 0
380
+ # end
381
+ #
382
+ # def step( ctx, name )
383
+ # @total += ( name ? name.length : 0 )
384
+ # end
385
+ #
386
+ # def finalize( ctx )
387
+ # ctx.result = @total
388
+ # end
389
+ # end
390
+ #
391
+ # db.create_aggregate_handler( LengthsAggregateHandler )
392
+ # puts db.get_first_value( "select lengths(name) from A" )
393
+ def create_aggregate_handler( handler )
394
+ proxy = Class.new do
395
+ def initialize klass
396
+ @klass = klass
397
+ @fp = FunctionProxy.new
398
+ end
399
+
400
+ def step( *args )
401
+ instance.step(@fp, *args)
402
+ end
403
+
404
+ def finalize
405
+ instance.finalize @fp
406
+ @instance = nil
407
+ @fp.result
408
+ end
409
+
410
+ private
411
+
412
+ def instance
413
+ @instance ||= @klass.new
414
+ end
415
+ end
416
+ define_aggregator(handler.name, proxy.new(handler))
417
+ self
418
+ end
419
+
420
+ # Creates a new function for use in SQL statements. It will be added as
421
+ # +name+, with the given +arity+. (For variable arity functions, use
422
+ # -1 for the arity.)
423
+ #
424
+ # The block should accept at least one parameter--the FunctionProxy
425
+ # instance that wraps this function invocation--and any other
426
+ # arguments it needs (up to its arity).
427
+ #
428
+ # The block does not return a value directly. Instead, it will invoke
429
+ # the FunctionProxy#result= method on the +func+ parameter and
430
+ # indicate the return value that way.
431
+ #
432
+ # Example:
433
+ #
434
+ # db.create_function( "maim", 1 ) do |func, value|
435
+ # if value.nil?
436
+ # func.result = nil
437
+ # else
438
+ # func.result = value.split(//).sort.join
439
+ # end
440
+ # end
441
+ #
442
+ # puts db.get_first_value( "select maim(name) from table" )
443
+ def create_function(name, arity, text_rep=Constants::TextRep::ANY, &handler)
444
+ must_be_open!
445
+ @functions[name] = handler
446
+ check Driver.sqlite3_create_function(@db, name, arity,
447
+ text_rep, nil, compile_function(FunctionProxy.proxy(handler)), nil, nil)
448
+ end
449
+
450
+ # def create_aggregate_handler(aggregator)
451
+ # step = lambda do |*args|
452
+ # fp = FunctionProxy.new
453
+ # args.unshift(fp)
454
+ # aggregator.new.step(*args)
455
+ # fp.result
456
+ # end
457
+ # finalize = lambda do |*args|
458
+ # fp = FunctionProxy.new
459
+ # args.unshift(fp)
460
+ # aggregator.new.finalize(*args)
461
+ # fp.result
462
+ # end
463
+ # check Driver.sqlite3_create_function(@db,
464
+ # aggregator.name,
465
+ # aggregator.arity,
466
+ # aggregator.text_rep,
467
+ # nil, nil,
468
+ # compile_function(step),
469
+ # compile_function(finalize))
470
+ # end
471
+
472
+ # Executes the given SQL statement. If additional parameters are given,
473
+ # they are treated as bind variables, and are bound to the placeholders in
474
+ # the query.
475
+ #
476
+ # Note that if any of the values passed to this are hashes, then the
477
+ # key/value pairs are each bound separately, with the key being used as
478
+ # the name of the placeholder to bind the value to.
479
+ #
480
+ # The block is optional. If given, it will be invoked for each row returned
481
+ # by the query. Otherwise, any results are accumulated into an array and
482
+ # returned wholesale.
483
+ #
484
+ # See also #execute2, #query, and #execute_batch for additional ways of
485
+ # executing statements.
486
+ def execute sql, bind_vars = [], *args, &block
487
+ if bind_vars.nil? || !args.empty?
488
+ if args.empty?
489
+ bind_vars = []
490
+ else
491
+ bind_vars = [bind_vars] + args
492
+ end
493
+ end
494
+
495
+ prepare( sql ) do |stmt|
496
+ stmt.bind_params(bind_vars)
497
+ columns = stmt.columns
498
+
499
+ if block_given?
500
+ stmt.each do |row|
501
+ if @results_as_hash
502
+ yield ordered_map_for(columns, row)
503
+ else
504
+ yield row
505
+ end
506
+ end
507
+ else
508
+ if @results_as_hash
509
+ stmt.map { |row| ordered_map_for(columns, row) }
510
+ else
511
+ stmt.to_a
512
+ end
513
+ end
514
+ end
515
+ end
516
+
517
+ # Executes the given SQL statement, exactly as with #execute. However, the
518
+ # first row returned (either via the block, or in the returned array) is
519
+ # always the names of the columns. Subsequent rows correspond to the data
520
+ # from the result set.
521
+ #
522
+ # Thus, even if the query itself returns no rows, this method will always
523
+ # return at least one row--the names of the columns.
524
+ #
525
+ # See also #execute, #query, and #execute_batch for additional ways of
526
+ # executing statements.
527
+ def execute2( sql, *bind_vars )
528
+ prepare( sql ) do |stmt|
529
+ result = stmt.execute( *bind_vars )
530
+ if block_given?
531
+ yield stmt.columns
532
+ result.each { |row| yield row }
533
+ else
534
+ return result.inject( [ stmt.columns ] ) { |arr,row|
535
+ arr << row; arr }
536
+ end
537
+ end
538
+ end
539
+
540
+ # Executes all SQL statements in the given string. By contrast, the other
541
+ # means of executing queries will only execute the first statement in the
542
+ # string, ignoring all subsequent statements. This will execute each one
543
+ # in turn. The same bind parameters, if given, will be applied to each
544
+ # statement.
545
+ #
546
+ # This always returns +nil+, making it unsuitable for queries that return
547
+ # rows.
548
+ def execute_batch( sql, bind_vars = [], *args )
549
+ # FIXME: remove this stuff later
550
+ unless [Array, Hash].include?(bind_vars.class)
551
+ bind_vars = [bind_vars]
552
+ end
553
+
554
+ # FIXME: remove this stuff later
555
+ if bind_vars.nil? || !args.empty?
556
+ if args.empty?
557
+ bind_vars = []
558
+ else
559
+ bind_vars = [nil] + args
560
+ end
561
+ end
562
+ sql = sql.strip
563
+ until sql.empty? do
564
+ prepare( sql ) do |stmt|
565
+ unless stmt.closed?
566
+ # FIXME: this should probably use sqlite3's api for batch execution
567
+ # This implementation requires stepping over the results.
568
+ if bind_vars.length == stmt.bind_parameter_count
569
+ stmt.bind_params(bind_vars)
570
+ end
571
+ stmt.step
572
+ end
573
+ sql = stmt.remainder.strip
574
+ end
575
+ end
576
+ # FIXME: we should not return `nil` as a success return value
577
+ nil
578
+ end
579
+
580
+ alias_method :batch, :execute_batch
581
+
582
+ def complete?(sql)
583
+ Driver.sqlite3_complete(sql.to_s) == 1
584
+ end
585
+
586
+ def changes
587
+ must_be_open!
588
+ Driver.sqlite3_changes(@db)
589
+ end
590
+
591
+ def errcode
592
+ must_be_open!
593
+ Driver.sqlite3_errcode(@db)
594
+ end
595
+
596
+ def errmsg
597
+ must_be_open!
598
+ Driver.sqlite3_errmsg(@db).to_s
599
+ end
600
+
601
+ def total_changes
602
+ must_be_open!
603
+ Driver.sqlite3_total_changes(@db)
604
+ end
605
+
606
+ def last_insert_row_id
607
+ must_be_open!
608
+ Driver.sqlite3_last_insert_rowid(@db)
609
+ end
610
+
611
+ # This is a convenience method for creating a statement, binding
612
+ # paramters to it, and calling execute:
613
+ #
614
+ # result = db.query( "select * from foo where a=?", [5])
615
+ # # is the same as
616
+ # result = db.prepare( "select * from foo where a=?" ).execute( 5 )
617
+ #
618
+ # You must be sure to call +close+ on the ResultSet instance that is
619
+ # returned, or you could have problems with locks on the table. If called
620
+ # with a block, +close+ will be invoked implicitly when the block
621
+ # terminates.
622
+ def query( sql, bind_vars = [], *args )
623
+
624
+ if bind_vars.nil? || !args.empty?
625
+ if args.empty?
626
+ bind_vars = []
627
+ else
628
+ bind_vars = [bind_vars] + args
629
+ end
630
+ end
631
+
632
+ result = prepare( sql ).execute( bind_vars )
633
+ if block_given?
634
+ begin
635
+ yield result
636
+ ensure
637
+ result.close
638
+ end
639
+ else
640
+ return result
641
+ end
642
+ end
643
+
644
+ alias_method :exec, :execute
645
+
646
+ # Returns a Statement object representing the given SQL. This does not
647
+ # execute the statement; it merely prepares the statement for execution.
648
+ #
649
+ # The Statement can then be executed using Statement#execute.
650
+ #
651
+ def prepare sql
652
+ must_be_open!
653
+ stmt = SQLite3::Statement.new(self, sql)
654
+ return stmt unless block_given?
655
+
656
+ begin
657
+ yield stmt
658
+ ensure
659
+ stmt.close unless stmt.closed?
660
+ end
661
+ end
662
+
663
+ # A convenience method for obtaining the first row of a result set, and
664
+ # discarding all others. It is otherwise identical to #execute.
665
+ #
666
+ # See also #get_first_value.
667
+ def get_first_row( sql, *bind_vars )
668
+ execute( sql, *bind_vars ).first
669
+ end
670
+
671
+ # A convenience method for obtaining the first value of the first row of a
672
+ # result set, and discarding all other values and rows. It is otherwise
673
+ # identical to #execute.
674
+ #
675
+ # See also #get_first_row.
676
+ def get_first_value( sql, *bind_vars )
677
+ execute( sql, *bind_vars ) { |row| return row[0] }
678
+ nil
679
+ end
680
+
681
+ # Begins a new transaction. Note that nested transactions are not allowed
682
+ # by SQLite, so attempting to nest a transaction will result in a runtime
683
+ # exception.
684
+ #
685
+ # The +mode+ parameter may be either <tt>:deferred</tt> (the default),
686
+ # <tt>:immediate</tt>, or <tt>:exclusive</tt>.
687
+ #
688
+ # If a block is given, the database instance is yielded to it, and the
689
+ # transaction is committed when the block terminates. If the block
690
+ # raises an exception, a rollback will be performed instead. Note that if
691
+ # a block is given, #commit and #rollback should never be called
692
+ # explicitly or you'll get an error when the block terminates.
693
+ #
694
+ # If a block is not given, it is the caller's responsibility to end the
695
+ # transaction explicitly, either by calling #commit, or by calling
696
+ # #rollback.
697
+ def transaction( mode = :deferred )
698
+ execute "begin #{mode.to_s} transaction"
699
+
700
+ if block_given?
701
+ abort = false
702
+ begin
703
+ yield self
704
+ rescue ::Object
705
+ abort = true
706
+ raise
707
+ ensure
708
+ abort and rollback or commit
709
+ end
710
+ end
711
+
712
+ true
713
+ end
714
+
715
+ def transaction_active?
716
+ must_be_open!
717
+ Driver.sqlite3_get_autocommit(@db) != 1
718
+ end
719
+
720
+ def encoding
721
+ @encoding = Encoding.find(get_first_value('PRAGMA encoding'))
722
+ end
723
+
724
+ # call-seq:
725
+ # trace { |sql| ... }
726
+ # trace(Class.new { def call sql; end }.new)
727
+ #
728
+ # Installs (or removes) a block that will be invoked for every SQL
729
+ # statement executed. The block receives one parameter: the SQL statement
730
+ # executed. If the block is +nil+, any existing tracer will be uninstalled.
731
+ #
732
+ def trace(tracer = nil, &block)
733
+ must_be_open!
734
+
735
+ tracer = block if block_given?
736
+ @tracefunc = tracer
737
+
738
+ if tracer
739
+ cb = Closure::BlockCaller.new(TYPE_VOIDP, TRACE_PARAMS) do |_, sql|
740
+ tracer.call(sql.to_s)
741
+ 0 # return something
742
+ end
743
+ end
744
+
745
+ Driver.sqlite3_trace(@db, cb, nil)
746
+ @tracefunc
747
+ end
748
+
749
+ def readonly?(db = 'main')
750
+ must_be_open!
751
+ Driver.sqlite3_db_readonly(@db, db.to_s) == 1
752
+ end
753
+
754
+ # Commits the current transaction. If there is no current transaction,
755
+ # this will cause an error to be raised. This returns +true+, in order
756
+ # to allow it to be used in idioms like
757
+ # <tt>abort? and rollback or commit</tt>.
758
+ def commit
759
+ execute "commit transaction"
760
+ true
761
+ end
762
+
763
+ # Rolls the current transaction back. If there is no current transaction,
764
+ # this will cause an error to be raised. This returns +true+, in order
765
+ # to allow it to be used in idioms like
766
+ # <tt>abort? and rollback or commit</tt>.
767
+ def rollback
768
+ execute "rollback transaction"
769
+ true
770
+ end
771
+
772
+ def check(error_code)
773
+ if error_code != SQLITE_OK
774
+ ptr = Driver.sqlite3_errmsg(@db)
775
+ fail(ERR_EXEPTION_MAPPING[error_code] || RuntimeError, ptr.to_s)
776
+ end
777
+ error_code
778
+ end
779
+
780
+ private
781
+
782
+ def sqlite_encoding(str, sqlite_encoding)
783
+ case sqlite_encoding
784
+ when SQLite3::Constants::TextRep::UTF8
785
+ str.force_encoding(Encoding::UTF_8)
786
+ when SQLite3::Constants::TextRep::UTF16LE
787
+ str.force_encoding(Encoding::UTF_16LE)
788
+ when SQLite3::Constants::TextRep::UTF16BE
789
+ str.force_encoding(Encoding::UTF_16BE)
790
+ when SQLite3::Constants::TextRep::UTF16
791
+ str.force_encoding(Encoding::UTF_16)
792
+ when SQLite3::Constants::TextRep::ANY
793
+ str
794
+ end
795
+ end
796
+
797
+ def native_to_ruby_args(argc, argv)
798
+ args = []
799
+ argc.times do |i|
800
+ args << Value.new(self, (argv + (i * SIZEOF_VOIDP)).ptr).native
801
+ end
802
+ args
803
+ end
804
+
805
+ def compile_function(handler)
806
+ Closure::BlockCaller.new(TYPE_VOIDP, FUNC_PARAMS) do |ctx, argc, argv|
807
+ args = native_to_ruby_args(argc, argv)
808
+ Driver.set_context_result(ctx, handler.call(*args))
809
+ 0 # return something
810
+ end
811
+ end
812
+
813
+ def must_be_open!
814
+ raise Exception, "#{self.class} closed!" if closed?
815
+ end
816
+
817
+ def ordered_map_for columns, row
818
+ h = Hash[*columns.zip(row).flatten]
819
+ row.each_with_index { |r, i| h[i] = r }
820
+ h
821
+ end
822
+ end
823
+ end