amalgalite 0.4.2-x86-mswin32-60

Sign up to get free protection for your applications and to get access to all the features.
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
+