amalgalite 0.4.2-x86-mswin32-60

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 (83) hide show
  1. data/HISTORY +81 -0
  2. data/LICENSE +29 -0
  3. data/README +40 -0
  4. data/bin/amalgalite-pack-into-db +155 -0
  5. data/examples/a.rb +9 -0
  6. data/examples/blob.rb +105 -0
  7. data/examples/bootstrap.rb +36 -0
  8. data/examples/gem-db.rb +94 -0
  9. data/examples/requires.rb +54 -0
  10. data/examples/schema-info.rb +34 -0
  11. data/ext/amalgalite3.c +201 -0
  12. data/ext/amalgalite3.h +121 -0
  13. data/ext/amalgalite3_blob.c +241 -0
  14. data/ext/amalgalite3_constants.c +221 -0
  15. data/ext/amalgalite3_database.c +550 -0
  16. data/ext/amalgalite3_requires_bootstrap.c +210 -0
  17. data/ext/amalgalite3_statement.c +628 -0
  18. data/ext/extconf.rb +19 -0
  19. data/ext/gen_constants.rb +130 -0
  20. data/ext/rbconfig-mingw.rb +178 -0
  21. data/ext/sqlite3.c +97092 -0
  22. data/ext/sqlite3.h +6364 -0
  23. data/ext/sqlite3_options.h +4 -0
  24. data/ext/sqlite3ext.h +372 -0
  25. data/gemspec.rb +55 -0
  26. data/lib/amalgalite.rb +33 -0
  27. data/lib/amalgalite/blob.rb +186 -0
  28. data/lib/amalgalite/boolean.rb +42 -0
  29. data/lib/amalgalite/column.rb +86 -0
  30. data/lib/amalgalite/core_ext/kernel/require.rb +14 -0
  31. data/lib/amalgalite/database.rb +514 -0
  32. data/lib/amalgalite/index.rb +43 -0
  33. data/lib/amalgalite/paths.rb +70 -0
  34. data/lib/amalgalite/profile_tap.rb +130 -0
  35. data/lib/amalgalite/requires.rb +112 -0
  36. data/lib/amalgalite/schema.rb +115 -0
  37. data/lib/amalgalite/sqlite3.rb +6 -0
  38. data/lib/amalgalite/sqlite3/constants.rb +82 -0
  39. data/lib/amalgalite/sqlite3/database/status.rb +69 -0
  40. data/lib/amalgalite/sqlite3/status.rb +61 -0
  41. data/lib/amalgalite/sqlite3/version.rb +38 -0
  42. data/lib/amalgalite/statement.rb +394 -0
  43. data/lib/amalgalite/table.rb +36 -0
  44. data/lib/amalgalite/taps.rb +2 -0
  45. data/lib/amalgalite/taps/console.rb +27 -0
  46. data/lib/amalgalite/taps/io.rb +71 -0
  47. data/lib/amalgalite/trace_tap.rb +35 -0
  48. data/lib/amalgalite/type_map.rb +63 -0
  49. data/lib/amalgalite/type_maps/default_map.rb +167 -0
  50. data/lib/amalgalite/type_maps/storage_map.rb +41 -0
  51. data/lib/amalgalite/type_maps/text_map.rb +23 -0
  52. data/lib/amalgalite/version.rb +37 -0
  53. data/lib/amalgalite/view.rb +26 -0
  54. data/lib/amalgalite3.so +0 -0
  55. data/spec/amalgalite_spec.rb +4 -0
  56. data/spec/blob_spec.rb +81 -0
  57. data/spec/boolean_spec.rb +23 -0
  58. data/spec/database_spec.rb +238 -0
  59. data/spec/default_map_spec.rb +87 -0
  60. data/spec/integeration_spec.rb +111 -0
  61. data/spec/paths_spec.rb +28 -0
  62. data/spec/schema_spec.rb +60 -0
  63. data/spec/spec_helper.rb +25 -0
  64. data/spec/sqlite3/constants_spec.rb +65 -0
  65. data/spec/sqlite3/database_status_spec.rb +36 -0
  66. data/spec/sqlite3/status_spec.rb +18 -0
  67. data/spec/sqlite3/version_spec.rb +14 -0
  68. data/spec/sqlite3_spec.rb +23 -0
  69. data/spec/statement_spec.rb +134 -0
  70. data/spec/storage_map_spec.rb +41 -0
  71. data/spec/tap_spec.rb +59 -0
  72. data/spec/text_map_spec.rb +23 -0
  73. data/spec/type_map_spec.rb +17 -0
  74. data/spec/version_spec.rb +9 -0
  75. data/tasks/announce.rake +39 -0
  76. data/tasks/config.rb +110 -0
  77. data/tasks/distribution.rake +53 -0
  78. data/tasks/documentation.rake +33 -0
  79. data/tasks/extension.rake +100 -0
  80. data/tasks/rspec.rake +32 -0
  81. data/tasks/rubyforge.rake +59 -0
  82. data/tasks/utils.rb +80 -0
  83. metadata +192 -0
