amalgalite 0.2.4 → 0.4.0
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.
- data/HISTORY +30 -5
- data/bin/amalgalite-pack-into-db +155 -0
- data/examples/a.rb +9 -0
- data/examples/blob.rb +105 -0
- data/examples/bootstrap.rb +36 -0
- data/examples/filestore.db +0 -0
- data/examples/gem-db.rb +94 -0
- data/examples/requires.rb +54 -0
- data/examples/schema-info.rb +34 -0
- data/ext/amalgalite3.c +40 -31
- data/ext/amalgalite3_blob.c +7 -12
- data/ext/amalgalite3_constants.c +41 -4
- data/ext/amalgalite3_database.c +55 -5
- data/ext/amalgalite3_requires_bootstrap.c +204 -0
- data/ext/amalgalite3_statement.c +3 -4
- data/ext/extconf.rb +2 -0
- data/ext/gen_constants.rb +15 -4
- data/ext/sqlite3.c +68652 -59046
- data/ext/sqlite3.h +2613 -1939
- data/ext/sqlite3ext.h +13 -3
- data/gemspec.rb +0 -1
- data/lib/amalgalite.rb +22 -18
- data/lib/amalgalite/core_ext/kernel/require.rb +2 -2
- data/lib/amalgalite/database.rb +15 -6
- data/lib/amalgalite/index.rb +19 -3
- data/lib/amalgalite/requires.rb +37 -0
- data/lib/amalgalite/schema.rb +26 -5
- data/lib/amalgalite/sqlite3.rb +2 -0
- data/lib/amalgalite/sqlite3/constants.rb +51 -14
- data/lib/amalgalite/sqlite3/database/status.rb +69 -0
- data/lib/amalgalite/sqlite3/status.rb +61 -0
- data/lib/amalgalite/statement.rb +1 -1
- data/lib/amalgalite/table.rb +5 -5
- data/lib/amalgalite/type_map.rb +3 -0
- data/lib/amalgalite/version.rb +2 -2
- data/spec/blob_spec.rb +1 -1
- data/spec/boolean_spec.rb +0 -3
- data/spec/database_spec.rb +11 -3
- data/spec/schema_spec.rb +14 -0
- data/spec/sqlite3/constants_spec.rb +44 -4
- data/spec/sqlite3/database_status_spec.rb +36 -0
- data/spec/sqlite3/status_spec.rb +18 -0
- data/spec/sqlite3/version_spec.rb +3 -3
- data/spec/sqlite3_spec.rb +0 -12
- data/tasks/announce.rake +2 -1
- data/tasks/config.rb +2 -1
- data/tasks/distribution.rake +7 -0
- data/tasks/rubyforge.rake +14 -6
- metadata +53 -36
data/HISTORY
CHANGED
@@ -1,21 +1,46 @@
|
|
1
1
|
= Changelog
|
2
|
-
== Version 0.
|
2
|
+
== Version 0.4.0 2008-09-14
|
3
|
+
|
4
|
+
* Major Enhancements
|
5
|
+
* update to SQLite3 version 3.6.2 and enable the RTree option by default
|
6
|
+
* Amalgalite::Requires module allowing ruby code to be 'required' from columns
|
7
|
+
in an SQLite database
|
8
|
+
* Amagalite::Requires::Bootstrap extension module enabling low level boot
|
9
|
+
strapping of the pure ruby Amalgalite code from an sqlite database
|
10
|
+
|
11
|
+
* Minor Enhancements
|
12
|
+
* more indepth information about indexes is available via the Index class
|
13
|
+
* add support for sqlite3_status and sqlite3_db_status information
|
14
|
+
|
15
|
+
* Bugfixes
|
16
|
+
* fix nil exception when using a declared_data_type on primary key column that
|
17
|
+
has no declared_data_type
|
18
|
+
* when Database#transaction is passed a block, the return value is the return
|
19
|
+
value of the block
|
20
|
+
* nested transactions are 'faked'. Calling Database#transaction while
|
21
|
+
Databased#in_transaction? is true does not result in an exception, but
|
22
|
+
continues on in the current transaction.
|
23
|
+
* raise LoadError if required in the same program as sqlite3-ruby. These
|
24
|
+
libraries conflict with each other.
|
25
|
+
|
26
|
+
== Version 0.2.4 2008-07-13
|
3
27
|
|
4
28
|
* Bugfixes
|
5
29
|
* fix compilation when ruby is compiled without pthreads using
|
6
30
|
|
7
|
-
== Version 0.2.3
|
31
|
+
== Version 0.2.3 2008-07-12
|
8
32
|
|
9
33
|
* Bugfixes
|
10
34
|
* make sure file permissions are all read before shipping gem
|
11
35
|
|
12
|
-
== Version 0.2.2
|
36
|
+
== Version 0.2.2 2008-07-12
|
13
37
|
|
14
38
|
* Bugfixes
|
15
39
|
* Database#pragma should accept a block just like Database#execute does
|
16
40
|
|
17
41
|
* compatibility fix
|
18
|
-
* convert to using extconf.rb instead of mkrf
|
42
|
+
* convert to using extconf.rb instead of mkrf to enable compilation as a
|
43
|
+
direct ruby extension in the ruby source tree
|
19
44
|
|
20
45
|
== Version 0.2.1 - 2008-07-05
|
21
46
|
|
@@ -32,7 +57,7 @@
|
|
32
57
|
* added examples/gem_db.rb script demonstrating taps and prepared statements
|
33
58
|
* added examples/schema-info.rb script demonstrating meta information
|
34
59
|
* added examples/blob.rb demonstrating incremental blob IO
|
35
|
-
* added
|
60
|
+
* added access to the SQLite3 errcode and errmsg api
|
36
61
|
|
37
62
|
* Bugfixes
|
38
63
|
* added taps.rb for requiring
|
@@ -0,0 +1,155 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
#
|
6
|
+
# A commandline utility to pack all of amalgalite into a database so that it can
|
7
|
+
# be loaded with the C extension only.
|
8
|
+
#
|
9
|
+
|
10
|
+
#
|
11
|
+
# given a file, see if it can be found in the ruby load path, if so, return that
|
12
|
+
# full path
|
13
|
+
#
|
14
|
+
def full_path_of( rb_file )
|
15
|
+
$:.each do |load_path|
|
16
|
+
guess = File.join( load_path, rb_file )
|
17
|
+
return guess if File.exist?( guess )
|
18
|
+
end
|
19
|
+
return nil
|
20
|
+
end
|
21
|
+
|
22
|
+
OPTIONS = { :force => false }
|
23
|
+
|
24
|
+
parser = OptionParser.new do |op|
|
25
|
+
op.banner = "Usage: #{op.program_name} [options] <dbfile>"
|
26
|
+
op.separator ""
|
27
|
+
|
28
|
+
op.on("-f", "--force", "Force overwriting of an existing database") do |f|
|
29
|
+
OPTIONS[:force]= true
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
begin
|
35
|
+
parser.parse!
|
36
|
+
rescue OptionParser::ParseError => pe
|
37
|
+
STDERR.puts "ERROR : #{pe}"
|
38
|
+
STDERR.puts parser
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# capture before and after snapshots of the loaded features to see what changed
|
44
|
+
# when we required amalgalite
|
45
|
+
#
|
46
|
+
require 'rubygems'
|
47
|
+
$: << "lib"
|
48
|
+
$: << "ext"
|
49
|
+
loaded_features_before = $".dup
|
50
|
+
require 'amalgalite/requires'
|
51
|
+
loaded_features_after = $".dup
|
52
|
+
|
53
|
+
#
|
54
|
+
# reorder the items that should be required, putting the system level .rb files
|
55
|
+
# first in the list
|
56
|
+
#
|
57
|
+
amalgalite_needs = loaded_features_after - loaded_features_before
|
58
|
+
core_libs = (amalgalite_needs - Amalgalite::Requires.require_order).delete_if { |l| l.index(".rb").nil? }
|
59
|
+
|
60
|
+
#
|
61
|
+
# check and make sure nothing is missed
|
62
|
+
#
|
63
|
+
core_libs.each do |l|
|
64
|
+
if l.index("malgalite") then
|
65
|
+
STDERR.puts "ERROR! require_order needs an update #{l}"
|
66
|
+
STDERR.puts "run rake test:check_requries and fix"
|
67
|
+
exit 2
|
68
|
+
end
|
69
|
+
end
|
70
|
+
amalgalite_needs = core_libs.concat( Amalgalite::Requires.require_order )
|
71
|
+
|
72
|
+
|
73
|
+
# width value for tidy output
|
74
|
+
max_width = amalgalite_needs.sort_by { |l| l.length }.last.length
|
75
|
+
|
76
|
+
#
|
77
|
+
# Get the filename, and do not overwrite if the file already exists, unless of
|
78
|
+
# course, --force is used
|
79
|
+
#
|
80
|
+
dbfile = ARGV.shift || Amalgalite::Requires::Bootstrap::DEFAULT_DB
|
81
|
+
if OPTIONS[:force] then
|
82
|
+
File.unlink( dbfile ) if File.exist?( dbfile )
|
83
|
+
end
|
84
|
+
|
85
|
+
if File.exist?( dbfile ) then
|
86
|
+
STDERR.puts "ERROR: #{dbfile} already exists, erase manually or use '--force' option"
|
87
|
+
STDERR.puts parser
|
88
|
+
exit 1
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
#
|
93
|
+
# Create the datbase
|
94
|
+
#
|
95
|
+
puts "Creating database #{dbfile}"
|
96
|
+
db = Amalgalite::Database.new( dbfile )
|
97
|
+
table_name = Amalgalite::Requires::Bootstrap::DEFAULT_TABLE
|
98
|
+
rowid_col = Amalgalite::Requires::Bootstrap::DEFAULT_ROWID_COLUMN
|
99
|
+
filename_col = Amalgalite::Requires::Bootstrap::DEFAULT_FILENAME_COLUMN
|
100
|
+
contents_col = Amalgalite::Requires::Bootstrap::DEFAULT_CONTENTS_COLUMN
|
101
|
+
db.execute(<<-create)
|
102
|
+
CREATE TABLE #{table_name} (
|
103
|
+
#{rowid_col} INTEGER PRIMARY KEY AUTOINCREMENT,
|
104
|
+
#{filename_col} TEXT UNIQUE,
|
105
|
+
#{contents_col} BLOB
|
106
|
+
)
|
107
|
+
create
|
108
|
+
db.reload_schema!
|
109
|
+
|
110
|
+
#
|
111
|
+
# for every file in the list, insert it into the database
|
112
|
+
#
|
113
|
+
db.transaction do |dbt|
|
114
|
+
db.prepare( "INSERT INTO #{table_name}(#{filename_col}, #{contents_col}) VALUES ( $filename, $contents )" ) do |stmt|
|
115
|
+
amalgalite_needs.each do |am_requires|
|
116
|
+
|
117
|
+
msg = " skipped, probably a binary extension"
|
118
|
+
|
119
|
+
if File.extname( am_requires ) == ".rb" then
|
120
|
+
full_path = File.expand_path( full_path_of( am_requires ) )
|
121
|
+
|
122
|
+
if full_path and File.readable?( full_path ) then
|
123
|
+
contents = IO.readlines( full_path )
|
124
|
+
contents.each { |l| l.gsub!( /^(\s*require .*)$/m, "# commented out by #{parser.program_name} \\1") }
|
125
|
+
# strip off the .rb
|
126
|
+
rq = am_requires[ /\A(.*)\.rb\Z/, 1]
|
127
|
+
stmt.execute( { "$filename" => rq,
|
128
|
+
"$contents" => Amalgalite::Blob.new( :string => contents.join,
|
129
|
+
:column => dbt.schema.tables[table_name].columns[contents_col] ) } )
|
130
|
+
msg = "stored #{full_path}"
|
131
|
+
end
|
132
|
+
|
133
|
+
puts " -> #{am_requires.ljust( max_width )} : #{msg}"
|
134
|
+
else
|
135
|
+
STDERR.puts "ERROR : #{am_requires} is an invalid file to pack"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
db.close
|
141
|
+
|
142
|
+
puts <<-text
|
143
|
+
|
144
|
+
Packing complete. To utilize the bootstrapping in #{dbfile} you must do
|
145
|
+
one of the following:
|
146
|
+
|
147
|
+
* statically compile the amalgalite C extension into your application
|
148
|
+
* require 'amalgalite3'
|
149
|
+
|
150
|
+
Once one of those is working you can boostrap the Amalgalite library with
|
151
|
+
this line in your code:
|
152
|
+
|
153
|
+
Amalgalite::Requries::Boostrap.lift( 'dbfile' => '#{dbfile}' )
|
154
|
+
|
155
|
+
text
|
data/examples/a.rb
ADDED
data/examples/blob.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#
|
4
|
+
# An Amalgalite example showing how Blob's can be utilized
|
5
|
+
#
|
6
|
+
# We'll make a database with one table, that we store files in. We'll use the
|
7
|
+
# Blob incremental IO to store the files and retrieve them from the database
|
8
|
+
#
|
9
|
+
# This little program will store 1 or more files in the sqlite3 database when
|
10
|
+
# the 'store' action is given, and cat a file to stdout on 'retrieve'
|
11
|
+
#
|
12
|
+
# e.g.
|
13
|
+
#
|
14
|
+
# ruby blob.rb store a.rb b.rb c.rb # => stores a.rb b.rb and c.rb in the db
|
15
|
+
#
|
16
|
+
# ruby blob.rb retrieve a.rb # => dumps a.rb to stdout
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'rubygems'
|
20
|
+
require 'amalgalite'
|
21
|
+
VALID_ACTIONS = %w[ list retrieve store ]
|
22
|
+
def usage
|
23
|
+
STDERR.puts "Usage: #{File.basename($0)} ( #{VALID_ACTIONS.join(' | ')} ) file(s)"
|
24
|
+
exit 1
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# This does the basic command line parsing
|
29
|
+
#
|
30
|
+
usage if ARGV.size < 1
|
31
|
+
action = ARGV.shift
|
32
|
+
usage unless VALID_ACTIONS.include? action
|
33
|
+
file_list = ARGV
|
34
|
+
|
35
|
+
#
|
36
|
+
# create the database if it doesn't exist
|
37
|
+
#
|
38
|
+
db = Amalgalite::Database.new( "filestore.db" )
|
39
|
+
unless db.schema.tables['files']
|
40
|
+
STDERR.puts "Creating files table"
|
41
|
+
db.execute(<<-create)
|
42
|
+
CREATE TABLE files(
|
43
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
44
|
+
path VARCHAR UNIQUE,
|
45
|
+
data BLOB
|
46
|
+
)
|
47
|
+
create
|
48
|
+
db.reload_schema!
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
case action
|
53
|
+
#
|
54
|
+
# list all the files that are stored in the database
|
55
|
+
#
|
56
|
+
when 'list'
|
57
|
+
db.execute("SELECT path FROM files") do |row|
|
58
|
+
puts row['path']
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# if we are doing the store action, then loop over the files and store them in
|
63
|
+
# the database. This will use incremental IO to store the files directly from
|
64
|
+
# the file names.
|
65
|
+
#
|
66
|
+
# It is slightly strange in that you have to tell the Blob object what column
|
67
|
+
# it is going to, but that is necessary at this point to be able to hook
|
68
|
+
# automatically into the lower level incremental blob IO api.
|
69
|
+
#
|
70
|
+
# This also shows using the $var syntax for binding name sql values in a
|
71
|
+
# prepared statement.
|
72
|
+
#
|
73
|
+
when 'store'
|
74
|
+
usage if file_list.empty?
|
75
|
+
db.transaction do |db_in_trans|
|
76
|
+
db_in_trans.prepare("INSERT INTO files(path, data) VALUES( $path, $data )") do |stmt|
|
77
|
+
file_list.each do |file_path|
|
78
|
+
begin
|
79
|
+
if File.exist?( file_path ) then
|
80
|
+
stmt.execute( "$path" => file_path,
|
81
|
+
"$data" => Amalgalite::Blob.new( :file => file_path, :column => db_in_trans.schema.tables['files'].columns['data'] ) )
|
82
|
+
STDERR.puts "inserted #{file_path} with id #{db.last_insert_rowid}"
|
83
|
+
else
|
84
|
+
STDERR.puts "#{file_path} does not exist"
|
85
|
+
end
|
86
|
+
rescue => e
|
87
|
+
STDERR.puts e
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
STDERR.puts "inserted a total of #{db.total_changes} changes"
|
93
|
+
|
94
|
+
#
|
95
|
+
# dump the file that matches the right path to stdout. This also shows
|
96
|
+
# positional sql varible binding using the '?' syntax.
|
97
|
+
#
|
98
|
+
when 'retrieve'
|
99
|
+
usage if file_list.empty?
|
100
|
+
db.execute("SELECT id, path, data FROM files WHERE path = ?", file_list.first) do |row|
|
101
|
+
STDERR.puts "Dumping #{row['path']} to stdout"
|
102
|
+
row['data'].write_to_io( STDOUT )
|
103
|
+
end
|
104
|
+
end
|
105
|
+
db.close
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# An example of requiring all the files in a table via the Bootstrap::lift
|
4
|
+
# method.
|
5
|
+
#
|
6
|
+
# First use the blob.rb example in this same directory to load the a.rb file
|
7
|
+
# into an example database:
|
8
|
+
#
|
9
|
+
# ruby blob.rb store a.rb
|
10
|
+
#
|
11
|
+
# Then run this example.
|
12
|
+
#
|
13
|
+
|
14
|
+
# Require "ONLY" then binary extension, do not +require+ the ruby based code
|
15
|
+
$: << "../ext"
|
16
|
+
require 'amalgalite3'
|
17
|
+
|
18
|
+
# See what the load path is, notice that it is very small
|
19
|
+
puts "Before $\" : #{$".inspect}"
|
20
|
+
|
21
|
+
# tell the binary extension to "require" every file in the filestore.db in the
|
22
|
+
# table 'files' orderd by column 'id'. The 'path' column is added to $" and the
|
23
|
+
# code in 'data' is evaled.
|
24
|
+
Amalgalite::Requires::Bootstrap.lift( "dbfile" => "filestore.db",
|
25
|
+
"table_name" => "files",
|
26
|
+
"rowid_column" => "id",
|
27
|
+
"filename_column" => "path",
|
28
|
+
"contents_column" => "data" )
|
29
|
+
|
30
|
+
# Notice that a.rb is in the list of files that has been required
|
31
|
+
puts "After $\" : #{$".inspect}"
|
32
|
+
|
33
|
+
# and look we prove that the code was eval'd appropriately
|
34
|
+
a = A.new
|
35
|
+
a.a
|
36
|
+
|
Binary file
|
data/examples/gem-db.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#
|
4
|
+
# Basic amalgalite example creating a table, inserting rows and doing various
|
5
|
+
# selects and prepared statements
|
6
|
+
#
|
7
|
+
require 'rubygems'
|
8
|
+
require 'amalgalite'
|
9
|
+
|
10
|
+
#
|
11
|
+
# Create a database, this will create the DB if it doesn't exist
|
12
|
+
#
|
13
|
+
puts "Opening database (version #{Amalgalite::Version})"
|
14
|
+
db = Amalgalite::Database.new("gems.db")
|
15
|
+
|
16
|
+
#
|
17
|
+
# Setup taps into profile and trace information of sqlite, the profile tap will
|
18
|
+
# goto the profile_tap.log file and the trace information will go to the
|
19
|
+
# trace_tap.log file
|
20
|
+
#
|
21
|
+
puts "Establishing taps"
|
22
|
+
db.trace_tap = Amalgalite::Taps::IO.new( trace_tap_file = File.open("trace_tap.log", "w+") )
|
23
|
+
db.profile_tap = Amalgalite::Taps::IO.new( profile_tap_file = File.open("profile_tap.log", "w+") )
|
24
|
+
|
25
|
+
#
|
26
|
+
# Create the schema unless it already exists in the table. The meta information
|
27
|
+
# about the database schema is available as the result of the db.schema method
|
28
|
+
#
|
29
|
+
schema = db.schema
|
30
|
+
unless schema.tables['gems']
|
31
|
+
puts "Create schema"
|
32
|
+
db.execute <<-SQL
|
33
|
+
CREATE TABLE gems (
|
34
|
+
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
35
|
+
name VARCHAR(128),
|
36
|
+
version VARCHAR(32),
|
37
|
+
author VARCHAR(128)
|
38
|
+
);
|
39
|
+
SQL
|
40
|
+
db.reload_schema!
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# Get some data from the system to insert into the database. Since everyone
|
45
|
+
# probably has gems installed, that's a ready known piece of information. We'll
|
46
|
+
# just pull in the latest version of each installed gem and dump some meta
|
47
|
+
# information into a db for testing.
|
48
|
+
#
|
49
|
+
latest_specs = Gem.source_index.latest_specs
|
50
|
+
|
51
|
+
puts "Inserting #{latest_specs.size} rows of gem information..."
|
52
|
+
before = Time.now
|
53
|
+
|
54
|
+
# Inserting bulk rows as a transaction is good practice with SQLite, it is
|
55
|
+
# MUCH faster.
|
56
|
+
db.transaction do |db_in_transaction|
|
57
|
+
db_in_transaction.prepare("INSERT INTO gems(name, version, author) VALUES( :name, :version, :author );") do |stmt|
|
58
|
+
latest_specs.each do |spec|
|
59
|
+
insert_data = {}
|
60
|
+
insert_data[':name'] = spec.name.to_s
|
61
|
+
insert_data[':version'] = spec.version.to_s
|
62
|
+
insert_data[':author'] = spec.authors.join(' ')
|
63
|
+
#puts "Inserting #{insert_data.inspect}"
|
64
|
+
stmt.execute( insert_data )
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
puts "Took #{Time.now - before} seconds"
|
69
|
+
puts "Done Inserting"
|
70
|
+
|
71
|
+
authors_by_number = db.execute("SELECT author, count( name ) as num_gems FROM gems GROUP BY author ORDER BY num_gems DESC")
|
72
|
+
favorite_author = authors_by_number.first
|
73
|
+
puts "Your favorite gem author is <#{favorite_author['author']}>, with #{favorite_author['num_gems']} gems installed."
|
74
|
+
|
75
|
+
#
|
76
|
+
# Now we'll look at the profile sampler and see what information it traced about
|
77
|
+
# our behavoir.
|
78
|
+
#
|
79
|
+
db.profile_tap.samplers.each do |stat_name, stat_values|
|
80
|
+
puts "-" * 20
|
81
|
+
puts stat_values.to_s
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# Clear out the taps (not really necessary, just cleaning up)
|
86
|
+
#
|
87
|
+
db.trace_tap = profile_tap = nil
|
88
|
+
|
89
|
+
#
|
90
|
+
# close things down
|
91
|
+
#
|
92
|
+
db.close
|
93
|
+
trace_tap_file.close
|
94
|
+
profile_tap_file.close
|