amalgalite 0.4.2-x86-mswin32-60
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 +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
|
+
|