amalgalite 0.10.1-x86-mingw32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. data/HISTORY +201 -0
  2. data/LICENSE +29 -0
  3. data/README +51 -0
  4. data/bin/amalgalite-pack +126 -0
  5. data/examples/a.rb +9 -0
  6. data/examples/blob.rb +88 -0
  7. data/examples/bootstrap.rb +36 -0
  8. data/examples/define_aggregate.rb +75 -0
  9. data/examples/define_function.rb +104 -0
  10. data/examples/gem-db.rb +94 -0
  11. data/examples/gems.db +0 -0
  12. data/examples/require_me.rb +11 -0
  13. data/examples/requires.rb +42 -0
  14. data/examples/schema-info.rb +34 -0
  15. data/ext/amalgalite/amalgalite3.c +290 -0
  16. data/ext/amalgalite/amalgalite3.h +151 -0
  17. data/ext/amalgalite/amalgalite3_blob.c +240 -0
  18. data/ext/amalgalite/amalgalite3_constants.c +221 -0
  19. data/ext/amalgalite/amalgalite3_database.c +1148 -0
  20. data/ext/amalgalite/amalgalite3_requires_bootstrap.c +210 -0
  21. data/ext/amalgalite/amalgalite3_statement.c +639 -0
  22. data/ext/amalgalite/extconf.rb +36 -0
  23. data/ext/amalgalite/gen_constants.rb +130 -0
  24. data/ext/amalgalite/sqlite3.c +106729 -0
  25. data/ext/amalgalite/sqlite3.h +5626 -0
  26. data/ext/amalgalite/sqlite3_options.h +4 -0
  27. data/ext/amalgalite/sqlite3ext.h +380 -0
  28. data/gemspec.rb +60 -0
  29. data/lib/amalgalite.rb +43 -0
  30. data/lib/amalgalite/1.8/amalgalite3.so +0 -0
  31. data/lib/amalgalite/1.9/amalgalite3.so +0 -0
  32. data/lib/amalgalite/aggregate.rb +67 -0
  33. data/lib/amalgalite/blob.rb +186 -0
  34. data/lib/amalgalite/boolean.rb +42 -0
  35. data/lib/amalgalite/busy_timeout.rb +47 -0
  36. data/lib/amalgalite/column.rb +97 -0
  37. data/lib/amalgalite/core_ext/kernel/require.rb +21 -0
  38. data/lib/amalgalite/database.rb +947 -0
  39. data/lib/amalgalite/function.rb +61 -0
  40. data/lib/amalgalite/index.rb +43 -0
  41. data/lib/amalgalite/packer.rb +226 -0
  42. data/lib/amalgalite/paths.rb +70 -0
  43. data/lib/amalgalite/profile_tap.rb +131 -0
  44. data/lib/amalgalite/progress_handler.rb +21 -0
  45. data/lib/amalgalite/requires.rb +120 -0
  46. data/lib/amalgalite/schema.rb +191 -0
  47. data/lib/amalgalite/sqlite3.rb +6 -0
  48. data/lib/amalgalite/sqlite3/constants.rb +80 -0
  49. data/lib/amalgalite/sqlite3/database/function.rb +48 -0
  50. data/lib/amalgalite/sqlite3/database/status.rb +68 -0
  51. data/lib/amalgalite/sqlite3/status.rb +60 -0
  52. data/lib/amalgalite/sqlite3/version.rb +37 -0
  53. data/lib/amalgalite/statement.rb +414 -0
  54. data/lib/amalgalite/table.rb +90 -0
  55. data/lib/amalgalite/taps.rb +2 -0
  56. data/lib/amalgalite/taps/console.rb +27 -0
  57. data/lib/amalgalite/taps/io.rb +71 -0
  58. data/lib/amalgalite/trace_tap.rb +35 -0
  59. data/lib/amalgalite/type_map.rb +63 -0
  60. data/lib/amalgalite/type_maps/default_map.rb +167 -0
  61. data/lib/amalgalite/type_maps/storage_map.rb +40 -0
  62. data/lib/amalgalite/type_maps/text_map.rb +22 -0
  63. data/lib/amalgalite/version.rb +37 -0
  64. data/lib/amalgalite/view.rb +26 -0
  65. data/spec/aggregate_spec.rb +169 -0
  66. data/spec/amalgalite_spec.rb +4 -0
  67. data/spec/blob_spec.rb +81 -0
  68. data/spec/boolean_spec.rb +23 -0
  69. data/spec/busy_handler.rb +165 -0
  70. data/spec/database_spec.rb +494 -0
  71. data/spec/default_map_spec.rb +87 -0
  72. data/spec/function_spec.rb +94 -0
  73. data/spec/integeration_spec.rb +111 -0
  74. data/spec/packer_spec.rb +60 -0
  75. data/spec/paths_spec.rb +28 -0
  76. data/spec/progress_handler_spec.rb +105 -0
  77. data/spec/requires_spec.rb +23 -0
  78. data/spec/rtree_spec.rb +71 -0
  79. data/spec/schema_spec.rb +120 -0
  80. data/spec/spec_helper.rb +27 -0
  81. data/spec/sqlite3/constants_spec.rb +65 -0
  82. data/spec/sqlite3/database_status_spec.rb +36 -0
  83. data/spec/sqlite3/status_spec.rb +18 -0
  84. data/spec/sqlite3/version_spec.rb +14 -0
  85. data/spec/sqlite3_spec.rb +53 -0
  86. data/spec/statement_spec.rb +161 -0
  87. data/spec/storage_map_spec.rb +41 -0
  88. data/spec/tap_spec.rb +59 -0
  89. data/spec/text_map_spec.rb +23 -0
  90. data/spec/type_map_spec.rb +17 -0
  91. data/spec/version_spec.rb +15 -0
  92. data/tasks/announce.rake +43 -0
  93. data/tasks/config.rb +107 -0
  94. data/tasks/distribution.rake +77 -0
  95. data/tasks/documentation.rake +32 -0
  96. data/tasks/extension.rake +141 -0
  97. data/tasks/rspec.rake +33 -0
  98. data/tasks/rubyforge.rake +59 -0
  99. data/tasks/utils.rb +80 -0
  100. metadata +237 -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,226 @@
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/database.rb
72
+ amalgalite/index.rb
73
+ amalgalite/paths.rb
74
+ amalgalite/table.rb
75
+ amalgalite/view.rb
76
+ amalgalite/schema.rb
77
+ amalgalite/version.rb
78
+ amalgalite/sqlite3/version.rb
79
+ amalgalite/sqlite3/constants.rb
80
+ amalgalite/sqlite3/status.rb
81
+ amalgalite/sqlite3/database/status.rb
82
+ amalgalite/sqlite3.rb
83
+ amalgalite/taps/io.rb
84
+ amalgalite/taps/console.rb
85
+ amalgalite/taps.rb
86
+ amalgalite/packer.rb
87
+ amalgalite/core_ext/kernel/require.rb
88
+ amalgalite/requires.rb
89
+ ]
90
+ end
91
+ end
92
+
93
+ #
94
+ # Create a new packer instance with the list of items to pack and all the
95
+ # options
96
+ #
97
+ def initialize( options = {} )
98
+ @options = Packer.default_options.merge( options )
99
+ @dbfile = @options[:dbfile] || Requires::Bootstrap::DEFAULT_DB
100
+ end
101
+
102
+ #
103
+ # The SQL to create the table for storing ruby code
104
+ #
105
+ def create_table_sql
106
+ sql = <<-create
107
+ CREATE TABLE #{options[:table_name]} (
108
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
109
+ #{options[:filename_column]} TEXT UNIQUE,
110
+ #{options[:compressed_column]} BOOLEAN,
111
+ #{options[:contents_column]} BLOB
112
+ );
113
+ create
114
+ end
115
+
116
+ #
117
+ # Make sure that the dbfile exists and has the appropriate schema.
118
+ #
119
+ def check_db( db )
120
+ if db.schema.tables[ options[:table_name] ] and options[:drop_table] then
121
+ STDERR.puts "Dropping table #{options[:table_name]}" if options[:verbose]
122
+ db.execute("DROP TABLE #{options[:table_name]}")
123
+ db.reload_schema!
124
+ end
125
+
126
+ unless db.schema.tables[ options[:table_name] ]
127
+ db.execute( create_table_sql )
128
+ db.reload_schema!
129
+ end
130
+
131
+ end
132
+
133
+
134
+ #
135
+ # Stores all the .rb files in the list into the given database. The prefix
136
+ # is the file system path to remove from the front of the path on each file
137
+ #
138
+ # manifest is an array of OpenStructs.
139
+ #
140
+ def pack_files( manifest )
141
+ db = Amalgalite::Database.new( dbfile )
142
+ check_db( db )
143
+ max_width = manifest.collect{ |m| m.require_path.length }.sort.last
144
+ contents_column = db.schema.tables[ options[:table_name] ].columns[ options[:contents_column] ]
145
+ db.transaction do |trans|
146
+ manifest.each do |file_info|
147
+ msg = " -> #{file_info.require_path.ljust( max_width )} : "
148
+ begin
149
+ if options[:merge] then
150
+ trans.execute( "DELETE FROM #{options[:table_name]} WHERE #{options[:filename_column]} = ?", file_info.require_path )
151
+ end
152
+
153
+ trans.prepare("INSERT INTO #{options[:table_name]}(#{options[:filename_column]}, #{options[:compressed_column]}, #{options[:contents_column]}) VALUES( $filename, $compressed, $contents)") do |stmt|
154
+ contents = IO.readlines( file_info.file_path )
155
+ if options[:self] then
156
+ contents.each { |l| l.gsub!( /^(\s*require .*)$/m, "# commented out by #{self.class.name} \\1") }
157
+ end
158
+ contents = contents.join
159
+
160
+ if options[:compressed] then
161
+ contents = Packer.gzip( contents )
162
+ end
163
+ content_io = StringIO.new( contents )
164
+ stmt.execute( "$filename" => file_info.require_path,
165
+ "$contents" => Amalgalite::Blob.new( :io => content_io,
166
+ :column => contents_column ),
167
+ "$compressed" => options[:compressed] )
168
+ STDERR.puts "#{msg} stored #{file_info.file_path}" if options[:verbose]
169
+ end
170
+ rescue => e
171
+ STDERR.puts "#{msg} error #{e}"
172
+ end
173
+ end
174
+ end
175
+ end
176
+
177
+ #
178
+ # given a file, see if it can be found in the ruby load path, if so, return that
179
+ # full path
180
+ #
181
+ def full_path_of( rb_file )
182
+ $LOAD_PATH.each do |load_path|
183
+ guess = File.expand_path( File.join( load_path, rb_file ) )
184
+ return guess if File.exist?( guess )
185
+ end
186
+ return nil
187
+ end
188
+
189
+ #
190
+ # Make the manifest for packing
191
+ #
192
+ def make_manifest( file_list )
193
+ manifest = []
194
+ prefix_path = ::Pathname.new( options[:strip_prefix] )
195
+ file_list.each do |f|
196
+ file_path = ::Pathname.new( File.expand_path( f ) )
197
+ m = ::OpenStruct.new
198
+ # if it is a directory then grab all the .rb files from it
199
+ if File.directory?( file_path ) then
200
+ manifest.concat( make_manifest( Dir.glob( File.join( f, "**", "*.rb" ) ) ) )
201
+ next
202
+ elsif File.readable?( file_path ) then
203
+ m.require_path = file_path.relative_path_from( prefix_path )
204
+ m.file_path = file_path.realpath.to_s
205
+ elsif lp = full_path_of( f ) then
206
+ m.require_path = f
207
+ m.file_path = lp
208
+ else
209
+ STDERR.puts "Unable to add #{f} to the manifest, cannot find the file on disk"
210
+ next
211
+ end
212
+ m.require_path = m.require_path.to_s[ /\A(.*)\.rb\Z/, 1]
213
+ manifest << m
214
+ end
215
+ return manifest
216
+ end
217
+
218
+ #
219
+ # Given a list of files pack them into the associated database and table.
220
+ #
221
+ def pack( file_list )
222
+ manifest = make_manifest( file_list )
223
+ pack_files( manifest )
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,70 @@
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
+ def self.sub_path(sub,*args)
65
+ sp = ::File.join(root_dir, sub) + File::SEPARATOR
66
+ sp = ::File.join(sp, *args) if args
67
+ end
68
+ end
69
+ end
70
+
@@ -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