amalgalite 0.2.4 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|