@@ -0,0 +1,186 @@
1
+ #--
2
+ # Copyright (c) 2008 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+ module Amalgalite
6
+ ##
7
+ # This is the interface to allow Blob objects to be written to and read from
8
+ # the SQLite database. When using statements, use a Blob object as
9
+ # the wrapper around the source to be written to the row, and a Blob object is
10
+ # returned if the the type mapping warrents during select queries.
11
+ #
12
+ # For instance during an insert:
13
+ #
14
+ # blob_column = db.schema.tables['blobs'].columsn['data']
15
+ # db.execute("INSERT INTO blobs(name, data) VALUES ( $name, $blob )",
16
+ # { "$name" => "/path/to/file",
17
+ # "$blob" => Amalgalite::Blob.new( :file => '/path/to/file',
18
+ # :column => blob_column) } )
19
+ #
20
+ # db.execute("INSERT INTO blobs(id, data) VALUES ($id, $blob )",
21
+ # { "$name" => 'blobname',
22
+ # "$blob" => Amalgalite::Blob.new( :io => "something with .read and .length methods",
23
+ # :column => blob_column) } )
24
+ #
25
+ # On select the blob data needs to be read into an IO object
26
+ #
27
+ # all_rows = db.execute("SELECT name, blob FROM blobs WHERE name = '/path/to/file' ")
28
+ # blob_row = all_rows.first
29
+ # blob_row['blob'].write_to_file( blob_row['name'] )
30
+ #
31
+ # Or write to an IO object
32
+ #
33
+ # blob_results = {}
34
+ # db.execute("SELECT name, blob FROM blobs") do |row|
35
+ # io = StringIO.new
36
+ # row['blob'].write_to_io( io )
37
+ # blob_results[row['name']] = io
38
+ # # or use a shortcut
39
+ # # blob_results[row['name']] = row['blob'].to_string_io
40
+ # end
41
+ #
42
+ # If using a Blob as a conditional, for instance in a WHERE clause then the
43
+ # Blob must resolvable to a String.
44
+ #
45
+ # db.execute("SELECT FROM blobs(name, data) WHERE data = $blob",
46
+ # { "$blob' => Amalgalite::Blob.new( :string => "A string of data" ) })
47
+ #
48
+ class Blob
49
+ class Error < ::Amalgalite::Error; end
50
+ class << self
51
+ def valid_source_params
52
+ @valid_source_params ||= [ :file, :io, :string, :db_blob ]
53
+ end
54
+
55
+ def default_block_size
56
+ @default_block_size ||= 8192
57
+ end
58
+ end
59
+
60
+ # the object representing the source of the blob
61
+ attr_reader :source
62
+
63
+ # the size in bytes of the of the blob
64
+ attr_reader :length
65
+
66
+ # the size in bytes of the blocks of data to move from the source
67
+ attr_reader :block_size
68
+
69
+ # the column the blob is associated with
70
+ attr_reader :column
71
+
72
+ ##
73
+ # Initialize a new blob, it takes a single parameter, a hash which describes
74
+ # the source of the blob. The keys of the hash are one of:
75
+ #
76
+ # :file : the value is the path to a file on the file system
77
+ # :io : the value is an object that responds to the the methods +read+
78
+ # and +length+. +read+ should have the behavior of IO#read
79
+ # :db_blob : not normally used by an end user, used to initialize a blob
80
+ # object that is returned from an SQL query.
81
+ # :string : used when a Blob is part of a WHERE clause or result
82
+ #
83
+ # And additional key of :block_size may be used to indicate the maximum size
84
+ # of a single block of data to move from the source to the destination, this
85
+ # defaults ot 8192.
86
+ #
87
+ def initialize( params )
88
+ if (Blob.valid_source_params & params.keys).size > 1 then
89
+ raise Blob::Error, "Only a one of #{Blob.valid_source_params.join(', ')} is allowed to initialize a Blob. #{params.keys.join(', ')} were sent"
90
+ end
91
+
92
+ @source = nil
93
+ @source_length = 0
94
+ @close_source_after_read = false
95
+ @incremental = true
96
+ @block_size = params[:block_size] || Blob.default_block_size
97
+ @column = params[:column]
98
+
99
+ raise Blob::Error, "A :column parameter is required for a Blob" unless @column or params.has_key?( :string )
100
+
101
+ if params.has_key?( :file ) then
102
+ @source = File.open( params[:file], "r" )
103
+ @length = File.size( params[:file] )
104
+ @close_source_after_read = true
105
+ elsif params.has_key?( :io ) then
106
+ @source = params[:io]
107
+ @length = @source.length
108
+ elsif params.has_key?( :db_blob ) then
109
+ @source = params[:db_blob]
110
+ @length = @source.length
111
+ @close_source_after_read = true
112
+ elsif params.has_key?( :string ) then
113
+ @source = params[:string]
114
+ @length = @source.length
115
+ @incremental = false
116
+ end
117
+ end
118
+
119
+ ##
120
+ # close the source when done reading from it
121
+ #
122
+ def close_source_after_read?
123
+ @close_source_after_read
124
+ end
125
+
126
+ ##
127
+ # is this an incremental Blob or not
128
+ #
129
+ def incremental?
130
+ @incremental
131
+ end
132
+
133
+ ##
134
+ # Write the Blob to an IO object
135
+ #
136
+ def write_to_io( io )
137
+ if source.respond_to?( :read ) then
138
+ while buf = source.read( block_size ) do
139
+ io.write( buf )
140
+ end
141
+ else
142
+ io.write( source.to_s )
143
+ end
144
+
145
+ if close_source_after_read? then
146
+ source.close
147
+ end
148
+ end
149
+
150
+ ##
151
+ # conver the blob to a string
152
+ #
153
+ def to_s
154
+ to_string_io.string
155
+ end
156
+
157
+ ##
158
+ # write the Blob contents to a StringIO
159
+ #
160
+ def to_string_io
161
+ sio = StringIO.new
162
+ write_to_io( sio )
163
+ return sio
164
+ end
165
+
166
+ ##
167
+ # Write the Blob contents to a File.
168
+ #
169
+ def write_to_file( filename, modestring="w" )
170
+ File.open(filename, modestring) do |f|
171
+ write_to_io( f )
172
+ end
173
+ end
174
+
175
+ ##
176
+ # Write the Blob contents to the column. This assumes that the row_id to
177
+ # insert into is the last row that was inserted into the db
178
+ #
179
+ def write_to_column!
180
+ last_rowid = column.schema.db.last_insert_rowid
181
+ SQLite3::Blob.new( column.schema.db.api, column.db, column.table, column.name, last_rowid, "w" ) do |sqlite_blob|
182
+ write_to_io( sqlite_blob )
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,42 @@
1
+ #--
2
+ # Copyright (c) 2008 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+ module Amalgalite
6
+ ##
7
+ # Do type conversion on values that could be boolen values into
8
+ # real 'true' or 'false'
9
+ #
10
+ # This is pulled from the possible boolean values from PostgreSQL
11
+ #
12
+ class Boolean
13
+ class << self
14
+ #
15
+ # list of downcased strings are potential true values
16
+ #
17
+ def true_values
18
+ @true_values ||= %w[ true t yes y 1 ]
19
+ end
20
+
21
+ #
22
+ # list of downcased strings are potential false values
23
+ #
24
+ def false_values
25
+ @false_values ||= %w[ false f no n 0 ]
26
+ end
27
+
28
+ #
29
+ # Convert +val+ to a string and attempt to convert it to +true+ or +false+
30
+ #
31
+ def to_bool( val )
32
+ return false if val.nil?
33
+ unless @to_bool
34
+ @to_bool = {}
35
+ true_values.each { |t| @to_bool[t] = true }
36
+ false_values.each { |f| @to_bool[f] = false }
37
+ end
38
+ return @to_bool[val.to_s.downcase]
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,86 @@
1
+ #--
2
+ # Copyright (c) 2008 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+
6
+ require 'amalgalite/boolean'
7
+ require 'amalgalite/blob'
8
+
9
+ module Amalgalite
10
+ ##
11
+ # a class representing the meta information about an SQLite column, this class
12
+ # serves both for general Schema level information, and for result set
13
+ # information from a SELECT query.
14
+ #
15
+ class Column
16
+ # the schema object this column is associated with
17
+ attr_accessor :schema
18
+
19
+ # the database name this column belongs to
20
+ attr_accessor :db
21
+
22
+ # the column name
23
+ attr_accessor :name
24
+
25
+ # the table to which this column belongs
26
+ attr_accessor :table
27
+
28
+ # the default value of the column. This may not have a value and that
29
+ # either means that there is no default value, or one could not be
30
+ # determined.
31
+ #
32
+ attr_accessor :default_value
33
+
34
+ # the declared data type of the column in the original sql that created the
35
+ # column
36
+ attr_accessor :declared_data_type
37
+
38
+ # the collation sequence name of the column
39
+ attr_accessor :collation_sequence_name
40
+
41
+ # true if the column has a NOT NULL constraint, false otherwise
42
+ attr_accessor :not_null_constraint
43
+
44
+ # true if the column is part of a primary key, false otherwise
45
+ attr_accessor :primary_key
46
+
47
+ # true if the column is AUTO INCREMENT, false otherwise
48
+ attr_accessor :auto_increment
49
+
50
+ ##
51
+ # Create a column with its name and associated table
52
+ #
53
+ def initialize( db, table, name)
54
+ @db = db
55
+ @name = name
56
+ @table = table
57
+ @declared_data_type = nil
58
+ @default_value = nil
59
+ end
60
+
61
+ # true if the column has a default value
62
+ def has_default_value?
63
+ not default_value.nil?
64
+ end
65
+
66
+ # true if the column may have a NULL value
67
+ def nullable?
68
+ not_null_constraint == false
69
+ end
70
+
71
+ # true if the column as a NOT NULL constraint
72
+ def not_null_constraint?
73
+ not_null_constraint
74
+ end
75
+
76
+ # true if the column is a primary key column
77
+ def primary_key?
78
+ primary_key
79
+ end
80
+
81
+ # true if the column is auto increment
82
+ def auto_increment?
83
+ auto_increment
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,14 @@
1
+ module Kernel
2
+ alias :amalgalite_original_require :require
3
+ #
4
+ # hook into the system 'require' to allow for required text or blobs from an
5
+ # amalgalite database.
6
+ #
7
+ def require( filename )
8
+ found = Amalgalite::Requires.require( filename )
9
+ unless found
10
+ found = amalgalite_original_require( filename )
11
+ end
12
+ return found
13
+ end
14
+ end
@@ -0,0 +1,514 @@
1
+ #--
2
+ # Copyright (c) 2008 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+ require 'amalgalite3'
6
+ require 'amalgalite/statement'
7
+ require 'amalgalite/trace_tap'
8
+ require 'amalgalite/profile_tap'
9
+ require 'amalgalite/type_maps/default_map'
10
+
11
+ module Amalgalite
12
+ #
13
+ # The encapsulation of a connection to an SQLite3 database.
14
+ #
15
+ # Example opening and possibly creating a new daabase
16
+ #
17
+ # db = Amalgalite::Database.new( "mydb.db" )
18
+ # db.execute( "SELECT * FROM table" ) do |row|
19
+ # puts row
20
+ # end
21
+ #
22
+ # db.close
23
+ #
24
+ # Open a database read only:
25
+ #
26
+ # db = Amalgalite::Database.new( "mydb.db", "r" )
27
+ #
28
+ #
29
+ class Database
30
+
31
+ # Error thrown if a database is opened with an invalid mode
32
+ class InvalidModeError < ::Amalgalite::Error; end
33
+
34
+ ##
35
+ # container class for holding transaction behavior constants. These are the
36
+ # SQLite values passed to a START TRANSACTION SQL statement.
37
+ #
38
+ class TransactionBehavior
39
+ # no read or write locks are created until the first statement is executed
40
+ # that requries a read or a write
41
+ DEFERRED = "DEFERRED"
42
+
43
+ # a readlock is obtained immediately so that no other process can write to
44
+ # the database
45
+ IMMEDIATE = "IMMEDIATE"
46
+
47
+ # a read+write lock is obtained, no other proces can read or write to the
48
+ # database
49
+ EXCLUSIVE = "EXCLUSIVE"
50
+
51
+ # list of valid transaction behavior constants
52
+ VALID = [ DEFERRED, IMMEDIATE, EXCLUSIVE ]
53
+
54
+ #
55
+ # is the given mode a valid transaction mode
56
+ #
57
+ def self.valid?( mode )
58
+ VALID.include? mode
59
+ end
60
+ end
61
+
62
+ include Amalgalite::SQLite3::Constants
63
+
64
+ # list of valid modes for opening an Amalgalite::Database
65
+ VALID_MODES = {
66
+ "r" => Open::READONLY,
67
+ "r+" => Open::READWRITE,
68
+ "w+" => Open::READWRITE | Open::CREATE,
69
+ }
70
+
71
+ # the low level Amalgalite::SQLite3::Database
72
+ attr_reader :api
73
+
74
+ # An object that follows the TraceTap protocol, or nil. By default this is nil
75
+ attr_reader :trace_tap
76
+
77
+ # An object that follows the ProfileTap protocol, or nil. By default this is nil
78
+ attr_reader :profile_tap
79
+
80
+ # An object that follows the TypeMap protocol, or nil.
81
+ # By default this is an instances of TypeMaps::DefaultMap
82
+ attr_reader :type_map
83
+
84
+ ##
85
+ # Create a new Amalgalite database
86
+ #
87
+ # :call-seq:
88
+ # Amalgalite::Database.new( filename, "w+", opts = {}) -> Database
89
+ #
90
+ # The first parameter is the filename of the sqlite database.
91
+ # The second parameter is the standard file modes of how to open a file.
92
+ #
93
+ # The modes are:
94
+ #
95
+ # * r - Read-only
96
+ # * r+ - Read/write, an error is thrown if the database does not already exist
97
+ # * w+ - Read/write, create a new database if it doesn't exist
98
+ #
99
+ # <tt>w+</tt> is the default as this is how most databases will want to be utilized.
100
+ #
101
+ # opts is a hash of available options for the database:
102
+ #
103
+ # * :utf16 option to set the database to a utf16 encoding if creating a database.
104
+ #
105
+ # By default, databases are created with an encoding of utf8. Setting this to
106
+ # true and opening an already existing database has no effect.
107
+ #
108
+ # *NOTE* Currently :utf16 is not supported by Amalgalite, it is planned
109
+ # for a later release
110
+ #
111
+ #
112
+ def initialize( filename, mode = "w+", opts = {})
113
+ @open = false
114
+ @profile_tap = nil
115
+ @trace_tap = nil
116
+ @type_map = ::Amalgalite::TypeMaps::DefaultMap.new
117
+
118
+ unless VALID_MODES.keys.include?( mode )
119
+ raise InvalidModeError, "#{mode} is invalid, must be one of #{VALID_MODES.keys.join(', ')}"
120
+ end
121
+
122
+ if not File.exist?( filename ) and opts[:utf16] then
123
+ raise NotImplementedError, "Currently Amalgalite has not implemented utf16 support"
124
+ else
125
+ @api = Amalgalite::SQLite3::Database.open( filename, VALID_MODES[mode] )
126
+ end
127
+ @open = true
128
+ end
129
+
130
+ ##
131
+ # Is the database open or not
132
+ #
133
+ def open?
134
+ @open
135
+ end
136
+
137
+ ##
138
+ # Close the database
139
+ #
140
+ def close
141
+ if open? then
142
+ @api.close
143
+ end
144
+ end
145
+
146
+ ##
147
+ # Is the database in autocommit mode or not
148
+ #
149
+ def autocommit?
150
+ @api.autocommit?
151
+ end
152
+
153
+ ##
154
+ # Return the rowid of the last inserted row
155
+ #
156
+ def last_insert_rowid
157
+ @api.last_insert_rowid
158
+ end
159
+
160
+ ##
161
+ # Is the database utf16 or not? A database is utf16 if the encoding is not
162
+ # UTF-8. Database can only be UTF-8 or UTF-16, and the default is UTF-8
163
+ #
164
+ def utf16?
165
+ unless @utf16.nil?
166
+ @utf16 = (encoding != "UTF-8")
167
+ end
168
+ return @utf16
169
+ end
170
+
171
+ ##
172
+ # return the encoding of the database
173
+ #
174
+ def encoding
175
+ unless @encoding
176
+ @encoding = pragma( "encoding" ).first['encoding']
177
+ end
178
+ return @encoding
179
+ end
180
+
181
+ ##
182
+ # return whether or not the database is currently in a transaction or not
183
+ #
184
+ def in_transaction?
185
+ not @api.autocommit?
186
+ end
187
+
188
+ ##
189
+ # return how many rows changed in the last insert, update or delete statement.
190
+ #
191
+ def row_changes
192
+ @api.row_changes
193
+ end
194
+
195
+ ##
196
+ # return how many rows have changed since this connection to the database was
197
+ # opened.
198
+ #
199
+ def total_changes
200
+ @api.total_changes
201
+ end
202
+
203
+ ##
204
+ # Prepare a statement for execution
205
+ #
206
+ # If called with a block, the statement is yielded to the block and the
207
+ # statement is closed when the block is done.
208
+ #
209
+ # db.prepare( "SELECT * FROM table WHERE c = ?" ) do |stmt|
210
+ # list_of_c_values.each do |c|
211
+ # stmt.execute( c ) do |row|
212
+ # puts "when c = #{c} : #{row.inspect}"
213
+ # end
214
+ # end
215
+ # end
216
+ #
217
+ # Or without a block:
218
+ #
219
+ # stmt = db.prepare( "INSERT INTO t1(x, y, z) VALUES ( :
220
+ #
221
+ def prepare( sql )
222
+ stmt = Amalgalite::Statement.new( self, sql )
223
+ if block_given? then
224
+ begin
225
+ yield stmt
226
+ ensure
227
+ stmt.close
228
+ stmt = nil
229
+ end
230
+ end
231
+ return stmt
232
+ end
233
+
234
+ ##
235
+ # Execute a single SQL statement.
236
+ #
237
+ # If called with a block and there are result rows, then they are iteratively
238
+ # yielded to the block.
239
+ #
240
+ # If no block passed and there are results, then a ResultSet is returned.
241
+ # Otherwise nil is returned. On an error an exception is thrown.
242
+ #
243
+ # This is just a wrapper around the preparation of an Amalgalite Statement and
244
+ # iterating over the results.
245
+ #
246
+ def execute( sql, *bind_params )
247
+ stmt = prepare( sql )
248
+ stmt.bind( *bind_params )
249
+ if block_given? then
250
+ stmt.each { |row| yield row }
251
+ else
252
+ return stmt.all_rows
253
+ end
254
+ ensure
255
+ stmt.close if stmt
256
+ end
257
+
258
+ ##
259
+ # Execute a batch of statements, this will execute all the sql in the given
260
+ # string until no more sql can be found in the string. It will bind the
261
+ # same parameters to each statement. All data that would be returned from
262
+ # all of the statements is thrown away.
263
+ #
264
+ # All statements to be executed in the batch must be terminated with a ';'
265
+ # Returns the number of statements executed
266
+ #
267
+ #
268
+ def execute_batch( sql, *bind_params)
269
+ count = 0
270
+ while sql
271
+ prepare( sql ) do |stmt|
272
+ stmt.execute( *bind_params )
273
+ sql = stmt.remaining_sql
274
+ sql = nil unless (sql.index(";") and Amalgalite::SQLite3.complete?( sql ))
275
+ end
276
+ count += 1
277
+ end
278
+ return count
279
+ end
280
+
281
+ ##
282
+ # clear all the current taps
283
+ #
284
+ def clear_taps!
285
+ self.trace_tap = nil
286
+ self.profile_tap = nil
287
+ end
288
+
289
+ ##
290
+ # call-seq:
291
+ # db.trace_tap = obj
292
+ #
293
+ # Register a trace tap.
294
+ #
295
+ # Registering a trace tap measn that the +obj+ registered will have its
296
+ # +trace+ method called with a string parameter at various times.
297
+ # If the object doesn't respond to the +trace+ method then +write+
298
+ # will be called.
299
+ #
300
+ # For instance:
301
+ #
302
+ # db.trace_tap = Amalgalite::TraceTap.new( logger, 'debug' )
303
+ #
304
+ # This will register an instance of TraceTap, which wraps an logger object.
305
+ # On each +trace+ event the TraceTap#trace method will be called, which in
306
+ # turn will call the <tt>logger.debug</tt> method
307
+ #
308
+ # db.trace_tap = $stderr
309
+ #
310
+ # This will register the <tt>$stderr</tt> io stream as a trace tap. Every time a
311
+ # +trace+ event happens then <tt>$stderr.write( msg )</tt> will be called.
312
+ #
313
+ # db.trace_tap = nil
314
+ #
315
+ # This will unregistere the trace tap
316
+ #
317
+ #
318
+ def trace_tap=( tap_obj )
319
+
320
+ # unregister any previous trace tap
321
+ #
322
+ unless @trace_tap.nil?
323
+ @trace_tap.trace( 'unregistered as trace tap' )
324
+ @trace_tap = nil
325
+ end
326
+ return @trace_tap if tap_obj.nil?
327
+
328
+
329
+ # wrap the tap if we need to
330
+ #
331
+ if tap_obj.respond_to?( 'trace' ) then
332
+ @trace_tap = tap_obj
333
+ elsif tap_obj.respond_to?( 'write' ) then
334
+ @trace_tap = Amalgalite::TraceTap.new( tap_obj, 'write' )
335
+ else
336
+ 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."
337
+ end
338
+
339
+ # and do the low level registration
340
+ #
341
+ @api.register_trace_tap( @trace_tap )
342
+
343
+ @trace_tap.trace( 'registered as trace tap' )
344
+ end
345
+
346
+
347
+ ##
348
+ # call-seq:
349
+ # db.profile_tap = obj
350
+ #
351
+ # Register a profile tap.
352
+ #
353
+ # Registering a profile tap means that the +obj+ registered will have its
354
+ # +profile+ method called with an Integer and a String parameter every time
355
+ # a profile event happens. The Integer is the number of nanoseconds it took
356
+ # for the String (SQL) to execute in wall-clock time.
357
+ #
358
+ # That is, every time a profile event happens in SQLite the following is
359
+ # invoked:
360
+ #
361
+ # obj.profile( str, int )
362
+ #
363
+ # For instance:
364
+ #
365
+ # db.profile_tap = Amalgalite::ProfileTap.new( logger, 'debug' )
366
+ #
367
+ # This will register an instance of ProfileTap, which wraps an logger object.
368
+ # On each +profile+ event the ProfileTap#profile method will be called
369
+ # which in turn will call <tt>logger.debug<tt> with a formatted string containing
370
+ # the String and Integer from the profile event.
371
+ #
372
+ # db.profile_tap = nil
373
+ #
374
+ # This will unregister the profile tap
375
+ #
376
+ #
377
+ def profile_tap=( tap_obj )
378
+
379
+ # unregister any previous profile tap
380
+ unless @profile_tap.nil?
381
+ @profile_tap.profile( 'unregistered as profile tap', 0.0 )
382
+ @profile_tap = nil
383
+ end
384
+ return @profile_tap if tap_obj.nil?
385
+
386
+ if tap_obj.respond_to?( 'profile' ) then
387
+ @profile_tap = tap_obj
388
+ else
389
+ raise Amalgalite::Error, "#{tap_obj.class.name} cannot be used to tap. It has no 'profile' method"
390
+ end
391
+ @api.register_profile_tap( @profile_tap )
392
+ @profile_tap.profile( 'registered as profile tap', 0.0 )
393
+ end
394
+
395
+ ##
396
+ # call-seq:
397
+ # db.type_map = DefaultMap.new
398
+ #
399
+ # Assign your own TypeMap instance to do type conversions. The value
400
+ # assigned here must respond to +bind_type_of+ and +result_value_of+
401
+ # methods. See the TypeMap class for more details.
402
+ #
403
+ #
404
+ def type_map=( type_map_obj )
405
+ %w[ bind_type_of result_value_of ].each do |method|
406
+ unless type_map_obj.respond_to?( method )
407
+ raise Amalgalite::Error, "#{type_map_obj.class.name} cannot be used to do type mapping. It does not respond to '#{method}'"
408
+ end
409
+ end
410
+ @type_map = type_map_obj
411
+ end
412
+
413
+ ##
414
+ # :call-seq:
415
+ # db.schema( dbname = "main" ) -> Schema
416
+ #
417
+ # Returns a Schema object containing the table and column structure of the
418
+ # database.
419
+ #
420
+ def schema( dbname = "main" )
421
+ @schema ||= ::Amalgalite::Schema.new( self, dbname )
422
+ end
423
+
424
+ ##
425
+ # :call-seq:
426
+ # db.reload_schema! -> Schema
427
+ #
428
+ # By default once the schema is obtained, it is cached. This is here to
429
+ # force the schema to be reloaded.
430
+ #
431
+ def reload_schema!( dbname = "main" )
432
+ @schema = nil
433
+ schema( dbname )
434
+ end
435
+
436
+ ##
437
+ # Run a pragma command against the database
438
+ #
439
+ # Returns the result set of the pragma
440
+ def pragma( cmd, &block )
441
+ execute("PRAGMA #{cmd}", &block)
442
+ end
443
+
444
+ ##
445
+ # Begin a transaction. The valid transaction types are:
446
+ #
447
+ # DEFERRED:: no read or write locks are created until the first
448
+ # statement is executed that requries a read or a write
449
+ # IMMEDIATE:: a readlock is obtained immediately so that no other process
450
+ # can write to the database
451
+ # EXCLUSIVE:: a read+write lock is obtained, no other proces can read or
452
+ # write to the database
453
+ #
454
+ # As a convenience, these are constants available in the
455
+ # Database::TransactionBehavior class.
456
+ #
457
+ # Amalgalite Transactions are database level transactions, just as SQLite's
458
+ # are.
459
+ #
460
+ # If a block is passed in, then when the block exits, it is guaranteed that
461
+ # either 'COMMIT' or 'ROLLBACK' has been executed.
462
+ #
463
+ # If any exception happens during the transaction that is caught by Amalgalite,
464
+ # then a 'ROLLBACK' is issued when the block closes.
465
+ #
466
+ # If no exception happens during the transaction then a 'COMMIT' is
467
+ # issued upon leaving the block.
468
+ #
469
+ # If no block is passed in then you are on your own.
470
+ #
471
+ # Nested transactions are not supported by SQLite, but they are faked here.
472
+ # If you call transaction within a transaction, no new transaction is
473
+ # started, the current one is just continued.
474
+ #
475
+ def transaction( mode = TransactionBehavior::DEFERRED )
476
+ raise Amalgalite::Error, "Invalid transaction behavior mode #{mode}" unless TransactionBehavior.valid?( mode )
477
+
478
+ # if already in a transaction, no need to start a new one.
479
+ if not in_transaction? then
480
+ execute( "BEGIN #{mode} TRANSACTION" )
481
+ end
482
+
483
+ if block_given? then
484
+ begin
485
+ return ( yield self )
486
+ ensure
487
+ if $! then
488
+ rollback
489
+ raise $!
490
+ else
491
+ commit
492
+ end
493
+ end
494
+ else
495
+ return in_transaction?
496
+ end
497
+ end
498
+
499
+ ##
500
+ # Commit a transaction
501
+ #
502
+ def commit
503
+ execute( "COMMIT" ) if in_transaction?
504
+ end
505
+
506
+ ##
507
+ # Rollback a transaction
508
+ #
509
+ def rollback
510
+ execute( "ROLLBACK" ) if in_transaction?
511
+ end
512
+ end
513
+ end
514
+