libsql 0.1.0-x64-mingw32

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.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTING.md +60 -0
  3. data/HISTORY.md +6 -0
  4. data/LICENSE +31 -0
  5. data/Manifest.txt +96 -0
  6. data/README.md +59 -0
  7. data/Rakefile +28 -0
  8. data/TODO.md +57 -0
  9. data/examples/a.rb +9 -0
  10. data/examples/blob.rb +106 -0
  11. data/examples/define_aggregate.rb +75 -0
  12. data/examples/define_function.rb +104 -0
  13. data/examples/fts5.rb +152 -0
  14. data/examples/gem-db.rb +94 -0
  15. data/examples/schema-info.rb +34 -0
  16. data/ext/libsql/c/extconf.rb +86 -0
  17. data/ext/libsql/c/gen_constants.rb +353 -0
  18. data/ext/libsql/c/libsql_blob.c +240 -0
  19. data/ext/libsql/c/libsql_constants.c +1518 -0
  20. data/ext/libsql/c/libsql_database.c +1188 -0
  21. data/ext/libsql/c/libsql_ext.c +383 -0
  22. data/ext/libsql/c/libsql_ext.h +149 -0
  23. data/ext/libsql/c/libsql_statement.c +649 -0
  24. data/ext/libsql/c/notes.txt +134 -0
  25. data/ext/libsql/c/sqlite3.c +247030 -0
  26. data/ext/libsql/c/sqlite3.h +13436 -0
  27. data/lib/libsql/aggregate.rb +73 -0
  28. data/lib/libsql/blob.rb +186 -0
  29. data/lib/libsql/boolean.rb +42 -0
  30. data/lib/libsql/busy_timeout.rb +47 -0
  31. data/lib/libsql/column.rb +99 -0
  32. data/lib/libsql/csv_table_importer.rb +75 -0
  33. data/lib/libsql/database.rb +933 -0
  34. data/lib/libsql/function.rb +61 -0
  35. data/lib/libsql/index.rb +43 -0
  36. data/lib/libsql/libsql_ext.so +0 -0
  37. data/lib/libsql/memory_database.rb +15 -0
  38. data/lib/libsql/paths.rb +80 -0
  39. data/lib/libsql/profile_tap.rb +131 -0
  40. data/lib/libsql/progress_handler.rb +21 -0
  41. data/lib/libsql/schema.rb +225 -0
  42. data/lib/libsql/sqlite3/constants.rb +95 -0
  43. data/lib/libsql/sqlite3/database/function.rb +48 -0
  44. data/lib/libsql/sqlite3/database/status.rb +68 -0
  45. data/lib/libsql/sqlite3/libsql_version.rb +32 -0
  46. data/lib/libsql/sqlite3/status.rb +60 -0
  47. data/lib/libsql/sqlite3/version.rb +55 -0
  48. data/lib/libsql/sqlite3.rb +7 -0
  49. data/lib/libsql/statement.rb +421 -0
  50. data/lib/libsql/table.rb +91 -0
  51. data/lib/libsql/taps/console.rb +27 -0
  52. data/lib/libsql/taps/io.rb +74 -0
  53. data/lib/libsql/taps.rb +2 -0
  54. data/lib/libsql/trace_tap.rb +35 -0
  55. data/lib/libsql/type_map.rb +63 -0
  56. data/lib/libsql/type_maps/default_map.rb +166 -0
  57. data/lib/libsql/type_maps/storage_map.rb +38 -0
  58. data/lib/libsql/type_maps/text_map.rb +21 -0
  59. data/lib/libsql/version.rb +8 -0
  60. data/lib/libsql/view.rb +26 -0
  61. data/lib/libsql-ruby.rb +1 -0
  62. data/lib/libsql.rb +51 -0
  63. data/spec/aggregate_spec.rb +158 -0
  64. data/spec/blob_spec.rb +78 -0
  65. data/spec/boolean_spec.rb +24 -0
  66. data/spec/busy_handler.rb +157 -0
  67. data/spec/data/iso-3166-country.txt +242 -0
  68. data/spec/data/iso-3166-schema.sql +22 -0
  69. data/spec/data/iso-3166-subcountry.txt +3995 -0
  70. data/spec/data/make-iso-db.sh +12 -0
  71. data/spec/database_spec.rb +505 -0
  72. data/spec/default_map_spec.rb +92 -0
  73. data/spec/function_spec.rb +78 -0
  74. data/spec/integeration_spec.rb +97 -0
  75. data/spec/iso_3166_database.rb +58 -0
  76. data/spec/json_spec.rb +24 -0
  77. data/spec/libsql_spec.rb +4 -0
  78. data/spec/paths_spec.rb +28 -0
  79. data/spec/progress_handler_spec.rb +91 -0
  80. data/spec/rtree_spec.rb +66 -0
  81. data/spec/schema_spec.rb +131 -0
  82. data/spec/spec_helper.rb +48 -0
  83. data/spec/sqlite3/constants_spec.rb +108 -0
  84. data/spec/sqlite3/database_status_spec.rb +36 -0
  85. data/spec/sqlite3/libsql_version_spec.rb +16 -0
  86. data/spec/sqlite3/status_spec.rb +22 -0
  87. data/spec/sqlite3/version_spec.rb +28 -0
  88. data/spec/sqlite3_spec.rb +53 -0
  89. data/spec/statement_spec.rb +168 -0
  90. data/spec/storage_map_spec.rb +38 -0
  91. data/spec/tap_spec.rb +57 -0
  92. data/spec/text_map_spec.rb +20 -0
  93. data/spec/type_map_spec.rb +14 -0
  94. data/spec/version_spec.rb +8 -0
  95. data/tasks/custom.rake +134 -0
  96. data/tasks/default.rake +257 -0
  97. data/tasks/extension.rake +29 -0
  98. data/tasks/this.rb +208 -0
  99. metadata +328 -0
