amalgalite 1.8.0-x64-mingw-ucrt
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CONTRIBUTING.md +60 -0
- data/HISTORY.md +386 -0
- data/LICENSE +31 -0
- data/Manifest.txt +105 -0
- data/README.md +62 -0
- data/Rakefile +27 -0
- data/TODO.md +57 -0
- data/bin/amalgalite-pack +147 -0
- data/examples/a.rb +9 -0
- data/examples/blob.rb +88 -0
- data/examples/bootstrap.rb +36 -0
- data/examples/define_aggregate.rb +75 -0
- data/examples/define_function.rb +104 -0
- data/examples/fts5.rb +152 -0
- data/examples/gem-db.rb +94 -0
- data/examples/require_me.rb +11 -0
- data/examples/requires.rb +42 -0
- data/examples/schema-info.rb +34 -0
- data/ext/amalgalite/c/amalgalite.c +355 -0
- data/ext/amalgalite/c/amalgalite.h +151 -0
- data/ext/amalgalite/c/amalgalite_blob.c +240 -0
- data/ext/amalgalite/c/amalgalite_constants.c +1432 -0
- data/ext/amalgalite/c/amalgalite_database.c +1188 -0
- data/ext/amalgalite/c/amalgalite_requires_bootstrap.c +282 -0
- data/ext/amalgalite/c/amalgalite_statement.c +649 -0
- data/ext/amalgalite/c/extconf.rb +71 -0
- data/ext/amalgalite/c/gen_constants.rb +353 -0
- data/ext/amalgalite/c/notes.txt +134 -0
- data/ext/amalgalite/c/sqlite3.c +243616 -0
- data/ext/amalgalite/c/sqlite3.h +12894 -0
- data/ext/amalgalite/c/sqlite3_options.h +4 -0
- data/ext/amalgalite/c/sqlite3ext.h +705 -0
- data/lib/amalgalite/3.1/amalgalite.so +0 -0
- data/lib/amalgalite/aggregate.rb +73 -0
- data/lib/amalgalite/blob.rb +186 -0
- data/lib/amalgalite/boolean.rb +42 -0
- data/lib/amalgalite/busy_timeout.rb +47 -0
- data/lib/amalgalite/column.rb +99 -0
- data/lib/amalgalite/core_ext/kernel/require.rb +21 -0
- data/lib/amalgalite/csv_table_importer.rb +75 -0
- data/lib/amalgalite/database.rb +933 -0
- data/lib/amalgalite/function.rb +61 -0
- data/lib/amalgalite/index.rb +43 -0
- data/lib/amalgalite/memory_database.rb +15 -0
- data/lib/amalgalite/packer.rb +231 -0
- data/lib/amalgalite/paths.rb +80 -0
- data/lib/amalgalite/profile_tap.rb +131 -0
- data/lib/amalgalite/progress_handler.rb +21 -0
- data/lib/amalgalite/requires.rb +151 -0
- data/lib/amalgalite/schema.rb +225 -0
- data/lib/amalgalite/sqlite3/constants.rb +95 -0
- data/lib/amalgalite/sqlite3/database/function.rb +48 -0
- data/lib/amalgalite/sqlite3/database/status.rb +68 -0
- data/lib/amalgalite/sqlite3/status.rb +60 -0
- data/lib/amalgalite/sqlite3/version.rb +55 -0
- data/lib/amalgalite/sqlite3.rb +6 -0
- data/lib/amalgalite/statement.rb +421 -0
- data/lib/amalgalite/table.rb +91 -0
- data/lib/amalgalite/taps/console.rb +27 -0
- data/lib/amalgalite/taps/io.rb +74 -0
- data/lib/amalgalite/taps.rb +2 -0
- data/lib/amalgalite/trace_tap.rb +35 -0
- data/lib/amalgalite/type_map.rb +63 -0
- data/lib/amalgalite/type_maps/default_map.rb +166 -0
- data/lib/amalgalite/type_maps/storage_map.rb +38 -0
- data/lib/amalgalite/type_maps/text_map.rb +21 -0
- data/lib/amalgalite/version.rb +8 -0
- data/lib/amalgalite/view.rb +26 -0
- data/lib/amalgalite.rb +51 -0
- data/spec/aggregate_spec.rb +158 -0
- data/spec/amalgalite_spec.rb +4 -0
- data/spec/blob_spec.rb +78 -0
- data/spec/boolean_spec.rb +24 -0
- data/spec/busy_handler.rb +157 -0
- data/spec/data/iso-3166-country.txt +242 -0
- data/spec/data/iso-3166-schema.sql +22 -0
- data/spec/data/iso-3166-subcountry.txt +3995 -0
- data/spec/data/make-iso-db.sh +12 -0
- data/spec/database_spec.rb +505 -0
- data/spec/default_map_spec.rb +92 -0
- data/spec/function_spec.rb +78 -0
- data/spec/integeration_spec.rb +97 -0
- data/spec/iso_3166_database.rb +58 -0
- data/spec/json_spec.rb +24 -0
- data/spec/packer_spec.rb +60 -0
- data/spec/paths_spec.rb +28 -0
- data/spec/progress_handler_spec.rb +91 -0
- data/spec/requires_spec.rb +54 -0
- data/spec/rtree_spec.rb +66 -0
- data/spec/schema_spec.rb +131 -0
- data/spec/spec_helper.rb +48 -0
- data/spec/sqlite3/constants_spec.rb +108 -0
- data/spec/sqlite3/database_status_spec.rb +36 -0
- data/spec/sqlite3/status_spec.rb +22 -0
- data/spec/sqlite3/version_spec.rb +28 -0
- data/spec/sqlite3_spec.rb +53 -0
- data/spec/statement_spec.rb +168 -0
- data/spec/storage_map_spec.rb +38 -0
- data/spec/tap_spec.rb +57 -0
- data/spec/text_map_spec.rb +20 -0
- data/spec/type_map_spec.rb +14 -0
- data/spec/version_spec.rb +8 -0
- data/tasks/custom.rake +101 -0
- data/tasks/default.rake +244 -0
- data/tasks/extension.rake +28 -0
- data/tasks/this.rb +208 -0
- metadata +325 -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
|
+
msg = msg.gsub(/\s+/,' ')
|
124
|
+
unless sampler = @samplers[msg]
|
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
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Amalgalite
|
2
|
+
##
|
3
|
+
# A base class for use in creating your own progress handler classes
|
4
|
+
#
|
5
|
+
class ProgressHandler
|
6
|
+
def to_proc
|
7
|
+
self
|
8
|
+
end
|
9
|
+
|
10
|
+
# the arity of the call method
|
11
|
+
def arity() 0 ; end
|
12
|
+
|
13
|
+
##
|
14
|
+
# Override this method, returning +false+ if the SQLite should act as if
|
15
|
+
# +interrupt!+ had been invoked.
|
16
|
+
#
|
17
|
+
def call
|
18
|
+
raise NotImplementedError, "The progress handler call() method must be implemented"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|