amalgalite 1.6.0-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTING.md +49 -0
  3. data/HISTORY.md +346 -0
  4. data/LICENSE +31 -0
  5. data/Manifest.txt +104 -0
  6. data/README.md +65 -0
  7. data/Rakefile +26 -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 +1226 -0
  24. data/ext/amalgalite/c/amalgalite_database.c +1178 -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 +62 -0
  28. data/ext/amalgalite/c/gen_constants.rb +330 -0
  29. data/ext/amalgalite/c/notes.txt +134 -0
  30. data/ext/amalgalite/c/sqlite3.c +205352 -0
  31. data/ext/amalgalite/c/sqlite3.h +10727 -0
  32. data/ext/amalgalite/c/sqlite3_options.h +4 -0
  33. data/ext/amalgalite/c/sqlite3ext.h +578 -0
  34. data/lib/amalgalite.rb +51 -0
  35. data/lib/amalgalite/2.0/amalgalite.so +0 -0
  36. data/lib/amalgalite/2.1/amalgalite.so +0 -0
  37. data/lib/amalgalite/2.2/amalgalite.so +0 -0
  38. data/lib/amalgalite/2.3/amalgalite.so +0 -0
  39. data/lib/amalgalite/2.4/amalgalite.so +0 -0
  40. data/lib/amalgalite/aggregate.rb +67 -0
  41. data/lib/amalgalite/blob.rb +186 -0
  42. data/lib/amalgalite/boolean.rb +42 -0
  43. data/lib/amalgalite/busy_timeout.rb +47 -0
  44. data/lib/amalgalite/column.rb +99 -0
  45. data/lib/amalgalite/core_ext/kernel/require.rb +21 -0
  46. data/lib/amalgalite/csv_table_importer.rb +74 -0
  47. data/lib/amalgalite/database.rb +984 -0
  48. data/lib/amalgalite/function.rb +61 -0
  49. data/lib/amalgalite/index.rb +43 -0
  50. data/lib/amalgalite/memory_database.rb +15 -0
  51. data/lib/amalgalite/packer.rb +231 -0
  52. data/lib/amalgalite/paths.rb +80 -0
  53. data/lib/amalgalite/profile_tap.rb +131 -0
  54. data/lib/amalgalite/progress_handler.rb +21 -0
  55. data/lib/amalgalite/requires.rb +151 -0
  56. data/lib/amalgalite/schema.rb +225 -0
  57. data/lib/amalgalite/sqlite3.rb +6 -0
  58. data/lib/amalgalite/sqlite3/constants.rb +95 -0
  59. data/lib/amalgalite/sqlite3/database/function.rb +48 -0
  60. data/lib/amalgalite/sqlite3/database/status.rb +68 -0
  61. data/lib/amalgalite/sqlite3/status.rb +60 -0
  62. data/lib/amalgalite/sqlite3/version.rb +55 -0
  63. data/lib/amalgalite/statement.rb +418 -0
  64. data/lib/amalgalite/table.rb +91 -0
  65. data/lib/amalgalite/taps.rb +2 -0
  66. data/lib/amalgalite/taps/console.rb +27 -0
  67. data/lib/amalgalite/taps/io.rb +71 -0
  68. data/lib/amalgalite/trace_tap.rb +35 -0
  69. data/lib/amalgalite/type_map.rb +63 -0
  70. data/lib/amalgalite/type_maps/default_map.rb +166 -0
  71. data/lib/amalgalite/type_maps/storage_map.rb +38 -0
  72. data/lib/amalgalite/type_maps/text_map.rb +21 -0
  73. data/lib/amalgalite/version.rb +8 -0
  74. data/lib/amalgalite/view.rb +26 -0
  75. data/spec/aggregate_spec.rb +154 -0
  76. data/spec/amalgalite_spec.rb +4 -0
  77. data/spec/blob_spec.rb +78 -0
  78. data/spec/boolean_spec.rb +24 -0
  79. data/spec/busy_handler.rb +157 -0
  80. data/spec/data/iso-3166-country.txt +242 -0
  81. data/spec/data/iso-3166-schema.sql +22 -0
  82. data/spec/data/iso-3166-subcountry.txt +3995 -0
  83. data/spec/data/make-iso-db.sh +12 -0
  84. data/spec/database_spec.rb +508 -0
  85. data/spec/default_map_spec.rb +92 -0
  86. data/spec/function_spec.rb +78 -0
  87. data/spec/integeration_spec.rb +97 -0
  88. data/spec/iso_3166_database.rb +58 -0
  89. data/spec/packer_spec.rb +60 -0
  90. data/spec/paths_spec.rb +28 -0
  91. data/spec/progress_handler_spec.rb +91 -0
  92. data/spec/requires_spec.rb +54 -0
  93. data/spec/rtree_spec.rb +66 -0
  94. data/spec/schema_spec.rb +131 -0
  95. data/spec/spec_helper.rb +48 -0
  96. data/spec/sqlite3/constants_spec.rb +108 -0
  97. data/spec/sqlite3/database_status_spec.rb +36 -0
  98. data/spec/sqlite3/status_spec.rb +22 -0
  99. data/spec/sqlite3/version_spec.rb +28 -0
  100. data/spec/sqlite3_spec.rb +53 -0
  101. data/spec/statement_spec.rb +168 -0
  102. data/spec/storage_map_spec.rb +38 -0
  103. data/spec/tap_spec.rb +57 -0
  104. data/spec/text_map_spec.rb +20 -0
  105. data/spec/type_map_spec.rb +14 -0
  106. data/spec/version_spec.rb +8 -0
  107. data/tasks/custom.rake +102 -0
  108. data/tasks/default.rake +240 -0
  109. data/tasks/extension.rake +38 -0
  110. data/tasks/this.rb +208 -0
  111. metadata +318 -0