@@ -0,0 +1,933 @@
1
+ #--
2
+ # Copyright (c) 2023 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+ require 'libsql/statement'
6
+ require 'libsql/trace_tap'
7
+ require 'libsql/profile_tap'
8
+ require 'libsql/type_maps/default_map'
9
+ require 'libsql/function'
10
+ require 'libsql/aggregate'
11
+ require 'libsql/busy_timeout'
12
+ require 'libsql/progress_handler'
13
+ require 'libsql/csv_table_importer'
14
+
15
+ module ::Libsql
16
+ #
17
+ # The encapsulation of a connection to an SQLite3 database.
18
+ #
19
+ # Example opening and possibly creating a new database
20
+ #
21
+ # db = ::Libsql::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 = ::Libsql::Database.new( "mydb.db", "r" )
31
+ #
32
+ # Open an in-memory database:
33
+ #
34
+ # db = ::Libsql::MemoryDatabase.new
35
+ #
36
+ class Database
37
+
38
+ # Error thrown if a database is opened with an invalid mode
39
+ class InvalidModeError < ::Libsql::Error; end
40
+
41
+ # Error thrown if there is a failure in a user defined function
42
+ class FunctionError < ::Libsql::Error; end
43
+
44
+ # Error thrown if there is a failure in a user defined aggregate
45
+ class AggregateError < ::Libsql::Error; end
46
+
47
+ # Error thrown if there is a failure in defining a busy handler
48
+ class BusyHandlerError < ::Libsql::Error; end
49
+
50
+ # Error thrown if there is a failure in defining a progress handler
51
+ class ProgressHandlerError < ::Libsql::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 ::Libsql::SQLite3::Constants
82
+
83
+ # list of valid modes for opening an ::Libsql::Database
84
+ VALID_MODES = {
85
+ "r" => Open::READONLY,
86
+ "r+" => Open::READWRITE,
87
+ "w+" => Open::READWRITE | Open::CREATE,
88
+ }
89
+
90
+ # the low level ::Libsql::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 ::Libsql database
111
+ #
112
+ # :call-seq:
113
+ # ::Libsql::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 ::Libsql, 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 = ::Libsql::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 ::Libsql has not implemented utf16 support"
154
+ else
155
+ @api = ::Libsql::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
+ ::Libsql::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
+ ::Libsql::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 = ::Libsql::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 ::Libsql 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 ::Libsql::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 = ::Libsql::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 = ::Libsql::TraceTap.new( tap_obj, 'write' )
414
+ else
415
+ raise ::Libsql::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 ::Libsql::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 ||= ::Libsql::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
+ # ::Libsql 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 ::Libsql,
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 ::Libsql::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 ::Libsql::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 ::Libsql::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 = ::Libsql::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
+ # ::Libsql 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 = ::Libsql::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 ::Libsql::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
+ # ::Libsql 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 = ::Libsql::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( ::Libsql::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( ::Libsql::Database.new( "/my/backup.db" ) ) -> new_db
881
+ #
882
+ # replicate_to() takes a single argument, either a String or an
883
+ # ::Libsql::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 = ::Libsql::Database.new( location )
898
+ when ::Libsql::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
+