amalgalite 1.8.0-x64-mingw-ucrt

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTING.md +60 -0
  3. data/HISTORY.md +386 -0
  4. data/LICENSE +31 -0
  5. data/Manifest.txt +105 -0
  6. data/README.md +62 -0
  7. data/Rakefile +27 -0
  8. data/TODO.md +57 -0
  9. data/bin/amalgalite-pack +147 -0
  10. data/examples/a.rb +9 -0
  11. data/examples/blob.rb +88 -0
  12. data/examples/bootstrap.rb +36 -0
  13. data/examples/define_aggregate.rb +75 -0
  14. data/examples/define_function.rb +104 -0
  15. data/examples/fts5.rb +152 -0
  16. data/examples/gem-db.rb +94 -0
  17. data/examples/require_me.rb +11 -0
  18. data/examples/requires.rb +42 -0
  19. data/examples/schema-info.rb +34 -0
  20. data/ext/amalgalite/c/amalgalite.c +355 -0
  21. data/ext/amalgalite/c/amalgalite.h +151 -0
  22. data/ext/amalgalite/c/amalgalite_blob.c +240 -0
  23. data/ext/amalgalite/c/amalgalite_constants.c +1432 -0
  24. data/ext/amalgalite/c/amalgalite_database.c +1188 -0
  25. data/ext/amalgalite/c/amalgalite_requires_bootstrap.c +282 -0
  26. data/ext/amalgalite/c/amalgalite_statement.c +649 -0
  27. data/ext/amalgalite/c/extconf.rb +71 -0
  28. data/ext/amalgalite/c/gen_constants.rb +353 -0
  29. data/ext/amalgalite/c/notes.txt +134 -0
  30. data/ext/amalgalite/c/sqlite3.c +243616 -0
  31. data/ext/amalgalite/c/sqlite3.h +12894 -0
  32. data/ext/amalgalite/c/sqlite3_options.h +4 -0
  33. data/ext/amalgalite/c/sqlite3ext.h +705 -0
  34. data/lib/amalgalite/3.1/amalgalite.so +0 -0
  35. data/lib/amalgalite/aggregate.rb +73 -0
  36. data/lib/amalgalite/blob.rb +186 -0
  37. data/lib/amalgalite/boolean.rb +42 -0
  38. data/lib/amalgalite/busy_timeout.rb +47 -0
  39. data/lib/amalgalite/column.rb +99 -0
  40. data/lib/amalgalite/core_ext/kernel/require.rb +21 -0
  41. data/lib/amalgalite/csv_table_importer.rb +75 -0
  42. data/lib/amalgalite/database.rb +933 -0
  43. data/lib/amalgalite/function.rb +61 -0
  44. data/lib/amalgalite/index.rb +43 -0
  45. data/lib/amalgalite/memory_database.rb +15 -0
  46. data/lib/amalgalite/packer.rb +231 -0
  47. data/lib/amalgalite/paths.rb +80 -0
  48. data/lib/amalgalite/profile_tap.rb +131 -0
  49. data/lib/amalgalite/progress_handler.rb +21 -0
  50. data/lib/amalgalite/requires.rb +151 -0
  51. data/lib/amalgalite/schema.rb +225 -0
  52. data/lib/amalgalite/sqlite3/constants.rb +95 -0
  53. data/lib/amalgalite/sqlite3/database/function.rb +48 -0
  54. data/lib/amalgalite/sqlite3/database/status.rb +68 -0
  55. data/lib/amalgalite/sqlite3/status.rb +60 -0
  56. data/lib/amalgalite/sqlite3/version.rb +55 -0
  57. data/lib/amalgalite/sqlite3.rb +6 -0
  58. data/lib/amalgalite/statement.rb +421 -0
  59. data/lib/amalgalite/table.rb +91 -0
  60. data/lib/amalgalite/taps/console.rb +27 -0
  61. data/lib/amalgalite/taps/io.rb +74 -0
  62. data/lib/amalgalite/taps.rb +2 -0
  63. data/lib/amalgalite/trace_tap.rb +35 -0
  64. data/lib/amalgalite/type_map.rb +63 -0
  65. data/lib/amalgalite/type_maps/default_map.rb +166 -0
  66. data/lib/amalgalite/type_maps/storage_map.rb +38 -0
  67. data/lib/amalgalite/type_maps/text_map.rb +21 -0
  68. data/lib/amalgalite/version.rb +8 -0
  69. data/lib/amalgalite/view.rb +26 -0
  70. data/lib/amalgalite.rb +51 -0
  71. data/spec/aggregate_spec.rb +158 -0
  72. data/spec/amalgalite_spec.rb +4 -0
  73. data/spec/blob_spec.rb +78 -0
  74. data/spec/boolean_spec.rb +24 -0
  75. data/spec/busy_handler.rb +157 -0
  76. data/spec/data/iso-3166-country.txt +242 -0
  77. data/spec/data/iso-3166-schema.sql +22 -0
  78. data/spec/data/iso-3166-subcountry.txt +3995 -0
  79. data/spec/data/make-iso-db.sh +12 -0
  80. data/spec/database_spec.rb +505 -0
  81. data/spec/default_map_spec.rb +92 -0
  82. data/spec/function_spec.rb +78 -0
  83. data/spec/integeration_spec.rb +97 -0
  84. data/spec/iso_3166_database.rb +58 -0
  85. data/spec/json_spec.rb +24 -0
  86. data/spec/packer_spec.rb +60 -0
  87. data/spec/paths_spec.rb +28 -0
  88. data/spec/progress_handler_spec.rb +91 -0
  89. data/spec/requires_spec.rb +54 -0
  90. data/spec/rtree_spec.rb +66 -0
  91. data/spec/schema_spec.rb +131 -0
  92. data/spec/spec_helper.rb +48 -0
  93. data/spec/sqlite3/constants_spec.rb +108 -0
  94. data/spec/sqlite3/database_status_spec.rb +36 -0
  95. data/spec/sqlite3/status_spec.rb +22 -0
  96. data/spec/sqlite3/version_spec.rb +28 -0
  97. data/spec/sqlite3_spec.rb +53 -0
  98. data/spec/statement_spec.rb +168 -0
  99. data/spec/storage_map_spec.rb +38 -0
  100. data/spec/tap_spec.rb +57 -0
  101. data/spec/text_map_spec.rb +20 -0
  102. data/spec/type_map_spec.rb +14 -0
  103. data/spec/version_spec.rb +8 -0
  104. data/tasks/custom.rake +101 -0
  105. data/tasks/default.rake +244 -0
  106. data/tasks/extension.rake +28 -0
  107. data/tasks/this.rb +208 -0
  108. metadata +325 -0
