amalgalite 1.8.0-x64-mingw-ucrt

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTING.md +60 -0
  3. data/HISTORY.md +386 -0
  4. data/LICENSE +31 -0
  5. data/Manifest.txt +105 -0
  6. data/README.md +62 -0
  7. data/Rakefile +27 -0
  8. data/TODO.md +57 -0
  9. data/bin/amalgalite-pack +147 -0
  10. data/examples/a.rb +9 -0
  11. data/examples/blob.rb +88 -0
  12. data/examples/bootstrap.rb +36 -0
  13. data/examples/define_aggregate.rb +75 -0
  14. data/examples/define_function.rb +104 -0
  15. data/examples/fts5.rb +152 -0
  16. data/examples/gem-db.rb +94 -0
  17. data/examples/require_me.rb +11 -0
  18. data/examples/requires.rb +42 -0
  19. data/examples/schema-info.rb +34 -0
  20. data/ext/amalgalite/c/amalgalite.c +355 -0
  21. data/ext/amalgalite/c/amalgalite.h +151 -0
  22. data/ext/amalgalite/c/amalgalite_blob.c +240 -0
  23. data/ext/amalgalite/c/amalgalite_constants.c +1432 -0
  24. data/ext/amalgalite/c/amalgalite_database.c +1188 -0
  25. data/ext/amalgalite/c/amalgalite_requires_bootstrap.c +282 -0
  26. data/ext/amalgalite/c/amalgalite_statement.c +649 -0
  27. data/ext/amalgalite/c/extconf.rb +71 -0
  28. data/ext/amalgalite/c/gen_constants.rb +353 -0
  29. data/ext/amalgalite/c/notes.txt +134 -0
  30. data/ext/amalgalite/c/sqlite3.c +243616 -0
  31. data/ext/amalgalite/c/sqlite3.h +12894 -0
  32. data/ext/amalgalite/c/sqlite3_options.h +4 -0
  33. data/ext/amalgalite/c/sqlite3ext.h +705 -0
  34. data/lib/amalgalite/3.1/amalgalite.so +0 -0
  35. data/lib/amalgalite/aggregate.rb +73 -0
  36. data/lib/amalgalite/blob.rb +186 -0
  37. data/lib/amalgalite/boolean.rb +42 -0
  38. data/lib/amalgalite/busy_timeout.rb +47 -0
  39. data/lib/amalgalite/column.rb +99 -0
  40. data/lib/amalgalite/core_ext/kernel/require.rb +21 -0
  41. data/lib/amalgalite/csv_table_importer.rb +75 -0
  42. data/lib/amalgalite/database.rb +933 -0
  43. data/lib/amalgalite/function.rb +61 -0
  44. data/lib/amalgalite/index.rb +43 -0
  45. data/lib/amalgalite/memory_database.rb +15 -0
  46. data/lib/amalgalite/packer.rb +231 -0
  47. data/lib/amalgalite/paths.rb +80 -0
  48. data/lib/amalgalite/profile_tap.rb +131 -0
  49. data/lib/amalgalite/progress_handler.rb +21 -0
  50. data/lib/amalgalite/requires.rb +151 -0
  51. data/lib/amalgalite/schema.rb +225 -0
  52. data/lib/amalgalite/sqlite3/constants.rb +95 -0
  53. data/lib/amalgalite/sqlite3/database/function.rb +48 -0
  54. data/lib/amalgalite/sqlite3/database/status.rb +68 -0
  55. data/lib/amalgalite/sqlite3/status.rb +60 -0
  56. data/lib/amalgalite/sqlite3/version.rb +55 -0
  57. data/lib/amalgalite/sqlite3.rb +6 -0
  58. data/lib/amalgalite/statement.rb +421 -0
  59. data/lib/amalgalite/table.rb +91 -0
  60. data/lib/amalgalite/taps/console.rb +27 -0
  61. data/lib/amalgalite/taps/io.rb +74 -0
  62. data/lib/amalgalite/taps.rb +2 -0
  63. data/lib/amalgalite/trace_tap.rb +35 -0
  64. data/lib/amalgalite/type_map.rb +63 -0
  65. data/lib/amalgalite/type_maps/default_map.rb +166 -0
  66. data/lib/amalgalite/type_maps/storage_map.rb +38 -0
  67. data/lib/amalgalite/type_maps/text_map.rb +21 -0
  68. data/lib/amalgalite/version.rb +8 -0
  69. data/lib/amalgalite/view.rb +26 -0
  70. data/lib/amalgalite.rb +51 -0
  71. data/spec/aggregate_spec.rb +158 -0
  72. data/spec/amalgalite_spec.rb +4 -0
  73. data/spec/blob_spec.rb +78 -0
  74. data/spec/boolean_spec.rb +24 -0
  75. data/spec/busy_handler.rb +157 -0
  76. data/spec/data/iso-3166-country.txt +242 -0
  77. data/spec/data/iso-3166-schema.sql +22 -0
  78. data/spec/data/iso-3166-subcountry.txt +3995 -0
  79. data/spec/data/make-iso-db.sh +12 -0
  80. data/spec/database_spec.rb +505 -0
  81. data/spec/default_map_spec.rb +92 -0
  82. data/spec/function_spec.rb +78 -0
  83. data/spec/integeration_spec.rb +97 -0
  84. data/spec/iso_3166_database.rb +58 -0
  85. data/spec/json_spec.rb +24 -0
  86. data/spec/packer_spec.rb +60 -0
  87. data/spec/paths_spec.rb +28 -0
  88. data/spec/progress_handler_spec.rb +91 -0
  89. data/spec/requires_spec.rb +54 -0
  90. data/spec/rtree_spec.rb +66 -0
  91. data/spec/schema_spec.rb +131 -0
  92. data/spec/spec_helper.rb +48 -0
  93. data/spec/sqlite3/constants_spec.rb +108 -0
  94. data/spec/sqlite3/database_status_spec.rb +36 -0
  95. data/spec/sqlite3/status_spec.rb +22 -0
  96. data/spec/sqlite3/version_spec.rb +28 -0
  97. data/spec/sqlite3_spec.rb +53 -0
  98. data/spec/statement_spec.rb +168 -0
  99. data/spec/storage_map_spec.rb +38 -0
  100. data/spec/tap_spec.rb +57 -0
  101. data/spec/text_map_spec.rb +20 -0
  102. data/spec/type_map_spec.rb +14 -0
  103. data/spec/version_spec.rb +8 -0
  104. data/tasks/custom.rake +101 -0
  105. data/tasks/default.rake +244 -0
  106. data/tasks/extension.rake +28 -0
  107. data/tasks/this.rb +208 -0
  108. metadata +325 -0
