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