@@ -0,0 +1,421 @@
1
+ #--
2
+ # Copyright (c) 2008 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+ #
6
+ require 'date'
7
+ require 'arrayfields'
8
+ require 'ostruct'
9
+
10
+ module Amalgalite
11
+ class Statement
12
+
13
+ include ::Amalgalite::SQLite3::Constants
14
+
15
+ attr_reader :db
16
+ attr_reader :api
17
+
18
+ class << self
19
+ # special column names that indicate that indicate the column is a rowid
20
+ def rowid_column_names
21
+ @rowid_column_names ||= %w[ ROWID OID _ROWID_ ]
22
+ end
23
+ end
24
+
25
+ ##
26
+ # Initialize a new statement on the database.
27
+ #
28
+ def initialize( db, sql )
29
+ @db = db
30
+ #prepare_method = @db.utf16? ? :prepare16 : :prepare
31
+ prepare_method = :prepare
32
+ @param_positions = {}
33
+ @stmt_api = @db.api.send( prepare_method, sql )
34
+ @blobs_to_write = []
35
+ @rowid_index = nil
36
+ @result_meta = nil
37
+ @open = true
38
+ end
39
+
40
+ ##
41
+ # is the statement open for business
42
+ #
43
+ def open?
44
+ @open
45
+ end
46
+
47
+ ##
48
+ # Is the special column "ROWID", "OID", or "_ROWID_" used?
49
+ #
50
+ def using_rowid_column?
51
+ not @rowid_index.nil?
52
+ end
53
+
54
+ ##
55
+ # reset the Statement back to it state right after the constructor returned,
56
+ # except if any variables have been bound to parameters, those are still
57
+ # bound.
58
+ #
59
+ def reset!
60
+ @stmt_api.reset!
61
+ @param_positions = {}
62
+ @blobs_to_write.clear
63
+ @rowid_index = nil
64
+ end
65
+
66
+ ##
67
+ # reset the Statement back to it state right after the constructor returned,
68
+ # AND clear all parameter bindings.
69
+ #
70
+ def reset_and_clear_bindings!
71
+ reset!
72
+ @stmt_api.clear_bindings!
73
+ end
74
+
75
+ ##
76
+ # reset the statment in preparation for executing it again
77
+ #
78
+ def reset_for_next_execute!
79
+ @stmt_api.reset!
80
+ @stmt_api.clear_bindings!
81
+ @blobs_to_write.clear
82
+ end
83
+
84
+ ##
85
+ # Execute the statement with the given parameters
86
+ #
87
+ # If a block is given, then yield each returned row to the block. If no
88
+ # block is given then return all rows from the result. No matter what the
89
+ # prepared statement should be reset before returning the final time.
90
+ #
91
+ def execute( *params )
92
+ bind( *params )
93
+ begin
94
+ # save the error state at the beginning of the execution. We only want to
95
+ # reraise the error if it was raised during this execution.
96
+ s_before = $!
97
+ if block_given? then
98
+ while row = next_row
99
+ yield row
100
+ end
101
+ else
102
+ all_rows
103
+ end
104
+ ensure
105
+ s = $!
106
+ begin
107
+ reset_for_next_execute!
108
+ rescue
109
+ # rescuing nothing on purpose
110
+ end
111
+ raise s if s != s_before
112
+ end
113
+ end
114
+
115
+ ##
116
+ # Bind parameters to the sql statement.
117
+ #
118
+ # Bindings in SQLite can have a number of formats:
119
+ #
120
+ # ?
121
+ # ?num
122
+ # :var
123
+ # @var
124
+ # $var
125
+ #
126
+ # Where 'num' is an Integer and 'var'is an alphanumerical variable.
127
+ # They may exist in the SQL for which this Statement was created.
128
+ #
129
+ # Amalgalite binds parameters to these variables in the following manner:
130
+ #
131
+ # If bind is passed in an Array, either as +bind( "foo", "bar", "baz")+ or
132
+ # as bind( ["foo", "bar", "baz"] ) then each of the params is assumed to be
133
+ # positionally bound to the statement( ?, ?num ).
134
+ #
135
+ # If bind is passed a Hash, either as +bind( :foo => 1, :bar => 'sqlite' )+
136
+ # or as bind( { :foo => 1, 'bar' => 'sqlite' }) then it is assumed that each
137
+ # parameter should be bound as a named parameter (:var, @var, $var).
138
+ #
139
+ # If bind is not passed any parameters, or nil, then nothing happens.
140
+ #
141
+ def bind( *params )
142
+ if params.nil? or params.empty? then
143
+ check_parameter_count!( 0 )
144
+ return nil
145
+ end
146
+
147
+ if params.first.instance_of?( Hash ) then
148
+ bind_named_parameters( params.first )
149
+ elsif params.first.instance_of?( Array ) then
150
+ bind_positional_parameters( *params )
151
+ else
152
+ bind_positional_parameters( params )
153
+ end
154
+ end
155
+
156
+ ##
157
+ # Bind parameters to the statement based upon named parameters
158
+ #
159
+ def bind_named_parameters( params )
160
+ check_parameter_count!( params.size )
161
+ params.each_pair do | param, value |
162
+ position = param_position_of( param )
163
+ if position > 0 then
164
+ bind_parameter_to( position, value )
165
+ else
166
+ raise Amalgalite::Error, "Unable to find parameter '#{param}' in SQL statement [#{sql}]"
167
+ end
168
+ end
169
+ end
170
+
171
+ ##
172
+ # Bind parameters to the statements based upon positions.
173
+ #
174
+ def bind_positional_parameters( params )
175
+ check_parameter_count!( params.size )
176
+ params.each_with_index do |value, index|
177
+ position = index + 1
178
+ bind_parameter_to( position, value )
179
+ end
180
+ end
181
+
182
+ ##
183
+ # bind a single parameter to a particular position
184
+ #
185
+ def bind_parameter_to( position, value )
186
+ bind_type = db.type_map.bind_type_of( value )
187
+ case bind_type
188
+ when DataType::FLOAT
189
+ @stmt_api.bind_double( position, value )
190
+ when DataType::INTEGER
191
+ @stmt_api.bind_int64( position, value )
192
+ when DataType::NULL
193
+ @stmt_api.bind_null( position )
194
+ when DataType::TEXT
195
+ @stmt_api.bind_text( position, value.to_s )
196
+ when DataType::BLOB
197
+ if value.incremental? then
198
+ @stmt_api.bind_zeroblob( position, value.length )
199
+ @blobs_to_write << value
200
+ else
201
+ @stmt_api.bind_blob( position, value.source )
202
+ end
203
+ else
204
+ raise ::Amalgalite::Error, "Unknown binding type of #{bind_type} from #{db.type_map.class.name}.bind_type_of"
205
+ end
206
+ end
207
+
208
+
209
+ ##
210
+ # Find and cache the binding parameter indexes
211
+ #
212
+ def param_position_of( name )
213
+ ns = name.to_s
214
+ unless pos = @param_positions[ns]
215
+ pos = @param_positions[ns] = @stmt_api.parameter_index( ns )
216
+ end
217
+ return pos
218
+ end
219
+
220
+ ##
221
+ # Check and make sure that the number of parameters aligns with the number
222
+ # that sqlite expects
223
+ #
224
+ def check_parameter_count!( num )
225
+ expected = @stmt_api.parameter_count
226
+ if num != expected then
227
+ raise Amalgalite::Error, "#{sql} has #{expected} parameters, but #{num} were passed to bind."
228
+ end
229
+ return expected
230
+ end
231
+
232
+ ##
233
+ # Write any blobs that have been bound to parameters to the database. This
234
+ # assumes that the blobs go into the last inserted row
235
+ #
236
+ def write_blobs
237
+ unless @blobs_to_write.empty?
238
+ @blobs_to_write.each do |blob|
239
+ blob.write_to_column!
240
+ end
241
+ end
242
+ end
243
+
244
+ ##
245
+ # Iterate over the results of the statement returning each row of results
246
+ # as a hash by +column_name+. The column names are the value after an
247
+ # 'AS' in the query or default chosen by sqlite.
248
+ #
249
+ def each
250
+ while row = next_row
251
+ yield row
252
+ end
253
+ return self
254
+ end
255
+
256
+ ##
257
+ # Return the next row of data, with type conversion as indicated by the
258
+ # Database#type_map
259
+ #
260
+ def next_row
261
+ row = []
262
+ case rc = @stmt_api.step
263
+ when ResultCode::ROW
264
+ result_meta.each_with_index do |col, idx|
265
+ value = nil
266
+ column_type = @stmt_api.column_type( idx )
267
+ case column_type
268
+ when DataType::TEXT
269
+ value = @stmt_api.column_text( idx )
270
+ when DataType::FLOAT
271
+ value = @stmt_api.column_double( idx )
272
+ when DataType::INTEGER
273
+ value = @stmt_api.column_int64( idx )
274
+ when DataType::NULL
275
+ value = nil
276
+ when DataType::BLOB
277
+ # if the rowid column is encountered, then we can use an incremental
278
+ # blob api, otherwise we have to use the all at once version.
279
+ if using_rowid_column? then
280
+ value = Amalgalite::Blob.new( :db_blob => SQLite3::Blob.new( db.api,
281
+ col.schema.db,
282
+ col.schema.table,
283
+ col.schema.name,
284
+ @stmt_api.column_int64( @rowid_index ),
285
+ "r"),
286
+ :column => col.schema)
287
+ else
288
+ value = Amalgalite::Blob.new( :string => @stmt_api.column_blob( idx ), :column => col.schema )
289
+ end
290
+ else
291
+ raise ::Amalgalite::Error, "BUG! : Unknown SQLite column type of #{column_type}"
292
+ end
293
+
294
+ row << db.type_map.result_value_of( col.schema.declared_data_type, value )
295
+ end
296
+ row.fields = result_fields
297
+ when ResultCode::DONE
298
+ row = nil
299
+ write_blobs
300
+ else
301
+ self.close # must close so that the error message is guaranteed to be pushed into the database handler
302
+ # and we can can call last_error_message on it
303
+ msg = "SQLITE ERROR #{rc} (#{Amalgalite::SQLite3::Constants::ResultCode.name_from_value( rc )}) : #{@db.api.last_error_message}"
304
+ raise Amalgalite::SQLite3::Error, msg
305
+ end
306
+ return row
307
+ end
308
+
309
+ ##
310
+ # Return all rows from the statement as one array
311
+ #
312
+ def all_rows
313
+ rows = []
314
+ while row = next_row
315
+ rows << row
316
+ end
317
+ return rows
318
+ end
319
+
320
+ ##
321
+ # Inspect the statement and gather all the meta information about the
322
+ # results, include the name of the column result column and the origin
323
+ # column. The origin column is the original database.table.column the value
324
+ # comes from.
325
+ #
326
+ # The full meta information from the origin column is also obtained for help
327
+ # in doing type conversion.
328
+ #
329
+ # As iteration over the row meta informatio happens, record if the special
330
+ # "ROWID", "OID", or "_ROWID_" column is encountered. If that column is
331
+ # encountered then we make note of it.
332
+ #
333
+ def result_meta
334
+ unless @result_meta
335
+ meta = []
336
+ column_count.times do |idx|
337
+ column_meta = ::OpenStruct.new
338
+ column_meta.name = @stmt_api.column_name( idx )
339
+
340
+ db_name = @stmt_api.column_database_name( idx )
341
+ tbl_name = @stmt_api.column_table_name( idx )
342
+ col_name = @stmt_api.column_origin_name( idx )
343
+
344
+ column_meta.schema = ::Amalgalite::Column.new( db_name, tbl_name, col_name, idx )
345
+ column_meta.schema.declared_data_type = @stmt_api.column_declared_type( idx )
346
+
347
+ # only check for rowid if we have a table name and it is not one of the
348
+ # sqlite_master tables. We could get recursion in those cases.
349
+ if not using_rowid_column? and tbl_name and
350
+ not %w[ sqlite_master sqlite_temp_master ].include?( tbl_name ) and is_column_rowid?( tbl_name, col_name ) then
351
+ @rowid_index = idx
352
+ end
353
+
354
+ meta << column_meta
355
+ end
356
+
357
+ @result_meta = meta
358
+ end
359
+ return @result_meta
360
+ end
361
+
362
+ ##
363
+ # is the column indicated by the Column a 'rowid' column
364
+ #
365
+ def is_column_rowid?( table_name, column_name )
366
+ table_schema = @db.schema.tables[table_name]
367
+ return false unless table_schema
368
+
369
+ column_schema = table_schema.columns[column_name]
370
+ if column_schema then
371
+ if column_schema.primary_key? and column_schema.declared_data_type and column_schema.declared_data_type.upcase == "INTEGER" then
372
+ return true
373
+ end
374
+ else
375
+ return true if Statement.rowid_column_names.include?( column_name.upcase )
376
+ end
377
+ return false
378
+ end
379
+
380
+ ##
381
+ # Return the array of field names for the result set, the field names are
382
+ # all strings
383
+ #
384
+ def result_fields
385
+ @fields ||= result_meta.collect { |m| m.name }
386
+ end
387
+
388
+ ##
389
+ # Return any unsued SQL from the statement
390
+ #
391
+ def remaining_sql
392
+ @stmt_api.remaining_sql
393
+ end
394
+
395
+
396
+ ##
397
+ # return the number of columns in the result of this query
398
+ #
399
+ def column_count
400
+ @stmt_api.column_count
401
+ end
402
+
403
+ ##
404
+ # return the raw sql that was originally used to prepare the statement
405
+ #
406
+ def sql
407
+ @stmt_api.sql
408
+ end
409
+
410
+ ##
411
+ # Close the statement. The statement is no longer valid for use after it
412
+ # has been closed.
413
+ #
414
+ def close
415
+ if open? then
416
+ @stmt_api.close
417
+ @open = false
418
+ end
419
+ end
420
+ end
421
+ end
@@ -0,0 +1,91 @@
1
+ #--
2
+ # Copyright (c) 2008 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+ require 'set'
6
+ module Amalgalite
7
+ #
8
+ # a class representing the meta information about an SQLite table
9
+ #
10
+ class Table
11
+ # the schema object the table is associated with
12
+ attr_accessor :schema
13
+
14
+ # the table name
15
+ attr_reader :name
16
+
17
+ # the original sql that was used to create this table
18
+ attr_reader :sql
19
+
20
+ # hash of Index objects holding the meta informationa about the indexes
21
+ # on this table. The keys of the indexes variable is the index name
22
+ attr_accessor :indexes
23
+
24
+ # a hash of Column objects holding the meta information about the columns
25
+ # in this table. keys are the column names
26
+ attr_accessor :columns
27
+
28
+ def initialize( name, sql = nil )
29
+ @name = name
30
+ @sql = sql
31
+ @indexes = {}
32
+ @columns = {}
33
+ @schema = nil
34
+ @primary_key = nil
35
+ end
36
+
37
+ # Is the table a temporary table or not
38
+ def temporary?
39
+ schema.temporary?
40
+ end
41
+
42
+ # the Columns in original definition order
43
+ def columns_in_order
44
+ @columns.values.sort_by { |c| c.order }
45
+ end
46
+
47
+ # the column names in original definition order
48
+ def column_names
49
+ columns_in_order.map { |c| c.name }
50
+ end
51
+
52
+ # the columns that make up the primary key
53
+ def primary_key_columns
54
+ @columns.values.find_all { |c| c.primary_key? }
55
+ end
56
+
57
+ # the array of colmuns that make up the primary key of the table
58
+ # since a primary key has an index, we loop over all the indexes for the
59
+ # table and pick the first one that is unique, and all the columns in the
60
+ # index have primary_key? as true.
61
+ #
62
+ # we do this instead of just looking for the columns where primary key is
63
+ # true because we want the columns in primary key order
64
+ def primary_key
65
+ unless @primary_key
66
+ pk_column_names = Set.new( primary_key_columns.collect { |c| c.name } )
67
+ unique_indexes = indexes.values.find_all { |i| i.unique? }
68
+
69
+ pk_result = []
70
+
71
+ unique_indexes.each do |idx|
72
+ idx_column_names = Set.new( idx.columns.collect { |c| c.name } )
73
+ r = idx_column_names ^ pk_column_names
74
+ if r.size == 0 then
75
+ pk_result = idx.columns
76
+ break
77
+ end
78
+ end
79
+
80
+ # no joy, see about just using all the columns that say the are primary
81
+ # keys
82
+ if pk_result.empty? then
83
+ pk_result = self.primary_key_columns
84
+ end
85
+ @primary_key = pk_result
86
+ end
87
+ return @primary_key
88
+ end
89
+ end
90
+ end
91
+
@@ -0,0 +1,27 @@
1
+ #--
2
+ # Copyright (c) 2008 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+
6
+ require 'amalgalite/taps/io'
7
+
8
+ module Amalgalite::Taps
9
+ #
10
+ # Class provide an IO tap that can write to $stdout
11
+ #
12
+ class Stdout < ::Amalgalite::Taps::IO
13
+ def initialize
14
+ super( $stdout )
15
+ end
16
+ end
17
+
18
+ #
19
+ # This class provide an IO tap that can write to $stderr
20
+ #
21
+ class Stderr < ::Amalgalite::Taps::IO
22
+ def initialize
23
+ super( $stderr )
24
+ end
25
+ end
26
+
27
+ end
@@ -0,0 +1,74 @@
1
+ #--
2
+ # Copyright (c) 2008 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+
6
+ require 'amalgalite/profile_tap'
7
+ require 'stringio'
8
+
9
+ module Amalgalite
10
+ module Taps
11
+ #
12
+ # An IOTap is an easy way to send all top information to any IO based
13
+ # object. Both profile and trace tap information can be captured
14
+ # This means you can send the events to STDOUT with:
15
+ #
16
+ # db.profile_tap = db.trace_tap = Amalgalite::Taps::Stdout.new
17
+ #
18
+ #
19
+ class IO
20
+
21
+ attr_reader :profile_tap
22
+ attr_reader :io
23
+ attr_reader :trace_count
24
+
25
+ def initialize( io )
26
+ @io = io
27
+ @profile_tap = ProfileTap.new( self, 'output_profile_event' )
28
+ @trace_count = 0
29
+ end
30
+
31
+ def trace( msg )
32
+ @trace_count += 1
33
+ io.puts msg
34
+ end
35
+
36
+ # need a profile method, it routes through the profile tap which calls back
37
+ # to output_profile_event
38
+ def profile( msg, time )
39
+ @profile_tap.profile(msg, time)
40
+ end
41
+
42
+ def output_profile_event( msg, time )
43
+ io.puts "#{time} : #{msg}"
44
+ end
45
+
46
+ def dump_profile
47
+ samplers.each_pair do |k,v|
48
+ io.puts v.to_s
49
+ end
50
+ end
51
+
52
+ def samplers
53
+ profile_tap.samplers
54
+ end
55
+ end
56
+
57
+ #
58
+ # This class provides an IO tap that writes to a StringIO. The result is
59
+ # available via .to_s or .string.
60
+ #
61
+ class StringIO < ::Amalgalite::Taps::IO
62
+ def initialize
63
+ @stringio = ::StringIO.new
64
+ super( @stringio )
65
+ end
66
+
67
+ def to_s
68
+ @stringio.string
69
+ end
70
+ alias :string :to_s
71
+ end
72
+ end
73
+ end
74
+
@@ -0,0 +1,2 @@
1
+ require 'amalgalite/taps/console'
2
+ require 'amalgalite/taps/io'
@@ -0,0 +1,35 @@
1
+ #--
2
+ # Copyright (c) 2008 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+
6
+ module Amalgalite
7
+ #
8
+ # A TraceTap receives tracing information from SQLite3. It receives the SQL
9
+ # statement being executed as a +msg+ just before the statement first begins
10
+ # executing.
11
+ #
12
+ # A TraceTap is a wrapper around another object and a method. The Tap object
13
+ # will receive the call to +trace+ and redirect that call to another object
14
+ # and method.
15
+ #
16
+ class TraceTap
17
+
18
+ attr_reader :delegate_obj
19
+ attr_reader :delegate_method
20
+
21
+ def initialize( wrapped_obj, send_to = 'trace' )
22
+ unless wrapped_obj.respond_to?( send_to )
23
+ raise Amalgalite::Error, "#{wrapped_obj.class.name} does not respond to #{send_to.to_s} "
24
+ end
25
+
26
+ @delegate_obj = wrapped_obj
27
+ @delegate_method = send_to
28
+ end
29
+
30
+ def trace( msg )
31
+ delegate_obj.send( delegate_method, msg )
32
+ end
33
+ end
34
+ end
35
+