@@ -0,0 +1,61 @@
1
+ require 'amalgalite/sqlite3/database/function'
2
+ module Amalgalite
3
+ #
4
+ # A Base class to inherit from for creating your own SQL scalar functions
5
+ # in ruby.
6
+ #
7
+ # These are SQL functions similar to _abs(X)_, _length(X)_, _random()_. Items
8
+ # that take parameters and return value. They have no state between
9
+ # calls. Built in SQLite scalar functions are :
10
+ #
11
+ # * http://www.sqlite.org/lang_corefunc.html
12
+ # * http://www.sqlite.org/lang_datefunc.html
13
+ #
14
+ # Functions defined in Amalgalite databases conform to the Proc interface.
15
+ # Everything that is defined in an Amalgalite database using +define_function+
16
+ # has its +to_proc+ method called. As a result, any Function must also
17
+ # conform to the +to_proc+ protocol.
18
+ #
19
+ # If you choose to use Function as a parent class of your SQL scalar function
20
+ # implementation you should only have implement +call+ with the appropriate
21
+ # _arity_.
22
+ #
23
+ # For instance to implement a _sha1(X)_ SQL function you could implement it as
24
+ #
25
+ # class SQLSha1 < ::Amalgalite::Function
26
+ # def initialize
27
+ # super( 'md5', 1 )
28
+ # end
29
+ # def call( s )
30
+ # ::Digest::MD5.hexdigest( s.to_s )
31
+ # end
32
+ # end
33
+ #
34
+ class Function
35
+ # The name of the SQL function
36
+ attr_accessor :name
37
+
38
+ # The arity of the SQL function
39
+ attr_accessor :arity
40
+
41
+ # Initialize the function with a name and arity
42
+ def initialize( name, arity )
43
+ @name = name
44
+ @arity = arity
45
+ end
46
+
47
+ # All SQL functions defined foloow the +to_proc+ protocol
48
+ def to_proc
49
+ self
50
+ end
51
+
52
+ # <b>Do Not Override</b>
53
+ #
54
+ # The function signature for use by the Amaglaite datase in tracking
55
+ # function definition and removal.
56
+ #
57
+ def signature
58
+ @signature ||= ::Amalgalite::SQLite3::Database::Function.signature( self.name, self.arity )
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,43 @@
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 class representing the meta information about an SQLite index
9
+ #
10
+ class Index
11
+ # the name of the index
12
+ attr_reader :name
13
+
14
+ # the sql statement that created the index
15
+ attr_reader :sql
16
+
17
+ # the table the index is for
18
+ attr_accessor :table
19
+
20
+ # the columns that make up this index, in index order
21
+ attr_accessor :columns
22
+
23
+ # sqlite sequence number of the index
24
+ attr_accessor :sequence_number
25
+
26
+ # is the index unique
27
+ attr_writer :unique
28
+
29
+ def initialize( name, sql, table )
30
+ @name = name
31
+ @sql = sql
32
+ @table = table
33
+ @columns = []
34
+ @sequence_number = nil
35
+ @unique = nil
36
+ end
37
+
38
+ def unique?
39
+ return @unique
40
+ end
41
+ end
42
+ end
43
+
@@ -0,0 +1,15 @@
1
+ require 'amalgalite/database'
2
+ module Amalgalite
3
+ #
4
+ # The encapsulation of a connection to an SQLite3 in-memory database.
5
+ #
6
+ # Open an in-memory database:
7
+ #
8
+ # db = Amalgalite::MemoryDatabase.new
9
+ #
10
+ class MemoryDatabase < Database
11
+ def initialize
12
+ super( ":memory:" )
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,231 @@
1
+ require 'optparse'
2
+ require 'ostruct'
3
+ require 'pathname'
4
+ require 'zlib'
5
+
6
+ require 'amalgalite'
7
+ module Amalgalite
8
+ #
9
+ # Pack items into an amalgalite database.
10
+ #
11
+ class Packer
12
+ attr_reader :packing_list
13
+ attr_reader :dbfile
14
+ attr_reader :options
15
+
16
+ class << self
17
+ def default_options
18
+ {
19
+ :table_name => Requires::Bootstrap::DEFAULT_TABLE,
20
+ :filename_column => Requires::Bootstrap::DEFAULT_FILENAME_COLUMN,
21
+ :contents_column => Requires::Bootstrap::DEFAULT_CONTENTS_COLUMN,
22
+ :compressed_column => Requires::Bootstrap::DEFAULT_COMPRESSED_COLUMN,
23
+ :strip_prefix => Dir.pwd,
24
+ :compressed => false,
25
+ :verbose => false,
26
+ }
27
+ end
28
+
29
+ #
30
+ # compress data
31
+ #
32
+ def gzip( data )
33
+ zipped = StringIO.new
34
+ Zlib::GzipWriter.wrap( zipped ) do |io|
35
+ io.write( data )
36
+ end
37
+ return zipped.string
38
+ end
39
+
40
+ #
41
+ # uncompress gzip data
42
+ #
43
+ def gunzip( data )
44
+ data = StringIO.new( data )
45
+ Zlib::GzipReader.new( data ).read
46
+ end
47
+
48
+
49
+ #
50
+ # return the files in their dependency order for use for packing into a
51
+ # database
52
+ #
53
+ def amalgalite_require_order
54
+ @require_order ||= %w[
55
+ amalgalite.rb
56
+ amalgalite/sqlite3/database/function.rb
57
+ amalgalite/aggregate.rb
58
+ amalgalite/blob.rb
59
+ amalgalite/boolean.rb
60
+ amalgalite/busy_timeout.rb
61
+ amalgalite/column.rb
62
+ amalgalite/statement.rb
63
+ amalgalite/trace_tap.rb
64
+ amalgalite/profile_tap.rb
65
+ amalgalite/type_map.rb
66
+ amalgalite/type_maps/storage_map.rb
67
+ amalgalite/type_maps/text_map.rb
68
+ amalgalite/type_maps/default_map.rb
69
+ amalgalite/function.rb
70
+ amalgalite/progress_handler.rb
71
+ amalgalite/csv_table_importer.rb
72
+ amalgalite/database.rb
73
+ amalgalite/index.rb
74
+ amalgalite/memory_database.rb
75
+ amalgalite/paths.rb
76
+ amalgalite/table.rb
77
+ amalgalite/view.rb
78
+ amalgalite/schema.rb
79
+ amalgalite/version.rb
80
+ amalgalite/sqlite3/version.rb
81
+ amalgalite/sqlite3/constants.rb
82
+ amalgalite/sqlite3/status.rb
83
+ amalgalite/sqlite3/database/status.rb
84
+ amalgalite/sqlite3.rb
85
+ amalgalite/taps/io.rb
86
+ amalgalite/taps/console.rb
87
+ amalgalite/taps.rb
88
+ amalgalite/packer.rb
89
+ amalgalite/core_ext/kernel/require.rb
90
+ amalgalite/requires.rb
91
+ ]
92
+ end
93
+ end
94
+
95
+ #
96
+ # Create a new packer instance with the list of items to pack and all the
97
+ # options
98
+ #
99
+ def initialize( options = {} )
100
+ @options = Packer.default_options.merge( options )
101
+ @dbfile = @options[:dbfile] || Requires::Bootstrap::DEFAULT_DB
102
+ end
103
+
104
+ #
105
+ # The SQL to create the table for storing ruby code
106
+ #
107
+ def create_table_sql
108
+ <<-create
109
+ CREATE TABLE #{options[:table_name]} (
110
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
111
+ #{options[:filename_column]} TEXT UNIQUE,
112
+ #{options[:compressed_column]} BOOLEAN,
113
+ #{options[:contents_column]} BLOB
114
+ );
115
+ create
116
+ end
117
+
118
+ #
119
+ # Make sure that the dbfile exists and has the appropriate schema.
120
+ #
121
+ def check_db( db )
122
+ if db.schema.tables[ options[:table_name] ] and options[:drop_table] then
123
+ STDERR.puts "Dropping table #{options[:table_name]}" if options[:verbose]
124
+ db.execute("DROP TABLE #{options[:table_name]}")
125
+ db.reload_schema!
126
+ end
127
+
128
+ unless db.schema.tables[ options[:table_name] ]
129
+ db.execute( create_table_sql )
130
+ db.reload_schema!
131
+ end
132
+
133
+ end
134
+
135
+
136
+ #
137
+ # Stores all the .rb files in the list into the given database. The prefix
138
+ # is the file system path to remove from the front of the path on each file
139
+ #
140
+ # manifest is an array of OpenStructs.
141
+ #
142
+ def pack_files( manifest )
143
+ db = Amalgalite::Database.new( dbfile )
144
+ check_db( db )
145
+ max_width = manifest.collect{ |m| m.require_path.length }.sort.last
146
+ contents_column = db.schema.tables[ options[:table_name] ].columns[ options[:contents_column] ]
147
+ db.transaction do |trans|
148
+ manifest.each do |file_info|
149
+ msg = " -> #{file_info.require_path.ljust( max_width )} : "
150
+ begin
151
+ if options[:merge] then
152
+ trans.execute( "DELETE FROM #{options[:table_name]} WHERE #{options[:filename_column]} = ?", file_info.require_path )
153
+ end
154
+
155
+ trans.prepare("INSERT INTO #{options[:table_name]}(#{options[:filename_column]}, #{options[:compressed_column]}, #{options[:contents_column]}) VALUES( $filename, $compressed, $contents)") do |stmt|
156
+ contents = IO.readlines( file_info.file_path )
157
+ if options[:self] then
158
+ contents.each { |l| l.gsub!( /^(\s*require .*)$/m, "# commented out by #{self.class.name} \\1") }
159
+ end
160
+ contents = contents.join
161
+
162
+ if options[:compressed] then
163
+ contents = Packer.gzip( contents )
164
+ end
165
+ content_io = StringIO.new( contents )
166
+ stmt.execute( "$filename" => file_info.require_path,
167
+ "$contents" => Amalgalite::Blob.new( :io => content_io,
168
+ :column => contents_column ),
169
+ "$compressed" => options[:compressed] )
170
+ STDERR.puts "#{msg} stored #{file_info.file_path}" if options[:verbose]
171
+ end
172
+ rescue => e
173
+ STDERR.puts "#{msg} error #{e}"
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ #
180
+ # given a file, see if it can be found in the ruby load path, if so, return that
181
+ # full path
182
+ #
183
+ def full_path_of( rb_file )
184
+ $LOAD_PATH.each do |load_path|
185
+ guess = File.expand_path( File.join( load_path, rb_file ) )
186
+ return guess if File.exist?( guess )
187
+ end
188
+ return nil
189
+ end
190
+
191
+ #
192
+ # Make the manifest for packing
193
+ #
194
+ def make_manifest( file_list )
195
+ manifest = []
196
+ prefix_path = ::Pathname.new( options[:strip_prefix] )
197
+ file_list.each do |f|
198
+ file_path = ::Pathname.new( File.expand_path( f ) )
199
+ m = ::OpenStruct.new
200
+ # if it is a directory then grab all the .rb files from it
201
+ if File.directory?( file_path ) then
202
+ manifest.concat( make_manifest( Dir.glob( File.join( f, "**", "*.rb" ) ) ) )
203
+ next
204
+ elsif File.readable?( file_path ) then
205
+ m.require_path = file_path.relative_path_from( prefix_path )
206
+ m.file_path = file_path.realpath.to_s
207
+ elsif lp = full_path_of( f ) then
208
+ m.require_path = f
209
+ m.file_path = lp
210
+ else
211
+ STDERR.puts "Unable to add #{f} to the manifest, cannot find the file on disk"
212
+ next
213
+ end
214
+ # Make sure that we can handle files without the .rb extension
215
+ # if we have to. This means bin/foo works as a require path
216
+ # without requiring bin/foo to actually be bin/foo.rb
217
+ m.require_path = m.require_path.to_s.sub(/\.rb\Z/,'')
218
+ manifest << m
219
+ end
220
+ return manifest
221
+ end
222
+
223
+ #
224
+ # Given a list of files pack them into the associated database and table.
225
+ #
226
+ def pack( file_list )
227
+ manifest = make_manifest( file_list )
228
+ pack_files( manifest )
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,80 @@
1
+ #--
2
+ # Copyright (c) 2008 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+ module Amalgalite
6
+ #
7
+ # Paths contains helpful methods to determine paths of files inside the
8
+ # Amalgalite library
9
+ #
10
+ module Paths
11
+ #
12
+ # The root directory of the project is considered to be the parent directory
13
+ # of the 'lib' directory.
14
+ #
15
+ # returns:: [String] The full expanded path of the parent directory of 'lib'
16
+ # going up the path from the current file. Trailing
17
+ # File::SEPARATOR is guaranteed.
18
+ #
19
+ def self.root_dir
20
+ @root_dir ||= (
21
+ path_parts = ::File.expand_path(__FILE__).split(::File::SEPARATOR)
22
+ lib_index = path_parts.rindex("lib")
23
+ path_parts[0...lib_index].join(::File::SEPARATOR) + ::File::SEPARATOR
24
+ )
25
+ return @root_dir
26
+ end
27
+
28
+ # returns:: [String] The full expanded path of the +config+ directory
29
+ # below _root_dir_. All parameters passed in are joined onto the
30
+ # result. Trailing File::SEPARATOR is guaranteed if _args_ are
31
+ # *not* present.
32
+ #
33
+ def self.config_path(*args)
34
+ self.sub_path("config", *args)
35
+ end
36
+
37
+ # returns:: [String] The full expanded path of the +data+ directory below
38
+ # _root_dir_. All parameters passed in are joined onto the
39
+ # result. Trailing File::SEPARATOR is guaranteed if
40
+ # _*args_ are *not* present.
41
+ #
42
+ def self.data_path(*args)
43
+ self.sub_path("data", *args)
44
+ end
45
+
46
+ # returns:: [String] The full expanded path of the +lib+ directory below
47
+ # _root_dir_. All parameters passed in are joined onto the
48
+ # result. Trailing File::SEPARATOR is guaranteed if
49
+ # _*args_ are *not* present.
50
+ #
51
+ def self.lib_path(*args)
52
+ self.sub_path("lib", *args)
53
+ end
54
+
55
+ # returns:: [String] The full expanded path of the +ext+ directory below
56
+ # _root_dir_. All parameters passed in are joined onto the
57
+ # result. Trailing File::SEPARATOR is guaranteed if
58
+ # _*args_ are *not* present.
59
+ #
60
+ def self.ext_path(*args)
61
+ self.sub_path("ext", *args)
62
+ end
63
+
64
+ # returns:: [String] The full expanded path of the +spec+ directory below
65
+ # _root_dir_. All parameters passed in are joined onto the
66
+ # result. Trailing File::SEPARATOR is guaranteed if
67
+ # _*args_ are *not* present.
68
+ #
69
+ def self.spec_path(*args)
70
+ self.sub_path("spec", *args)
71
+ end
72
+
73
+
74
+ def self.sub_path(sub,*args)
75
+ sp = ::File.join(root_dir, sub) + File::SEPARATOR
76
+ sp = ::File.join(sp, *args) if args
77
+ end
78
+ end
79
+ end
80
+
@@ -0,0 +1,131 @@
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 ProfileSampler is a sampler of profile times. It aggregates up profile
9
+ # events that happen for the same source. It is based upon the RFuzz::Sampler
10
+ # class from the rfuzz gem
11
+ #
12
+ class ProfileSampler
13
+ #
14
+ # create a new sampler with the given name
15
+ #
16
+ def initialize( name )
17
+ @name = name
18
+ reset!
19
+ end
20
+
21
+ ##
22
+ # reset the internal state so it may be used again
23
+ #
24
+ def reset!
25
+ @sum = 0.0
26
+ @sumsq = 0.0
27
+ @n = 0
28
+ @min = 0.0
29
+ @max = 0.0
30
+ end
31
+
32
+ ##
33
+ # add a sample to the calculations
34
+ #
35
+ def sample( value )
36
+ @sum += value
37
+ @sumsq += (value * value)
38
+ if @n == 0 then
39
+ @min = @max = value
40
+ else
41
+ @min = value if value < @min
42
+ @max = value if value > @max
43
+ end
44
+ @n += 1
45
+ end
46
+
47
+ ##
48
+ # return the mean of the data
49
+ #
50
+ def mean
51
+ @sum / @n
52
+ end
53
+
54
+ ##
55
+ # returns the standard deviation of the data
56
+ #
57
+ def stddev
58
+ begin
59
+ return 0.0 if ( 1 == @n )
60
+ Math.sqrt( (@sumsq - ( @sum * @sum / @n)) / (@n-1) )
61
+ rescue Errno::EDOM
62
+ return 0.0
63
+ end
64
+ end
65
+
66
+ ##
67
+ # return all the values as an array
68
+ #
69
+ def to_a
70
+ [ @name, @sum, @sumsq, @n, mean, stddev, @min, @max ]
71
+ end
72
+
73
+ ##
74
+ # return all the values as a hash
75
+ #
76
+ def to_h
77
+ { 'name' => @name, 'n' => @n,
78
+ 'sum' => @sum, 'sumsq' => @sumsq, 'mean' => mean,
79
+ 'stddev' => stddev, 'min' => @min, 'max' => @max }
80
+ end
81
+
82
+ ##
83
+ # return a string containing the sampler summary
84
+ #
85
+ def to_s
86
+ "[%s] => sum: %d, sumsq: %d, n: %d, mean: %0.6f, stddev: %0.6f, min: %d, max: %d" % self.to_a
87
+ end
88
+
89
+ end
90
+
91
+ #
92
+ # A Profile Tap recives +profile+ events from SQLite which involve the number of
93
+ # nanoseconds in wall-clock time it took for a particular thing to happen. In
94
+ # general this +thing+ is an SQL statement.
95
+ #
96
+ # It has a well known +profile+ method which when invoked will write the event
97
+ # to a delegate object.
98
+ #
99
+ #
100
+ class ProfileTap
101
+
102
+ attr_reader :samplers
103
+
104
+ #
105
+ # Create a new ProfileTap object that wraps the given object and calls the
106
+ # method named in +send_to+ ever time a profile event happens.
107
+ #
108
+ def initialize( wrapped_obj, send_to = 'profile' )
109
+ unless wrapped_obj.respond_to?( send_to )
110
+ raise Amalgalite::Error, "#{wrapped_obj.class.name} does not respond to #{send_to.to_s} "
111
+ end
112
+
113
+ @delegate_obj = wrapped_obj
114
+ @delegate_method = send_to
115
+ @samplers = {}
116
+ end
117
+
118
+ #
119
+ # Record the profile information and send the delegate object the msg and
120
+ # time information.
121
+ #
122
+ def profile( msg, time )
123
+ unless sampler = @samplers[msg]
124
+ msg = msg.gsub(/\s+/,' ')
125
+ sampler = @samplers[msg] = ProfileSampler.new( msg )
126
+ end
127
+ sampler.sample( time )
128
+ @delegate_obj.send( @delegate_method, msg, time )
129
+ end
130
+ end
131
+ end