@@ -0,0 +1,933 @@
1
+ #--
2
+ # Copyright (c) 2008 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+ require 'amalgalite/statement'
6
+ require 'amalgalite/trace_tap'
7
+ require 'amalgalite/profile_tap'
8
+ require 'amalgalite/type_maps/default_map'
9
+ require 'amalgalite/function'
10
+ require 'amalgalite/aggregate'
11
+ require 'amalgalite/busy_timeout'
12
+ require 'amalgalite/progress_handler'
13
+ require 'amalgalite/csv_table_importer'
14
+
15
+ module Amalgalite
16
+ #
17
+ # The encapsulation of a connection to an SQLite3 database.
18
+ #
19
+ # Example opening and possibly creating a new database
20
+ #
21
+ # db = Amalgalite::Database.new( "mydb.db" )
22
+ # db.execute( "SELECT * FROM table" ) do |row|
23
+ # puts row
24
+ # end
25
+ #
26
+ # db.close
27
+ #
28
+ # Open a database read only:
29
+ #
30
+ # db = Amalgalite::Database.new( "mydb.db", "r" )
31
+ #
32
+ # Open an in-memory database:
33
+ #
34
+ # db = Amalgalite::MemoryDatabase.new
35
+ #
36
+ class Database
37
+
38
+ # Error thrown if a database is opened with an invalid mode
39
+ class InvalidModeError < ::Amalgalite::Error; end
40
+
41
+ # Error thrown if there is a failure in a user defined function
42
+ class FunctionError < ::Amalgalite::Error; end
43
+
44
+ # Error thrown if there is a failure in a user defined aggregate
45
+ class AggregateError < ::Amalgalite::Error; end
46
+
47
+ # Error thrown if there is a failure in defining a busy handler
48
+ class BusyHandlerError < ::Amalgalite::Error; end
49
+
50
+ # Error thrown if there is a failure in defining a progress handler
51
+ class ProgressHandlerError < ::Amalgalite::Error; end
52
+
53
+ ##
54
+ # container class for holding transaction behavior constants. These are the
55
+ # SQLite values passed to a START TRANSACTION SQL statement.
56
+ #
57
+ class TransactionBehavior
58
+ # no read or write locks are created until the first statement is executed
59
+ # that requries a read or a write
60
+ DEFERRED = "DEFERRED"
61
+
62
+ # a readlock is obtained immediately so that no other process can write to
63
+ # the database
64
+ IMMEDIATE = "IMMEDIATE"
65
+
66
+ # a read+write lock is obtained, no other proces can read or write to the
67
+ # database
68
+ EXCLUSIVE = "EXCLUSIVE"
69
+
70
+ # list of valid transaction behavior constants
71
+ VALID = [ DEFERRED, IMMEDIATE, EXCLUSIVE ]
72
+
73
+ #
74
+ # is the given mode a valid transaction mode
75
+ #
76
+ def self.valid?( mode )
77
+ VALID.include? mode
78
+ end
79
+ end
80
+
81
+ include Amalgalite::SQLite3::Constants
82
+
83
+ # list of valid modes for opening an Amalgalite::Database
84
+ VALID_MODES = {
85
+ "r" => Open::READONLY,
86
+ "r+" => Open::READWRITE,
87
+ "w+" => Open::READWRITE | Open::CREATE,
88
+ }
89
+
90
+ # the low level Amalgalite::SQLite3::Database
91
+ attr_reader :api
92
+
93
+ # An object that follows the TraceTap protocol, or nil. By default this is nil
94
+ attr_reader :trace_tap
95
+
96
+ # An object that follows the ProfileTap protocol, or nil. By default this is nil
97
+ attr_reader :profile_tap
98
+
99
+ # An object that follows the TypeMap protocol, or nil.
100
+ # By default this is an instances of TypeMaps::DefaultMap
101
+ attr_reader :type_map
102
+
103
+ # A list of the user defined functions
104
+ attr_reader :functions
105
+
106
+ # A list of the user defined aggregates
107
+ attr_reader :aggregates
108
+
109
+ ##
110
+ # Create a new Amalgalite database
111
+ #
112
+ # :call-seq:
113
+ # Amalgalite::Database.new( filename, "w+", opts = {}) -> Database
114
+ #
115
+ # The first parameter is the filename of the sqlite database. Specifying
116
+ # ":memory:" as the filename creates an in-memory database.
117
+ #
118
+ # The second parameter is the standard file modes of how to open a file.
119
+ #
120
+ # The modes are:
121
+ #
122
+ # * r - Read-only
123
+ # * r+ - Read/write, an error is thrown if the database does not already exist
124
+ # * w+ - Read/write, create a new database if it doesn't exist
125
+ #
126
+ # <tt>w+</tt> is the default as this is how most databases will want to be utilized.
127
+ #
128
+ # opts is a hash of available options for the database:
129
+ #
130
+ # * :utf16 option to set the database to a utf16 encoding if creating a database.
131
+ #
132
+ # By default, databases are created with an encoding of utf8. Setting this to
133
+ # true and opening an already existing database has no effect.
134
+ #
135
+ # *NOTE* Currently :utf16 is not supported by Amalgalite, it is planned
136
+ # for a later release
137
+ #
138
+ #
139
+ def initialize( filename, mode = "w+", opts = {})
140
+ @open = false
141
+ @profile_tap = nil
142
+ @trace_tap = nil
143
+ @type_map = ::Amalgalite::TypeMaps::DefaultMap.new
144
+ @functions = Hash.new
145
+ @aggregates = Hash.new
146
+ @utf16 = false
147
+
148
+ unless VALID_MODES.keys.include?( mode )
149
+ raise InvalidModeError, "#{mode} is invalid, must be one of #{VALID_MODES.keys.join(', ')}"
150
+ end
151
+
152
+ if not File.exist?( filename ) and opts[:utf16] then
153
+ raise NotImplementedError, "Currently Amalgalite has not implemented utf16 support"
154
+ else
155
+ @api = Amalgalite::SQLite3::Database.open( filename, VALID_MODES[mode] )
156
+ end
157
+ @open = true
158
+ end
159
+
160
+ ##
161
+ # Is the database open or not
162
+ #
163
+ def open?
164
+ @open
165
+ end
166
+
167
+ ##
168
+ # Close the database
169
+ #
170
+ def close
171
+ if open? then
172
+ @api.close
173
+ @open = false
174
+ end
175
+ end
176
+
177
+ ##
178
+ # Is the database in autocommit mode or not
179
+ #
180
+ def autocommit?
181
+ @api.autocommit?
182
+ end
183
+
184
+ ##
185
+ # Return the rowid of the last inserted row
186
+ #
187
+ def last_insert_rowid
188
+ @api.last_insert_rowid
189
+ end
190
+
191
+ ##
192
+ # SQL escape the input string
193
+ #
194
+ def escape( s )
195
+ Amalgalite::SQLite3.escape( s )
196
+ end
197
+
198
+ ##
199
+ # Surround the give string with single-quotes and escape any single-quotes
200
+ # in the string
201
+ def quote( s )
202
+ Amalgalite::SQLite3.quote( s )
203
+ end
204
+
205
+ ##
206
+ # Is the database utf16 or not? A database is utf16 if the encoding is not
207
+ # UTF-8. Database can only be UTF-8 or UTF-16, and the default is UTF-8
208
+ #
209
+ def utf16?
210
+ return @utf16
211
+ #if @utf16.nil?
212
+ # @utf16 = (encoding != "UTF-8")
213
+ #end
214
+ #return @utf16
215
+ end
216
+
217
+ ##
218
+ # return the encoding of the database
219
+ #
220
+ def encoding
221
+ @encoding ||= pragma( "encoding" ).first['encoding']
222
+ end
223
+
224
+ ##
225
+ # return whether or not the database is currently in a transaction or not
226
+ #
227
+ def in_transaction?
228
+ not @api.autocommit?
229
+ end
230
+
231
+ ##
232
+ # return how many rows changed in the last insert, update or delete statement.
233
+ #
234
+ def row_changes
235
+ @api.row_changes
236
+ end
237
+
238
+ ##
239
+ # return how many rows have changed since this connection to the database was
240
+ # opened.
241
+ #
242
+ def total_changes
243
+ @api.total_changes
244
+ end
245
+
246
+ ##
247
+ # Prepare a statement for execution
248
+ #
249
+ # If called with a block, the statement is yielded to the block and the
250
+ # statement is closed when the block is done.
251
+ #
252
+ # db.prepare( "SELECT * FROM table WHERE c = ?" ) do |stmt|
253
+ # list_of_c_values.each do |c|
254
+ # stmt.execute( c ) do |row|
255
+ # puts "when c = #{c} : #{row.inspect}"
256
+ # end
257
+ # end
258
+ # end
259
+ #
260
+ # Or without a block:
261
+ #
262
+ # stmt = db.prepare( "INSERT INTO t1(x, y, z) VALUES ( :
263
+ #
264
+ def prepare( sql )
265
+ stmt = Amalgalite::Statement.new( self, sql )
266
+ if block_given? then
267
+ begin
268
+ yield stmt
269
+ ensure
270
+ stmt.close
271
+ stmt = nil
272
+ end
273
+ end
274
+ return stmt
275
+ end
276
+
277
+ ##
278
+ # Execute a single SQL statement.
279
+ #
280
+ # If called with a block and there are result rows, then they are iteratively
281
+ # yielded to the block.
282
+ #
283
+ # If no block is passed, then all the results are returned as an arrayfields
284
+ # instance. This is an array with field name access.
285
+ #
286
+ # If no block is passed, and there are no results, then an empty Array is
287
+ # returned.
288
+ #
289
+ # On an error an exception is thrown
290
+ #
291
+ # This is just a wrapper around the preparation of an Amalgalite Statement and
292
+ # iterating over the results.
293
+ #
294
+ def execute( sql, *bind_params )
295
+ stmt = prepare( sql )
296
+ stmt.bind( *bind_params )
297
+ if block_given? then
298
+ stmt.each { |row| yield row }
299
+ else
300
+ return stmt.all_rows
301
+ end
302
+ ensure
303
+ stmt.close if stmt
304
+ end
305
+
306
+ ##
307
+ # Execute a batch of statements, this will execute all the sql in the given
308
+ # string until no more sql can be found in the string. It will bind the
309
+ # same parameters to each statement. All data that would be returned from
310
+ # all of the statements is thrown away.
311
+ #
312
+ # All statements to be executed in the batch must be terminated with a ';'
313
+ # Returns the number of statements executed
314
+ #
315
+ #
316
+ def execute_batch( sql, *bind_params)
317
+ count = 0
318
+ while sql
319
+ prepare( sql ) do |stmt|
320
+ stmt.execute( *bind_params )
321
+ sql = stmt.remaining_sql
322
+ sql = nil unless (sql.index(";") and Amalgalite::SQLite3.complete?( sql ))
323
+ end
324
+ count += 1
325
+ end
326
+ return count
327
+ end
328
+
329
+ ##
330
+ # Execute a batch of statements via sqlite3_exec. This does the same as
331
+ # execute_batch, but doesn't update the statement statistics.
332
+ #
333
+ def import(sql)
334
+ @api.execute_batch(sql)
335
+ end
336
+
337
+ ##
338
+ # clear all the current taps
339
+ #
340
+ def clear_taps!
341
+ self.trace_tap = nil
342
+ end
343
+
344
+ ##
345
+ # Execute a sql statment, and only return the first row of results. This
346
+ # is a shorthand method when you only want a single row of results from a
347
+ # query. If there is no result, then return an empty array
348
+ #
349
+ # It is in all other was, exactly like #execute()
350
+ #
351
+ def first_row_from( sql, *bind_params )
352
+ stmt = prepare( sql )
353
+ stmt.bind( *bind_params)
354
+ row = stmt.next_row || []
355
+ stmt.close
356
+ return row
357
+ end
358
+
359
+ ##
360
+ # Execute an sql statement, and return only the first column of the first
361
+ # row. If there is no result, return nil.
362
+ #
363
+ # It is in all other ways, exactly like #first_row_from()
364
+ #
365
+ def first_value_from( sql, *bind_params )
366
+ return first_row_from( sql, *bind_params).first
367
+ end
368
+
369
+ ##
370
+ # call-seq:
371
+ # db.trace_tap = obj
372
+ #
373
+ # Register a trace tap.
374
+ #
375
+ # Registering a trace tap measn that the +obj+ registered will have its
376
+ # +trace+ method called with a string parameter at various times.
377
+ # If the object doesn't respond to the +trace+ method then +write+
378
+ # will be called.
379
+ #
380
+ # For instance:
381
+ #
382
+ # db.trace_tap = Amalgalite::TraceTap.new( logger, 'debug' )
383
+ #
384
+ # This will register an instance of TraceTap, which wraps an logger object.
385
+ # On each +trace+ event the TraceTap#trace method will be called, which in
386
+ # turn will call the <tt>logger.debug</tt> method
387
+ #
388
+ # db.trace_tap = $stderr
389
+ #
390
+ # This will register the <tt>$stderr</tt> io stream as a trace tap. Every time a
391
+ # +trace+ event happens then <tt>$stderr.write( msg )</tt> will be called.
392
+ #
393
+ # db.trace_tap = nil
394
+ #
395
+ # This will unregister the trace tap
396
+ #
397
+ #
398
+ def trace_tap=( tap_obj )
399
+
400
+ # unregister any previous trace tap
401
+ #
402
+ if !@trace_tap.nil? then
403
+ @trace_tap.trace( 'unregistered as trace tap' )
404
+ @trace_tap = nil
405
+ end
406
+ return @trace_tap if tap_obj.nil?
407
+
408
+ # wrap the tap if we need to
409
+ #
410
+ if tap_obj.respond_to?( 'trace' ) then
411
+ @trace_tap = tap_obj
412
+ elsif tap_obj.respond_to?( 'write' ) then
413
+ @trace_tap = Amalgalite::TraceTap.new( tap_obj, 'write' )
414
+ else
415
+ raise Amalgalite::Error, "#{tap_obj.class.name} cannot be used to tap. It has no 'write' or 'trace' method. Look at wrapping it in a Tap instances."
416
+ end
417
+
418
+ # and do the low level registration
419
+ #
420
+ @api.register_trace_tap( @trace_tap )
421
+
422
+ @trace_tap.trace( 'registered as trace tap' )
423
+ end
424
+
425
+ ##
426
+ # call-seq:
427
+ # db.type_map = DefaultMap.new
428
+ #
429
+ # Assign your own TypeMap instance to do type conversions. The value
430
+ # assigned here must respond to +bind_type_of+ and +result_value_of+
431
+ # methods. See the TypeMap class for more details.
432
+ #
433
+ #
434
+ def type_map=( type_map_obj )
435
+ %w[ bind_type_of result_value_of ].each do |method|
436
+ unless type_map_obj.respond_to?( method )
437
+ raise Amalgalite::Error, "#{type_map_obj.class.name} cannot be used to do type mapping. It does not respond to '#{method}'"
438
+ end
439
+ end
440
+ @type_map = type_map_obj
441
+ end
442
+
443
+ ##
444
+ # :call-seq:
445
+ # db.schema( dbname = "main" ) -> Schema
446
+ #
447
+ # Returns a Schema object containing the table and column structure of the
448
+ # database.
449
+ #
450
+ def schema( dbname = "main" )
451
+ @schema ||= ::Amalgalite::Schema.new( self, dbname )
452
+ if @schema and @schema.dirty?
453
+ reload_schema!( dbname )
454
+ end
455
+ return @schema
456
+ end
457
+
458
+ ##
459
+ # :call-seq:
460
+ # db.reload_schema! -> Schema
461
+ #
462
+ # By default once the schema is obtained, it is cached. This is here to
463
+ # force the schema to be reloaded.
464
+ #
465
+ def reload_schema!( dbname = "main" )
466
+ @schema = nil
467
+ schema( dbname )
468
+ end
469
+
470
+ ##
471
+ # Run a pragma command against the database
472
+ #
473
+ # Returns the result set of the pragma
474
+ def pragma( cmd, &block )
475
+ execute("PRAGMA #{cmd}", &block)
476
+ end
477
+
478
+ ##
479
+ # Begin a transaction. The valid transaction types are:
480
+ #
481
+ # DEFERRED:: no read or write locks are created until the first
482
+ # statement is executed that requries a read or a write
483
+ # IMMEDIATE:: a readlock is obtained immediately so that no other process
484
+ # can write to the database
485
+ # EXCLUSIVE:: a read+write lock is obtained, no other proces can read or
486
+ # write to the database
487
+ #
488
+ # As a convenience, these are constants available in the
489
+ # Database::TransactionBehavior class.
490
+ #
491
+ # Amalgalite Transactions are database level transactions, just as SQLite's
492
+ # are.
493
+ #
494
+ # If a block is passed in, then when the block exits, it is guaranteed that
495
+ # either 'COMMIT' or 'ROLLBACK' has been executed.
496
+ #
497
+ # If any exception happens during the transaction that is caught by Amalgalite,
498
+ # then a 'ROLLBACK' is issued when the block closes.
499
+ #
500
+ # If no exception happens during the transaction then a 'COMMIT' is
501
+ # issued upon leaving the block.
502
+ #
503
+ # If no block is passed in then you are on your own.
504
+ #
505
+ # Nesting a transaaction via the _transaction_ method are no-ops.
506
+ # If you call transaction within a transaction, no new transaction is
507
+ # started, the current one is just continued.
508
+ #
509
+ # True nexted transactions are available through the _savepoint_ method.
510
+ #
511
+ def transaction( mode = TransactionBehavior::DEFERRED, &block )
512
+ raise Amalgalite::Error, "Invalid transaction behavior mode #{mode}" unless TransactionBehavior.valid?( mode )
513
+
514
+ # if already in a transaction, no need to start a new one.
515
+ if not in_transaction? then
516
+ execute( "BEGIN #{mode} TRANSACTION" )
517
+ end
518
+
519
+ if block_given? then
520
+ begin
521
+ previous_exception = $!
522
+ return ( yield self )
523
+ ensure
524
+ if $! and ($! != previous_exception) then
525
+ rollback
526
+ raise $!
527
+ else
528
+ commit
529
+ end
530
+ end
531
+ else
532
+ return in_transaction?
533
+ end
534
+ end
535
+ alias :deferred_transaction :transaction
536
+
537
+ # helper for an immediate transaction
538
+ def immediate_transaction( &block )
539
+ transaction( TransactionBehavior::IMMEDIATE, &block )
540
+ end
541
+
542
+ # helper for an exclusive transaction
543
+ def exclusive_transaction( &block )
544
+ transaction( TransactionBehavior::EXCLUSIVE, &block )
545
+ end
546
+
547
+ ##
548
+ # call-seq:
549
+ # db.savepoint( 'mypoint' ) -> db
550
+ # db.savepoint( 'mypoint' ) do |db_in_savepoint|
551
+ # ...
552
+ # end
553
+ #
554
+ # Much of the following documentation is para-phrased from
555
+ # http://sqlite.org/lang_savepoint.html
556
+ #
557
+ # Savepoints are a method of creating transactions, similar to _transaction_
558
+ # except that they may be nested.
559
+ #
560
+ # * Every savepoint must have a name, +to_s+ is called on the method
561
+ # argument
562
+ # * A savepoint does not need to be initialized inside a _transaction_. If
563
+ # it is not inside a _transaction_ it behaves exactly as if a DEFERRED
564
+ # transaction had been started.
565
+ # * If a block is passed to _saveponit_ then when the block exists, it is
566
+ # guaranteed that either a 'RELEASE' or 'ROLLBACK TO name' has been executed.
567
+ # * If any exception happens during the savepoint transaction, then a
568
+ # 'ROLLOBACK TO' is issued when the block closes.
569
+ # * If no exception happens during the transaction then a 'RELEASE name' is
570
+ # issued upon leaving the block
571
+ #
572
+ # If no block is passed in then you are on your own.
573
+ #
574
+ def savepoint( name )
575
+ point_name = name.to_s.strip
576
+ raise Amalgalite::Error, "Invalid savepoint name '#{name}'" unless point_name and point_name.length > 1
577
+ execute( "SAVEPOINT #{point_name};")
578
+ if block_given? then
579
+ begin
580
+ return ( yield self )
581
+ ensure
582
+ if $! then
583
+ rollback_to( point_name )
584
+ raise $!
585
+ else
586
+ release( point_name )
587
+ end
588
+ end
589
+ else
590
+ return in_transaction?
591
+ end
592
+ end
593
+
594
+ ##
595
+ # call-seq:
596
+ # db.release( 'mypoint' )
597
+ #
598
+ # Release a savepoint. This is similar to a _commit_ but only for
599
+ # savepoints. All savepoints up the savepoint stack and include the name
600
+ # savepoint being released are 'committed' to the transaction. There are
601
+ # several ways of thinking about release and they are all detailed in the
602
+ # sqlite documentation: http://sqlite.org/lang_savepoint.html
603
+ #
604
+ def release( point_name )
605
+ execute( "RELEASE SAVEPOINT #{point_name}" ) if in_transaction?
606
+ end
607
+
608
+ ##
609
+ # call-seq:
610
+ # db.rollback_to( point_name )
611
+ #
612
+ # Rollback to a savepoint. The transaction is not cancelled, the
613
+ # transaction is restarted.
614
+ def rollback_to( point_name )
615
+ execute( "ROLLBACK TO SAVEPOINT #{point_name}" )
616
+ end
617
+
618
+ ##
619
+ # Commit a transaction
620
+ #
621
+ def commit
622
+ execute( "COMMIT TRANSACTION" ) if in_transaction?
623
+ end
624
+
625
+ ##
626
+ # Rollback a transaction
627
+ #
628
+ def rollback
629
+ execute( "ROLLBACK TRANSACTION" ) if in_transaction?
630
+ end
631
+
632
+ ##
633
+ # call-seq:
634
+ # db.define_function( "name", MyDBFunction.new )
635
+ # db.define_function( "my_func", callable )
636
+ # db.define_function( "my_func" ) do |x,y|
637
+ # ....
638
+ # return result
639
+ # end
640
+ #
641
+ # register a callback to be exposed as an SQL function. There are multiple
642
+ # ways to register this function:
643
+ #
644
+ # 1. db.define_function( "name" ) { |a| ... }
645
+ # * pass +define_function+ a _name_ and a block.
646
+ # * The SQL function _name_ taking _arity_ parameters will be registered,
647
+ # where _arity_ is the _arity_ of the block.
648
+ # * The return value of the block is the return value of the registred
649
+ # SQL function
650
+ # 2. db.define_function( "name", callable )
651
+ # * pass +function+ a _name_ and something that <tt>responds_to?( :to_proc )</tt>
652
+ # * The SQL function _name_ is registered taking _arity_ parameters is
653
+ # registered where _arity_ is the _arity_ of +callable.to_proc.call+
654
+ # * The return value of the +callable.to_proc.call+ is the return value
655
+ # of the SQL function
656
+ #
657
+ # See also ::Amalgalite::Function
658
+ #
659
+ def define_function( name, callable = nil, &block )
660
+ p = ( callable || block ).to_proc
661
+ raise FunctionError, "Use only mandatory or arbitrary parameters in an SQL Function, not both" if p.arity < -1
662
+ db_function = ::Amalgalite::SQLite3::Database::Function.new( name, p )
663
+ @api.define_function( db_function.name, db_function )
664
+ @functions[db_function.signature] = db_function
665
+ nil
666
+ end
667
+ alias :function :define_function
668
+
669
+ ##
670
+ # call-seq:
671
+ # db.remove_function( 'name', MyScalerFunctor.new )
672
+ # db.remove_function( 'name', callable )
673
+ # db.remove_function( 'name', arity )
674
+ # db.remove_function( 'name' )
675
+ #
676
+ # Remove a function from use in the database. Since the same function may
677
+ # be registered more than once with different arity, you may specify the
678
+ # arity, or the function object, or nil. If nil is used for the arity, then
679
+ # Amalgalite does its best to remove all functions of given name.
680
+ #
681
+ def remove_function( name, callable_or_arity = nil )
682
+ arity = nil
683
+ if callable_or_arity.respond_to?( :to_proc ) then
684
+ arity = callable_or_arity.to_proc.arity
685
+ elsif callable_or_arity.respond_to?( :to_int ) then
686
+ arity = callable_or_arity.to_int
687
+ end
688
+ to_remove = []
689
+
690
+ if arity then
691
+ signature = ::Amalgalite::SQLite3::Database::Function.signature( name, arity )
692
+ db_function = @functions[ signature ]
693
+ raise FunctionError, "db function '#{name}' with arity #{arity} does not appear to be defined" unless db_function
694
+ to_remove << db_function
695
+ else
696
+ possibles = @functions.values.select { |f| f.name == name }
697
+ raise FunctionError, "no db function '#{name}' appears to be defined" if possibles.empty?
698
+ to_remove = possibles
699
+ end
700
+
701
+ to_remove.each do |db_func|
702
+ @api.remove_function( db_func.name, db_func )
703
+ @functions.delete( db_func.signature )
704
+ end
705
+ end
706
+
707
+ ##
708
+ # call-seq:
709
+ # db.define_aggregate( 'name', MyAggregateClass )
710
+ #
711
+ # Define an SQL aggregate function, these are functions like max(), min(),
712
+ # avg(), etc. SQL functions that would be used when a GROUP BY clause is in
713
+ # effect. See also ::Amalgalite::Aggregate.
714
+ #
715
+ # A new instance of MyAggregateClass is created for each instance that the
716
+ # SQL aggregate is mentioned in SQL.
717
+ #
718
+ def define_aggregate( name, klass )
719
+ db_aggregate = klass
720
+ a = klass.new
721
+ raise AggregateError, "Use only mandatory or arbitrary parameters in an SQL Aggregate, not both" if a.arity < -1
722
+ raise AggregateError, "Aggregate implementation name '#{a.name}' does not match defined name '#{name}'" if a.name != name
723
+ @api.define_aggregate( name, a.arity, klass )
724
+ @aggregates[a.signature] = db_aggregate
725
+ nil
726
+ end
727
+ alias :aggregate :define_aggregate
728
+
729
+ ##
730
+ # call-seq:
731
+ # db.remove_aggregate( 'name', MyAggregateClass )
732
+ # db.remove_aggregate( 'name' )
733
+ #
734
+ # Remove an aggregate from use in the database. Since the same aggregate
735
+ # may be refistered more than once with different arity, you may specify the
736
+ # arity, or the aggregate class, or nil. If nil is used for the arity then
737
+ # Amalgalite does its best to remove all aggregates of the given name
738
+ #
739
+ def remove_aggregate( name, klass_or_arity = nil )
740
+ klass = nil
741
+ case klass_or_arity
742
+ when Integer
743
+ arity = klass_or_arity
744
+ when NilClass
745
+ arity = nil
746
+ else
747
+ klass = klass_or_arity
748
+ arity = klass.new.arity
749
+ end
750
+ to_remove = []
751
+ if arity then
752
+ signature = ::Amalgalite::SQLite3::Database::Function.signature( name, arity )
753
+ db_aggregate = @aggregates[ signature ]
754
+ raise AggregateError, "db aggregate '#{name}' with arity #{arity} does not appear to be defined" unless db_aggregate
755
+ to_remove << db_aggregate
756
+ else
757
+ possibles = @aggregates.values.select { |a| a.new.name == name }
758
+ raise AggregateError, "no db aggregate '#{name}' appears to be defined" if possibles.empty?
759
+ to_remove = possibles
760
+ end
761
+
762
+ to_remove.each do |db_agg|
763
+ i = db_agg.new
764
+ @api.remove_aggregate( i.name, i.arity, db_agg)
765
+ @aggregates.delete( i.signature )
766
+ end
767
+ end
768
+
769
+ ##
770
+ # call-seq:
771
+ # db.busy_handler( callable )
772
+ # db.define_busy_handler do |count|
773
+ # end
774
+ # db.busy_handler( Amalgalite::BusyTimeout.new( 30 ) )
775
+ #
776
+ # Register a busy handler for this database connection, the handler MUST
777
+ # follow the +to_proc+ protocol indicating that is will
778
+ # +respond_to?(:call)+. This is intrinsic to lambdas and blocks so
779
+ # those will work automatically.
780
+ #
781
+ # This exposes the sqlite busy handler api to ruby.
782
+ #
783
+ # * http://sqlite.org/c3ref/busy_handler.html
784
+ #
785
+ # The busy handler's _call(N)_ method may be invoked whenever an attempt is
786
+ # made to open a database table that another thread or process has locked.
787
+ # +N+ will be the number of times the _call(N)_ method has been invoked
788
+ # during this locking event.
789
+ #
790
+ # The handler may or maynot be called based upon what SQLite determins.
791
+ #
792
+ # If the handler returns _nil_ or _false_ then no more busy handler calls will
793
+ # be made in this lock event and you are probably going to see an
794
+ # SQLite::Error in your immediately future in another process or in another
795
+ # piece of code.
796
+ #
797
+ # If the handler returns non-nil or non-false then another attempt will be
798
+ # made to obtain the lock, lather, rinse, repeat.
799
+ #
800
+ # If an Exception happens in a busy handler, it will be the same as if the
801
+ # busy handler had returned _nil_ or _false_. The exception itself will not
802
+ # be propogated further.
803
+ #
804
+ def define_busy_handler( callable = nil, &block )
805
+ handler = ( callable || block ).to_proc
806
+ a = handler.arity
807
+ raise BusyHandlerError, "A busy handler expects 1 and only 1 argument, not #{a}" if a != 1
808
+ @api.busy_handler( handler )
809
+ end
810
+ alias :busy_handler :define_busy_handler
811
+
812
+ ##
813
+ # call-seq:
814
+ # db.remove_busy_handler
815
+ #
816
+ # Remove the busy handler for this database connection.
817
+ def remove_busy_handler
818
+ @api.busy_handler( nil )
819
+ end
820
+
821
+ ##
822
+ # call-seq:
823
+ # db.interrupt!
824
+ #
825
+ # Cause another thread with a handle on this database to be interrupted and
826
+ # return at the earliest opportunity as interrupted. It is not safe to call
827
+ # this method if the database might be closed before interrupt! returns.
828
+ #
829
+ def interrupt!
830
+ @api.interrupt!
831
+ end
832
+
833
+ ##
834
+ # call-seq:
835
+ # db.progress_handler( 50, MyProgressHandler.new )
836
+ # db.progress_handler( 25 , callable )
837
+ # db.progress_handler do
838
+ # ....
839
+ # return result
840
+ # end
841
+ #
842
+ # Register a progress handler for this database connection, the handler MUST
843
+ # follow the +to_proc+ protocol indicating that is will
844
+ # +respond_to?(:call)+. This is intrinsic to lambdas and blocks so
845
+ # those will work automatically.
846
+ #
847
+ # This exposes the sqlite progress handler api to ruby.
848
+ #
849
+ # * http://sqlite.org/c3ref/progress_handler.html
850
+ #
851
+ # The progress handler's _call()_ method may be invoked ever N SQLite op
852
+ # codes. If the progress handler returns anything that can evaluate to
853
+ # +true+ then current running sqlite statement is terminated at the earliest
854
+ # oppportunity.
855
+ #
856
+ # You can use this to be notified that a thread is still processingn a
857
+ # request.
858
+ #
859
+ def define_progress_handler( op_code_count = 25, callable = nil, &block )
860
+ handler = ( callable || block ).to_proc
861
+ a = handler.arity
862
+ raise ProgressHandlerError, "A progress handler expects 0 arguments, not #{a}" if a != 0
863
+ @api.progress_handler( op_code_count, handler )
864
+ end
865
+ alias :progress_handler :define_progress_handler
866
+
867
+ ##
868
+ # call-seq:
869
+ # db.remove_progress_handler
870
+ #
871
+ # Remove the progress handler for this database connection.
872
+ def remove_progress_handler
873
+ @api.progress_handler( nil, nil )
874
+ end
875
+
876
+ ##
877
+ # call-seq:
878
+ # db.replicate_to( ":memory:" ) -> new_db
879
+ # db.replicate_to( "/some/location/my.db" ) -> new_db
880
+ # db.replicate_to( Amalgalite::Database.new( "/my/backup.db" ) ) -> new_db
881
+ #
882
+ # replicate_to() takes a single argument, either a String or an
883
+ # Amalgalite::Database. It returns the replicated database object. If
884
+ # given a String, it will truncate that database if it already exists.
885
+ #
886
+ # Replicate the current database to another location, this can be used for a
887
+ # number of purposes:
888
+ #
889
+ # * load an sqlite database from disk into memory
890
+ # * snaphost an in memory db and save it to disk
891
+ # * backup on sqlite database to another location
892
+ #
893
+ def replicate_to( location )
894
+ to_db = nil
895
+ case location
896
+ when String
897
+ to_db = Amalgalite::Database.new( location )
898
+ when Amalgalite::Database
899
+ to_db = location
900
+ else
901
+ raise ArgumentError, "replicate_to( #{location} ) must be a String or a Database"
902
+ end
903
+
904
+ @api.replicate_to( to_db.api )
905
+ return to_db
906
+ end
907
+
908
+ ##
909
+ # call-seq:
910
+ # db.import_csv_to_table( "/some/location/data.csv", "my_table" )
911
+ # db.import_csv_to_table( "countries.csv", "countries", :col_sep => "|", :headers => %w[ name two_letter id ] )
912
+ #
913
+ #
914
+ # import_csv_to_table() takes 2 required arguments, and a hash of options. The
915
+ # first argument is the path to a CSV, the second is the table in which
916
+ # to load the data. The options has is a subset of those used by CSV
917
+ #
918
+ # * :col_sep - the string placed between each field. Default is ","
919
+ # * :row_sep - the String appended to the end of each row. Default is :auto
920
+ # * :quote_char - The character used to quote fields. Default '"'
921
+ # * :headers - set to true or :first_row if there are headers in this CSV. Default is false.
922
+ # This may also be an Array. If that is the case then the
923
+ # array is used as the fields in the CSV and the fields in the
924
+ # table in which to insert. If this is set to an Array, it is
925
+ # assumed that all rows in the csv will be inserted.
926
+ #
927
+ def import_csv_to_table( csv_path, table_name, options = {} )
928
+ importer = CSVTableImporter.new( csv_path, self, table_name, options )
929
+ importer.run
930
+ end
931
+ end
932
+ end
933
+