amalgalite 0.4.2-x86-mswin32-60
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY +81 -0
- data/LICENSE +29 -0
- data/README +40 -0
- 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/gem-db.rb +94 -0
- data/examples/requires.rb +54 -0
- data/examples/schema-info.rb +34 -0
- data/ext/amalgalite3.c +201 -0
- data/ext/amalgalite3.h +121 -0
- data/ext/amalgalite3_blob.c +241 -0
- data/ext/amalgalite3_constants.c +221 -0
- data/ext/amalgalite3_database.c +550 -0
- data/ext/amalgalite3_requires_bootstrap.c +210 -0
- data/ext/amalgalite3_statement.c +628 -0
- data/ext/extconf.rb +19 -0
- data/ext/gen_constants.rb +130 -0
- data/ext/rbconfig-mingw.rb +178 -0
- data/ext/sqlite3.c +97092 -0
- data/ext/sqlite3.h +6364 -0
- data/ext/sqlite3_options.h +4 -0
- data/ext/sqlite3ext.h +372 -0
- data/gemspec.rb +55 -0
- data/lib/amalgalite.rb +33 -0
- data/lib/amalgalite/blob.rb +186 -0
- data/lib/amalgalite/boolean.rb +42 -0
- data/lib/amalgalite/column.rb +86 -0
- data/lib/amalgalite/core_ext/kernel/require.rb +14 -0
- data/lib/amalgalite/database.rb +514 -0
- data/lib/amalgalite/index.rb +43 -0
- data/lib/amalgalite/paths.rb +70 -0
- data/lib/amalgalite/profile_tap.rb +130 -0
- data/lib/amalgalite/requires.rb +112 -0
- data/lib/amalgalite/schema.rb +115 -0
- data/lib/amalgalite/sqlite3.rb +6 -0
- data/lib/amalgalite/sqlite3/constants.rb +82 -0
- data/lib/amalgalite/sqlite3/database/status.rb +69 -0
- data/lib/amalgalite/sqlite3/status.rb +61 -0
- data/lib/amalgalite/sqlite3/version.rb +38 -0
- data/lib/amalgalite/statement.rb +394 -0
- data/lib/amalgalite/table.rb +36 -0
- data/lib/amalgalite/taps.rb +2 -0
- data/lib/amalgalite/taps/console.rb +27 -0
- data/lib/amalgalite/taps/io.rb +71 -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 +167 -0
- data/lib/amalgalite/type_maps/storage_map.rb +41 -0
- data/lib/amalgalite/type_maps/text_map.rb +23 -0
- data/lib/amalgalite/version.rb +37 -0
- data/lib/amalgalite/view.rb +26 -0
- data/lib/amalgalite3.so +0 -0
- data/spec/amalgalite_spec.rb +4 -0
- data/spec/blob_spec.rb +81 -0
- data/spec/boolean_spec.rb +23 -0
- data/spec/database_spec.rb +238 -0
- data/spec/default_map_spec.rb +87 -0
- data/spec/integeration_spec.rb +111 -0
- data/spec/paths_spec.rb +28 -0
- data/spec/schema_spec.rb +60 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/sqlite3/constants_spec.rb +65 -0
- data/spec/sqlite3/database_status_spec.rb +36 -0
- data/spec/sqlite3/status_spec.rb +18 -0
- data/spec/sqlite3/version_spec.rb +14 -0
- data/spec/sqlite3_spec.rb +23 -0
- data/spec/statement_spec.rb +134 -0
- data/spec/storage_map_spec.rb +41 -0
- data/spec/tap_spec.rb +59 -0
- data/spec/text_map_spec.rb +23 -0
- data/spec/type_map_spec.rb +17 -0
- data/spec/version_spec.rb +9 -0
- data/tasks/announce.rake +39 -0
- data/tasks/config.rb +110 -0
- data/tasks/distribution.rake +53 -0
- data/tasks/documentation.rake +33 -0
- data/tasks/extension.rake +100 -0
- data/tasks/rspec.rake +32 -0
- data/tasks/rubyforge.rake +59 -0
- data/tasks/utils.rb +80 -0
- metadata +192 -0
@@ -0,0 +1,186 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2008 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details.
|
4
|
+
#++
|
5
|
+
module Amalgalite
|
6
|
+
##
|
7
|
+
# This is the interface to allow Blob objects to be written to and read from
|
8
|
+
# the SQLite database. When using statements, use a Blob object as
|
9
|
+
# the wrapper around the source to be written to the row, and a Blob object is
|
10
|
+
# returned if the the type mapping warrents during select queries.
|
11
|
+
#
|
12
|
+
# For instance during an insert:
|
13
|
+
#
|
14
|
+
# blob_column = db.schema.tables['blobs'].columsn['data']
|
15
|
+
# db.execute("INSERT INTO blobs(name, data) VALUES ( $name, $blob )",
|
16
|
+
# { "$name" => "/path/to/file",
|
17
|
+
# "$blob" => Amalgalite::Blob.new( :file => '/path/to/file',
|
18
|
+
# :column => blob_column) } )
|
19
|
+
#
|
20
|
+
# db.execute("INSERT INTO blobs(id, data) VALUES ($id, $blob )",
|
21
|
+
# { "$name" => 'blobname',
|
22
|
+
# "$blob" => Amalgalite::Blob.new( :io => "something with .read and .length methods",
|
23
|
+
# :column => blob_column) } )
|
24
|
+
#
|
25
|
+
# On select the blob data needs to be read into an IO object
|
26
|
+
#
|
27
|
+
# all_rows = db.execute("SELECT name, blob FROM blobs WHERE name = '/path/to/file' ")
|
28
|
+
# blob_row = all_rows.first
|
29
|
+
# blob_row['blob'].write_to_file( blob_row['name'] )
|
30
|
+
#
|
31
|
+
# Or write to an IO object
|
32
|
+
#
|
33
|
+
# blob_results = {}
|
34
|
+
# db.execute("SELECT name, blob FROM blobs") do |row|
|
35
|
+
# io = StringIO.new
|
36
|
+
# row['blob'].write_to_io( io )
|
37
|
+
# blob_results[row['name']] = io
|
38
|
+
# # or use a shortcut
|
39
|
+
# # blob_results[row['name']] = row['blob'].to_string_io
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# If using a Blob as a conditional, for instance in a WHERE clause then the
|
43
|
+
# Blob must resolvable to a String.
|
44
|
+
#
|
45
|
+
# db.execute("SELECT FROM blobs(name, data) WHERE data = $blob",
|
46
|
+
# { "$blob' => Amalgalite::Blob.new( :string => "A string of data" ) })
|
47
|
+
#
|
48
|
+
class Blob
|
49
|
+
class Error < ::Amalgalite::Error; end
|
50
|
+
class << self
|
51
|
+
def valid_source_params
|
52
|
+
@valid_source_params ||= [ :file, :io, :string, :db_blob ]
|
53
|
+
end
|
54
|
+
|
55
|
+
def default_block_size
|
56
|
+
@default_block_size ||= 8192
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# the object representing the source of the blob
|
61
|
+
attr_reader :source
|
62
|
+
|
63
|
+
# the size in bytes of the of the blob
|
64
|
+
attr_reader :length
|
65
|
+
|
66
|
+
# the size in bytes of the blocks of data to move from the source
|
67
|
+
attr_reader :block_size
|
68
|
+
|
69
|
+
# the column the blob is associated with
|
70
|
+
attr_reader :column
|
71
|
+
|
72
|
+
##
|
73
|
+
# Initialize a new blob, it takes a single parameter, a hash which describes
|
74
|
+
# the source of the blob. The keys of the hash are one of:
|
75
|
+
#
|
76
|
+
# :file : the value is the path to a file on the file system
|
77
|
+
# :io : the value is an object that responds to the the methods +read+
|
78
|
+
# and +length+. +read+ should have the behavior of IO#read
|
79
|
+
# :db_blob : not normally used by an end user, used to initialize a blob
|
80
|
+
# object that is returned from an SQL query.
|
81
|
+
# :string : used when a Blob is part of a WHERE clause or result
|
82
|
+
#
|
83
|
+
# And additional key of :block_size may be used to indicate the maximum size
|
84
|
+
# of a single block of data to move from the source to the destination, this
|
85
|
+
# defaults ot 8192.
|
86
|
+
#
|
87
|
+
def initialize( params )
|
88
|
+
if (Blob.valid_source_params & params.keys).size > 1 then
|
89
|
+
raise Blob::Error, "Only a one of #{Blob.valid_source_params.join(', ')} is allowed to initialize a Blob. #{params.keys.join(', ')} were sent"
|
90
|
+
end
|
91
|
+
|
92
|
+
@source = nil
|
93
|
+
@source_length = 0
|
94
|
+
@close_source_after_read = false
|
95
|
+
@incremental = true
|
96
|
+
@block_size = params[:block_size] || Blob.default_block_size
|
97
|
+
@column = params[:column]
|
98
|
+
|
99
|
+
raise Blob::Error, "A :column parameter is required for a Blob" unless @column or params.has_key?( :string )
|
100
|
+
|
101
|
+
if params.has_key?( :file ) then
|
102
|
+
@source = File.open( params[:file], "r" )
|
103
|
+
@length = File.size( params[:file] )
|
104
|
+
@close_source_after_read = true
|
105
|
+
elsif params.has_key?( :io ) then
|
106
|
+
@source = params[:io]
|
107
|
+
@length = @source.length
|
108
|
+
elsif params.has_key?( :db_blob ) then
|
109
|
+
@source = params[:db_blob]
|
110
|
+
@length = @source.length
|
111
|
+
@close_source_after_read = true
|
112
|
+
elsif params.has_key?( :string ) then
|
113
|
+
@source = params[:string]
|
114
|
+
@length = @source.length
|
115
|
+
@incremental = false
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
##
|
120
|
+
# close the source when done reading from it
|
121
|
+
#
|
122
|
+
def close_source_after_read?
|
123
|
+
@close_source_after_read
|
124
|
+
end
|
125
|
+
|
126
|
+
##
|
127
|
+
# is this an incremental Blob or not
|
128
|
+
#
|
129
|
+
def incremental?
|
130
|
+
@incremental
|
131
|
+
end
|
132
|
+
|
133
|
+
##
|
134
|
+
# Write the Blob to an IO object
|
135
|
+
#
|
136
|
+
def write_to_io( io )
|
137
|
+
if source.respond_to?( :read ) then
|
138
|
+
while buf = source.read( block_size ) do
|
139
|
+
io.write( buf )
|
140
|
+
end
|
141
|
+
else
|
142
|
+
io.write( source.to_s )
|
143
|
+
end
|
144
|
+
|
145
|
+
if close_source_after_read? then
|
146
|
+
source.close
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
##
|
151
|
+
# conver the blob to a string
|
152
|
+
#
|
153
|
+
def to_s
|
154
|
+
to_string_io.string
|
155
|
+
end
|
156
|
+
|
157
|
+
##
|
158
|
+
# write the Blob contents to a StringIO
|
159
|
+
#
|
160
|
+
def to_string_io
|
161
|
+
sio = StringIO.new
|
162
|
+
write_to_io( sio )
|
163
|
+
return sio
|
164
|
+
end
|
165
|
+
|
166
|
+
##
|
167
|
+
# Write the Blob contents to a File.
|
168
|
+
#
|
169
|
+
def write_to_file( filename, modestring="w" )
|
170
|
+
File.open(filename, modestring) do |f|
|
171
|
+
write_to_io( f )
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
##
|
176
|
+
# Write the Blob contents to the column. This assumes that the row_id to
|
177
|
+
# insert into is the last row that was inserted into the db
|
178
|
+
#
|
179
|
+
def write_to_column!
|
180
|
+
last_rowid = column.schema.db.last_insert_rowid
|
181
|
+
SQLite3::Blob.new( column.schema.db.api, column.db, column.table, column.name, last_rowid, "w" ) do |sqlite_blob|
|
182
|
+
write_to_io( sqlite_blob )
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2008 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details.
|
4
|
+
#++
|
5
|
+
module Amalgalite
|
6
|
+
##
|
7
|
+
# Do type conversion on values that could be boolen values into
|
8
|
+
# real 'true' or 'false'
|
9
|
+
#
|
10
|
+
# This is pulled from the possible boolean values from PostgreSQL
|
11
|
+
#
|
12
|
+
class Boolean
|
13
|
+
class << self
|
14
|
+
#
|
15
|
+
# list of downcased strings are potential true values
|
16
|
+
#
|
17
|
+
def true_values
|
18
|
+
@true_values ||= %w[ true t yes y 1 ]
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# list of downcased strings are potential false values
|
23
|
+
#
|
24
|
+
def false_values
|
25
|
+
@false_values ||= %w[ false f no n 0 ]
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# Convert +val+ to a string and attempt to convert it to +true+ or +false+
|
30
|
+
#
|
31
|
+
def to_bool( val )
|
32
|
+
return false if val.nil?
|
33
|
+
unless @to_bool
|
34
|
+
@to_bool = {}
|
35
|
+
true_values.each { |t| @to_bool[t] = true }
|
36
|
+
false_values.each { |f| @to_bool[f] = false }
|
37
|
+
end
|
38
|
+
return @to_bool[val.to_s.downcase]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2008 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details.
|
4
|
+
#++
|
5
|
+
|
6
|
+
require 'amalgalite/boolean'
|
7
|
+
require 'amalgalite/blob'
|
8
|
+
|
9
|
+
module Amalgalite
|
10
|
+
##
|
11
|
+
# a class representing the meta information about an SQLite column, this class
|
12
|
+
# serves both for general Schema level information, and for result set
|
13
|
+
# information from a SELECT query.
|
14
|
+
#
|
15
|
+
class Column
|
16
|
+
# the schema object this column is associated with
|
17
|
+
attr_accessor :schema
|
18
|
+
|
19
|
+
# the database name this column belongs to
|
20
|
+
attr_accessor :db
|
21
|
+
|
22
|
+
# the column name
|
23
|
+
attr_accessor :name
|
24
|
+
|
25
|
+
# the table to which this column belongs
|
26
|
+
attr_accessor :table
|
27
|
+
|
28
|
+
# the default value of the column. This may not have a value and that
|
29
|
+
# either means that there is no default value, or one could not be
|
30
|
+
# determined.
|
31
|
+
#
|
32
|
+
attr_accessor :default_value
|
33
|
+
|
34
|
+
# the declared data type of the column in the original sql that created the
|
35
|
+
# column
|
36
|
+
attr_accessor :declared_data_type
|
37
|
+
|
38
|
+
# the collation sequence name of the column
|
39
|
+
attr_accessor :collation_sequence_name
|
40
|
+
|
41
|
+
# true if the column has a NOT NULL constraint, false otherwise
|
42
|
+
attr_accessor :not_null_constraint
|
43
|
+
|
44
|
+
# true if the column is part of a primary key, false otherwise
|
45
|
+
attr_accessor :primary_key
|
46
|
+
|
47
|
+
# true if the column is AUTO INCREMENT, false otherwise
|
48
|
+
attr_accessor :auto_increment
|
49
|
+
|
50
|
+
##
|
51
|
+
# Create a column with its name and associated table
|
52
|
+
#
|
53
|
+
def initialize( db, table, name)
|
54
|
+
@db = db
|
55
|
+
@name = name
|
56
|
+
@table = table
|
57
|
+
@declared_data_type = nil
|
58
|
+
@default_value = nil
|
59
|
+
end
|
60
|
+
|
61
|
+
# true if the column has a default value
|
62
|
+
def has_default_value?
|
63
|
+
not default_value.nil?
|
64
|
+
end
|
65
|
+
|
66
|
+
# true if the column may have a NULL value
|
67
|
+
def nullable?
|
68
|
+
not_null_constraint == false
|
69
|
+
end
|
70
|
+
|
71
|
+
# true if the column as a NOT NULL constraint
|
72
|
+
def not_null_constraint?
|
73
|
+
not_null_constraint
|
74
|
+
end
|
75
|
+
|
76
|
+
# true if the column is a primary key column
|
77
|
+
def primary_key?
|
78
|
+
primary_key
|
79
|
+
end
|
80
|
+
|
81
|
+
# true if the column is auto increment
|
82
|
+
def auto_increment?
|
83
|
+
auto_increment
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Kernel
|
2
|
+
alias :amalgalite_original_require :require
|
3
|
+
#
|
4
|
+
# hook into the system 'require' to allow for required text or blobs from an
|
5
|
+
# amalgalite database.
|
6
|
+
#
|
7
|
+
def require( filename )
|
8
|
+
found = Amalgalite::Requires.require( filename )
|
9
|
+
unless found
|
10
|
+
found = amalgalite_original_require( filename )
|
11
|
+
end
|
12
|
+
return found
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,514 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2008 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details.
|
4
|
+
#++
|
5
|
+
require 'amalgalite3'
|
6
|
+
require 'amalgalite/statement'
|
7
|
+
require 'amalgalite/trace_tap'
|
8
|
+
require 'amalgalite/profile_tap'
|
9
|
+
require 'amalgalite/type_maps/default_map'
|
10
|
+
|
11
|
+
module Amalgalite
|
12
|
+
#
|
13
|
+
# The encapsulation of a connection to an SQLite3 database.
|
14
|
+
#
|
15
|
+
# Example opening and possibly creating a new daabase
|
16
|
+
#
|
17
|
+
# db = Amalgalite::Database.new( "mydb.db" )
|
18
|
+
# db.execute( "SELECT * FROM table" ) do |row|
|
19
|
+
# puts row
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# db.close
|
23
|
+
#
|
24
|
+
# Open a database read only:
|
25
|
+
#
|
26
|
+
# db = Amalgalite::Database.new( "mydb.db", "r" )
|
27
|
+
#
|
28
|
+
#
|
29
|
+
class Database
|
30
|
+
|
31
|
+
# Error thrown if a database is opened with an invalid mode
|
32
|
+
class InvalidModeError < ::Amalgalite::Error; end
|
33
|
+
|
34
|
+
##
|
35
|
+
# container class for holding transaction behavior constants. These are the
|
36
|
+
# SQLite values passed to a START TRANSACTION SQL statement.
|
37
|
+
#
|
38
|
+
class TransactionBehavior
|
39
|
+
# no read or write locks are created until the first statement is executed
|
40
|
+
# that requries a read or a write
|
41
|
+
DEFERRED = "DEFERRED"
|
42
|
+
|
43
|
+
# a readlock is obtained immediately so that no other process can write to
|
44
|
+
# the database
|
45
|
+
IMMEDIATE = "IMMEDIATE"
|
46
|
+
|
47
|
+
# a read+write lock is obtained, no other proces can read or write to the
|
48
|
+
# database
|
49
|
+
EXCLUSIVE = "EXCLUSIVE"
|
50
|
+
|
51
|
+
# list of valid transaction behavior constants
|
52
|
+
VALID = [ DEFERRED, IMMEDIATE, EXCLUSIVE ]
|
53
|
+
|
54
|
+
#
|
55
|
+
# is the given mode a valid transaction mode
|
56
|
+
#
|
57
|
+
def self.valid?( mode )
|
58
|
+
VALID.include? mode
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
include Amalgalite::SQLite3::Constants
|
63
|
+
|
64
|
+
# list of valid modes for opening an Amalgalite::Database
|
65
|
+
VALID_MODES = {
|
66
|
+
"r" => Open::READONLY,
|
67
|
+
"r+" => Open::READWRITE,
|
68
|
+
"w+" => Open::READWRITE | Open::CREATE,
|
69
|
+
}
|
70
|
+
|
71
|
+
# the low level Amalgalite::SQLite3::Database
|
72
|
+
attr_reader :api
|
73
|
+
|
74
|
+
# An object that follows the TraceTap protocol, or nil. By default this is nil
|
75
|
+
attr_reader :trace_tap
|
76
|
+
|
77
|
+
# An object that follows the ProfileTap protocol, or nil. By default this is nil
|
78
|
+
attr_reader :profile_tap
|
79
|
+
|
80
|
+
# An object that follows the TypeMap protocol, or nil.
|
81
|
+
# By default this is an instances of TypeMaps::DefaultMap
|
82
|
+
attr_reader :type_map
|
83
|
+
|
84
|
+
##
|
85
|
+
# Create a new Amalgalite database
|
86
|
+
#
|
87
|
+
# :call-seq:
|
88
|
+
# Amalgalite::Database.new( filename, "w+", opts = {}) -> Database
|
89
|
+
#
|
90
|
+
# The first parameter is the filename of the sqlite database.
|
91
|
+
# The second parameter is the standard file modes of how to open a file.
|
92
|
+
#
|
93
|
+
# The modes are:
|
94
|
+
#
|
95
|
+
# * r - Read-only
|
96
|
+
# * r+ - Read/write, an error is thrown if the database does not already exist
|
97
|
+
# * w+ - Read/write, create a new database if it doesn't exist
|
98
|
+
#
|
99
|
+
# <tt>w+</tt> is the default as this is how most databases will want to be utilized.
|
100
|
+
#
|
101
|
+
# opts is a hash of available options for the database:
|
102
|
+
#
|
103
|
+
# * :utf16 option to set the database to a utf16 encoding if creating a database.
|
104
|
+
#
|
105
|
+
# By default, databases are created with an encoding of utf8. Setting this to
|
106
|
+
# true and opening an already existing database has no effect.
|
107
|
+
#
|
108
|
+
# *NOTE* Currently :utf16 is not supported by Amalgalite, it is planned
|
109
|
+
# for a later release
|
110
|
+
#
|
111
|
+
#
|
112
|
+
def initialize( filename, mode = "w+", opts = {})
|
113
|
+
@open = false
|
114
|
+
@profile_tap = nil
|
115
|
+
@trace_tap = nil
|
116
|
+
@type_map = ::Amalgalite::TypeMaps::DefaultMap.new
|
117
|
+
|
118
|
+
unless VALID_MODES.keys.include?( mode )
|
119
|
+
raise InvalidModeError, "#{mode} is invalid, must be one of #{VALID_MODES.keys.join(', ')}"
|
120
|
+
end
|
121
|
+
|
122
|
+
if not File.exist?( filename ) and opts[:utf16] then
|
123
|
+
raise NotImplementedError, "Currently Amalgalite has not implemented utf16 support"
|
124
|
+
else
|
125
|
+
@api = Amalgalite::SQLite3::Database.open( filename, VALID_MODES[mode] )
|
126
|
+
end
|
127
|
+
@open = true
|
128
|
+
end
|
129
|
+
|
130
|
+
##
|
131
|
+
# Is the database open or not
|
132
|
+
#
|
133
|
+
def open?
|
134
|
+
@open
|
135
|
+
end
|
136
|
+
|
137
|
+
##
|
138
|
+
# Close the database
|
139
|
+
#
|
140
|
+
def close
|
141
|
+
if open? then
|
142
|
+
@api.close
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
##
|
147
|
+
# Is the database in autocommit mode or not
|
148
|
+
#
|
149
|
+
def autocommit?
|
150
|
+
@api.autocommit?
|
151
|
+
end
|
152
|
+
|
153
|
+
##
|
154
|
+
# Return the rowid of the last inserted row
|
155
|
+
#
|
156
|
+
def last_insert_rowid
|
157
|
+
@api.last_insert_rowid
|
158
|
+
end
|
159
|
+
|
160
|
+
##
|
161
|
+
# Is the database utf16 or not? A database is utf16 if the encoding is not
|
162
|
+
# UTF-8. Database can only be UTF-8 or UTF-16, and the default is UTF-8
|
163
|
+
#
|
164
|
+
def utf16?
|
165
|
+
unless @utf16.nil?
|
166
|
+
@utf16 = (encoding != "UTF-8")
|
167
|
+
end
|
168
|
+
return @utf16
|
169
|
+
end
|
170
|
+
|
171
|
+
##
|
172
|
+
# return the encoding of the database
|
173
|
+
#
|
174
|
+
def encoding
|
175
|
+
unless @encoding
|
176
|
+
@encoding = pragma( "encoding" ).first['encoding']
|
177
|
+
end
|
178
|
+
return @encoding
|
179
|
+
end
|
180
|
+
|
181
|
+
##
|
182
|
+
# return whether or not the database is currently in a transaction or not
|
183
|
+
#
|
184
|
+
def in_transaction?
|
185
|
+
not @api.autocommit?
|
186
|
+
end
|
187
|
+
|
188
|
+
##
|
189
|
+
# return how many rows changed in the last insert, update or delete statement.
|
190
|
+
#
|
191
|
+
def row_changes
|
192
|
+
@api.row_changes
|
193
|
+
end
|
194
|
+
|
195
|
+
##
|
196
|
+
# return how many rows have changed since this connection to the database was
|
197
|
+
# opened.
|
198
|
+
#
|
199
|
+
def total_changes
|
200
|
+
@api.total_changes
|
201
|
+
end
|
202
|
+
|
203
|
+
##
|
204
|
+
# Prepare a statement for execution
|
205
|
+
#
|
206
|
+
# If called with a block, the statement is yielded to the block and the
|
207
|
+
# statement is closed when the block is done.
|
208
|
+
#
|
209
|
+
# db.prepare( "SELECT * FROM table WHERE c = ?" ) do |stmt|
|
210
|
+
# list_of_c_values.each do |c|
|
211
|
+
# stmt.execute( c ) do |row|
|
212
|
+
# puts "when c = #{c} : #{row.inspect}"
|
213
|
+
# end
|
214
|
+
# end
|
215
|
+
# end
|
216
|
+
#
|
217
|
+
# Or without a block:
|
218
|
+
#
|
219
|
+
# stmt = db.prepare( "INSERT INTO t1(x, y, z) VALUES ( :
|
220
|
+
#
|
221
|
+
def prepare( sql )
|
222
|
+
stmt = Amalgalite::Statement.new( self, sql )
|
223
|
+
if block_given? then
|
224
|
+
begin
|
225
|
+
yield stmt
|
226
|
+
ensure
|
227
|
+
stmt.close
|
228
|
+
stmt = nil
|
229
|
+
end
|
230
|
+
end
|
231
|
+
return stmt
|
232
|
+
end
|
233
|
+
|
234
|
+
##
|
235
|
+
# Execute a single SQL statement.
|
236
|
+
#
|
237
|
+
# If called with a block and there are result rows, then they are iteratively
|
238
|
+
# yielded to the block.
|
239
|
+
#
|
240
|
+
# If no block passed and there are results, then a ResultSet is returned.
|
241
|
+
# Otherwise nil is returned. On an error an exception is thrown.
|
242
|
+
#
|
243
|
+
# This is just a wrapper around the preparation of an Amalgalite Statement and
|
244
|
+
# iterating over the results.
|
245
|
+
#
|
246
|
+
def execute( sql, *bind_params )
|
247
|
+
stmt = prepare( sql )
|
248
|
+
stmt.bind( *bind_params )
|
249
|
+
if block_given? then
|
250
|
+
stmt.each { |row| yield row }
|
251
|
+
else
|
252
|
+
return stmt.all_rows
|
253
|
+
end
|
254
|
+
ensure
|
255
|
+
stmt.close if stmt
|
256
|
+
end
|
257
|
+
|
258
|
+
##
|
259
|
+
# Execute a batch of statements, this will execute all the sql in the given
|
260
|
+
# string until no more sql can be found in the string. It will bind the
|
261
|
+
# same parameters to each statement. All data that would be returned from
|
262
|
+
# all of the statements is thrown away.
|
263
|
+
#
|
264
|
+
# All statements to be executed in the batch must be terminated with a ';'
|
265
|
+
# Returns the number of statements executed
|
266
|
+
#
|
267
|
+
#
|
268
|
+
def execute_batch( sql, *bind_params)
|
269
|
+
count = 0
|
270
|
+
while sql
|
271
|
+
prepare( sql ) do |stmt|
|
272
|
+
stmt.execute( *bind_params )
|
273
|
+
sql = stmt.remaining_sql
|
274
|
+
sql = nil unless (sql.index(";") and Amalgalite::SQLite3.complete?( sql ))
|
275
|
+
end
|
276
|
+
count += 1
|
277
|
+
end
|
278
|
+
return count
|
279
|
+
end
|
280
|
+
|
281
|
+
##
|
282
|
+
# clear all the current taps
|
283
|
+
#
|
284
|
+
def clear_taps!
|
285
|
+
self.trace_tap = nil
|
286
|
+
self.profile_tap = nil
|
287
|
+
end
|
288
|
+
|
289
|
+
##
|
290
|
+
# call-seq:
|
291
|
+
# db.trace_tap = obj
|
292
|
+
#
|
293
|
+
# Register a trace tap.
|
294
|
+
#
|
295
|
+
# Registering a trace tap measn that the +obj+ registered will have its
|
296
|
+
# +trace+ method called with a string parameter at various times.
|
297
|
+
# If the object doesn't respond to the +trace+ method then +write+
|
298
|
+
# will be called.
|
299
|
+
#
|
300
|
+
# For instance:
|
301
|
+
#
|
302
|
+
# db.trace_tap = Amalgalite::TraceTap.new( logger, 'debug' )
|
303
|
+
#
|
304
|
+
# This will register an instance of TraceTap, which wraps an logger object.
|
305
|
+
# On each +trace+ event the TraceTap#trace method will be called, which in
|
306
|
+
# turn will call the <tt>logger.debug</tt> method
|
307
|
+
#
|
308
|
+
# db.trace_tap = $stderr
|
309
|
+
#
|
310
|
+
# This will register the <tt>$stderr</tt> io stream as a trace tap. Every time a
|
311
|
+
# +trace+ event happens then <tt>$stderr.write( msg )</tt> will be called.
|
312
|
+
#
|
313
|
+
# db.trace_tap = nil
|
314
|
+
#
|
315
|
+
# This will unregistere the trace tap
|
316
|
+
#
|
317
|
+
#
|
318
|
+
def trace_tap=( tap_obj )
|
319
|
+
|
320
|
+
# unregister any previous trace tap
|
321
|
+
#
|
322
|
+
unless @trace_tap.nil?
|
323
|
+
@trace_tap.trace( 'unregistered as trace tap' )
|
324
|
+
@trace_tap = nil
|
325
|
+
end
|
326
|
+
return @trace_tap if tap_obj.nil?
|
327
|
+
|
328
|
+
|
329
|
+
# wrap the tap if we need to
|
330
|
+
#
|
331
|
+
if tap_obj.respond_to?( 'trace' ) then
|
332
|
+
@trace_tap = tap_obj
|
333
|
+
elsif tap_obj.respond_to?( 'write' ) then
|
334
|
+
@trace_tap = Amalgalite::TraceTap.new( tap_obj, 'write' )
|
335
|
+
else
|
336
|
+
raise Amalgalite::Error, "#{tap_obj.class.name} cannot be used to tap. It has no 'write' or 'trace' method. Look at wrapping it in a Tap instances."
|
337
|
+
end
|
338
|
+
|
339
|
+
# and do the low level registration
|
340
|
+
#
|
341
|
+
@api.register_trace_tap( @trace_tap )
|
342
|
+
|
343
|
+
@trace_tap.trace( 'registered as trace tap' )
|
344
|
+
end
|
345
|
+
|
346
|
+
|
347
|
+
##
|
348
|
+
# call-seq:
|
349
|
+
# db.profile_tap = obj
|
350
|
+
#
|
351
|
+
# Register a profile tap.
|
352
|
+
#
|
353
|
+
# Registering a profile tap means that the +obj+ registered will have its
|
354
|
+
# +profile+ method called with an Integer and a String parameter every time
|
355
|
+
# a profile event happens. The Integer is the number of nanoseconds it took
|
356
|
+
# for the String (SQL) to execute in wall-clock time.
|
357
|
+
#
|
358
|
+
# That is, every time a profile event happens in SQLite the following is
|
359
|
+
# invoked:
|
360
|
+
#
|
361
|
+
# obj.profile( str, int )
|
362
|
+
#
|
363
|
+
# For instance:
|
364
|
+
#
|
365
|
+
# db.profile_tap = Amalgalite::ProfileTap.new( logger, 'debug' )
|
366
|
+
#
|
367
|
+
# This will register an instance of ProfileTap, which wraps an logger object.
|
368
|
+
# On each +profile+ event the ProfileTap#profile method will be called
|
369
|
+
# which in turn will call <tt>logger.debug<tt> with a formatted string containing
|
370
|
+
# the String and Integer from the profile event.
|
371
|
+
#
|
372
|
+
# db.profile_tap = nil
|
373
|
+
#
|
374
|
+
# This will unregister the profile tap
|
375
|
+
#
|
376
|
+
#
|
377
|
+
def profile_tap=( tap_obj )
|
378
|
+
|
379
|
+
# unregister any previous profile tap
|
380
|
+
unless @profile_tap.nil?
|
381
|
+
@profile_tap.profile( 'unregistered as profile tap', 0.0 )
|
382
|
+
@profile_tap = nil
|
383
|
+
end
|
384
|
+
return @profile_tap if tap_obj.nil?
|
385
|
+
|
386
|
+
if tap_obj.respond_to?( 'profile' ) then
|
387
|
+
@profile_tap = tap_obj
|
388
|
+
else
|
389
|
+
raise Amalgalite::Error, "#{tap_obj.class.name} cannot be used to tap. It has no 'profile' method"
|
390
|
+
end
|
391
|
+
@api.register_profile_tap( @profile_tap )
|
392
|
+
@profile_tap.profile( 'registered as profile tap', 0.0 )
|
393
|
+
end
|
394
|
+
|
395
|
+
##
|
396
|
+
# call-seq:
|
397
|
+
# db.type_map = DefaultMap.new
|
398
|
+
#
|
399
|
+
# Assign your own TypeMap instance to do type conversions. The value
|
400
|
+
# assigned here must respond to +bind_type_of+ and +result_value_of+
|
401
|
+
# methods. See the TypeMap class for more details.
|
402
|
+
#
|
403
|
+
#
|
404
|
+
def type_map=( type_map_obj )
|
405
|
+
%w[ bind_type_of result_value_of ].each do |method|
|
406
|
+
unless type_map_obj.respond_to?( method )
|
407
|
+
raise Amalgalite::Error, "#{type_map_obj.class.name} cannot be used to do type mapping. It does not respond to '#{method}'"
|
408
|
+
end
|
409
|
+
end
|
410
|
+
@type_map = type_map_obj
|
411
|
+
end
|
412
|
+
|
413
|
+
##
|
414
|
+
# :call-seq:
|
415
|
+
# db.schema( dbname = "main" ) -> Schema
|
416
|
+
#
|
417
|
+
# Returns a Schema object containing the table and column structure of the
|
418
|
+
# database.
|
419
|
+
#
|
420
|
+
def schema( dbname = "main" )
|
421
|
+
@schema ||= ::Amalgalite::Schema.new( self, dbname )
|
422
|
+
end
|
423
|
+
|
424
|
+
##
|
425
|
+
# :call-seq:
|
426
|
+
# db.reload_schema! -> Schema
|
427
|
+
#
|
428
|
+
# By default once the schema is obtained, it is cached. This is here to
|
429
|
+
# force the schema to be reloaded.
|
430
|
+
#
|
431
|
+
def reload_schema!( dbname = "main" )
|
432
|
+
@schema = nil
|
433
|
+
schema( dbname )
|
434
|
+
end
|
435
|
+
|
436
|
+
##
|
437
|
+
# Run a pragma command against the database
|
438
|
+
#
|
439
|
+
# Returns the result set of the pragma
|
440
|
+
def pragma( cmd, &block )
|
441
|
+
execute("PRAGMA #{cmd}", &block)
|
442
|
+
end
|
443
|
+
|
444
|
+
##
|
445
|
+
# Begin a transaction. The valid transaction types are:
|
446
|
+
#
|
447
|
+
# DEFERRED:: no read or write locks are created until the first
|
448
|
+
# statement is executed that requries a read or a write
|
449
|
+
# IMMEDIATE:: a readlock is obtained immediately so that no other process
|
450
|
+
# can write to the database
|
451
|
+
# EXCLUSIVE:: a read+write lock is obtained, no other proces can read or
|
452
|
+
# write to the database
|
453
|
+
#
|
454
|
+
# As a convenience, these are constants available in the
|
455
|
+
# Database::TransactionBehavior class.
|
456
|
+
#
|
457
|
+
# Amalgalite Transactions are database level transactions, just as SQLite's
|
458
|
+
# are.
|
459
|
+
#
|
460
|
+
# If a block is passed in, then when the block exits, it is guaranteed that
|
461
|
+
# either 'COMMIT' or 'ROLLBACK' has been executed.
|
462
|
+
#
|
463
|
+
# If any exception happens during the transaction that is caught by Amalgalite,
|
464
|
+
# then a 'ROLLBACK' is issued when the block closes.
|
465
|
+
#
|
466
|
+
# If no exception happens during the transaction then a 'COMMIT' is
|
467
|
+
# issued upon leaving the block.
|
468
|
+
#
|
469
|
+
# If no block is passed in then you are on your own.
|
470
|
+
#
|
471
|
+
# Nested transactions are not supported by SQLite, but they are faked here.
|
472
|
+
# If you call transaction within a transaction, no new transaction is
|
473
|
+
# started, the current one is just continued.
|
474
|
+
#
|
475
|
+
def transaction( mode = TransactionBehavior::DEFERRED )
|
476
|
+
raise Amalgalite::Error, "Invalid transaction behavior mode #{mode}" unless TransactionBehavior.valid?( mode )
|
477
|
+
|
478
|
+
# if already in a transaction, no need to start a new one.
|
479
|
+
if not in_transaction? then
|
480
|
+
execute( "BEGIN #{mode} TRANSACTION" )
|
481
|
+
end
|
482
|
+
|
483
|
+
if block_given? then
|
484
|
+
begin
|
485
|
+
return ( yield self )
|
486
|
+
ensure
|
487
|
+
if $! then
|
488
|
+
rollback
|
489
|
+
raise $!
|
490
|
+
else
|
491
|
+
commit
|
492
|
+
end
|
493
|
+
end
|
494
|
+
else
|
495
|
+
return in_transaction?
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
##
|
500
|
+
# Commit a transaction
|
501
|
+
#
|
502
|
+
def commit
|
503
|
+
execute( "COMMIT" ) if in_transaction?
|
504
|
+
end
|
505
|
+
|
506
|
+
##
|
507
|
+
# Rollback a transaction
|
508
|
+
#
|
509
|
+
def rollback
|
510
|
+
execute( "ROLLBACK" ) if in_transaction?
|
511
|
+
end
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|