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.
Files changed (49) hide show
  1. data/HISTORY +30 -5
  2. data/bin/amalgalite-pack-into-db +155 -0
  3. data/examples/a.rb +9 -0
  4. data/examples/blob.rb +105 -0
  5. data/examples/bootstrap.rb +36 -0
  6. data/examples/filestore.db +0 -0
  7. data/examples/gem-db.rb +94 -0
  8. data/examples/requires.rb +54 -0
  9. data/examples/schema-info.rb +34 -0
  10. data/ext/amalgalite3.c +40 -31
  11. data/ext/amalgalite3_blob.c +7 -12
  12. data/ext/amalgalite3_constants.c +41 -4
  13. data/ext/amalgalite3_database.c +55 -5
  14. data/ext/amalgalite3_requires_bootstrap.c +204 -0
  15. data/ext/amalgalite3_statement.c +3 -4
  16. data/ext/extconf.rb +2 -0
  17. data/ext/gen_constants.rb +15 -4
  18. data/ext/sqlite3.c +68652 -59046
  19. data/ext/sqlite3.h +2613 -1939
  20. data/ext/sqlite3ext.h +13 -3
  21. data/gemspec.rb +0 -1
  22. data/lib/amalgalite.rb +22 -18
  23. data/lib/amalgalite/core_ext/kernel/require.rb +2 -2
  24. data/lib/amalgalite/database.rb +15 -6
  25. data/lib/amalgalite/index.rb +19 -3
  26. data/lib/amalgalite/requires.rb +37 -0
  27. data/lib/amalgalite/schema.rb +26 -5
  28. data/lib/amalgalite/sqlite3.rb +2 -0
  29. data/lib/amalgalite/sqlite3/constants.rb +51 -14
  30. data/lib/amalgalite/sqlite3/database/status.rb +69 -0
  31. data/lib/amalgalite/sqlite3/status.rb +61 -0
  32. data/lib/amalgalite/statement.rb +1 -1
  33. data/lib/amalgalite/table.rb +5 -5
  34. data/lib/amalgalite/type_map.rb +3 -0
  35. data/lib/amalgalite/version.rb +2 -2
  36. data/spec/blob_spec.rb +1 -1
  37. data/spec/boolean_spec.rb +0 -3
  38. data/spec/database_spec.rb +11 -3
  39. data/spec/schema_spec.rb +14 -0
  40. data/spec/sqlite3/constants_spec.rb +44 -4
  41. data/spec/sqlite3/database_status_spec.rb +36 -0
  42. data/spec/sqlite3/status_spec.rb +18 -0
  43. data/spec/sqlite3/version_spec.rb +3 -3
  44. data/spec/sqlite3_spec.rb +0 -12
  45. data/tasks/announce.rake +2 -1
  46. data/tasks/config.rb +2 -1
  47. data/tasks/distribution.rake +7 -0
  48. data/tasks/rubyforge.rake +14 -6
  49. metadata +53 -36
data/HISTORY CHANGED
@@ -1,21 +1,46 @@
1
1
  = Changelog
2
- == Version 0.2.4
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 for muster integration
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 acces to the SQLite3 errcode and errmsg api
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
@@ -0,0 +1,9 @@
1
+ class A
2
+ def initialize
3
+ puts "Initialized A"
4
+ end
5
+
6
+ def a
7
+ puts "called a"
8
+ end
9
+ end
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
@@ -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