amalgalite 1.8.0-x64-mingw-ucrt
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CONTRIBUTING.md +60 -0
- data/HISTORY.md +386 -0
- data/LICENSE +31 -0
- data/Manifest.txt +105 -0
- data/README.md +62 -0
- data/Rakefile +27 -0
- data/TODO.md +57 -0
- data/bin/amalgalite-pack +147 -0
- data/examples/a.rb +9 -0
- data/examples/blob.rb +88 -0
- data/examples/bootstrap.rb +36 -0
- data/examples/define_aggregate.rb +75 -0
- data/examples/define_function.rb +104 -0
- data/examples/fts5.rb +152 -0
- data/examples/gem-db.rb +94 -0
- data/examples/require_me.rb +11 -0
- data/examples/requires.rb +42 -0
- data/examples/schema-info.rb +34 -0
- data/ext/amalgalite/c/amalgalite.c +355 -0
- data/ext/amalgalite/c/amalgalite.h +151 -0
- data/ext/amalgalite/c/amalgalite_blob.c +240 -0
- data/ext/amalgalite/c/amalgalite_constants.c +1432 -0
- data/ext/amalgalite/c/amalgalite_database.c +1188 -0
- data/ext/amalgalite/c/amalgalite_requires_bootstrap.c +282 -0
- data/ext/amalgalite/c/amalgalite_statement.c +649 -0
- data/ext/amalgalite/c/extconf.rb +71 -0
- data/ext/amalgalite/c/gen_constants.rb +353 -0
- data/ext/amalgalite/c/notes.txt +134 -0
- data/ext/amalgalite/c/sqlite3.c +243616 -0
- data/ext/amalgalite/c/sqlite3.h +12894 -0
- data/ext/amalgalite/c/sqlite3_options.h +4 -0
- data/ext/amalgalite/c/sqlite3ext.h +705 -0
- data/lib/amalgalite/3.1/amalgalite.so +0 -0
- data/lib/amalgalite/aggregate.rb +73 -0
- data/lib/amalgalite/blob.rb +186 -0
- data/lib/amalgalite/boolean.rb +42 -0
- data/lib/amalgalite/busy_timeout.rb +47 -0
- data/lib/amalgalite/column.rb +99 -0
- data/lib/amalgalite/core_ext/kernel/require.rb +21 -0
- data/lib/amalgalite/csv_table_importer.rb +75 -0
- data/lib/amalgalite/database.rb +933 -0
- data/lib/amalgalite/function.rb +61 -0
- data/lib/amalgalite/index.rb +43 -0
- data/lib/amalgalite/memory_database.rb +15 -0
- data/lib/amalgalite/packer.rb +231 -0
- data/lib/amalgalite/paths.rb +80 -0
- data/lib/amalgalite/profile_tap.rb +131 -0
- data/lib/amalgalite/progress_handler.rb +21 -0
- data/lib/amalgalite/requires.rb +151 -0
- data/lib/amalgalite/schema.rb +225 -0
- data/lib/amalgalite/sqlite3/constants.rb +95 -0
- data/lib/amalgalite/sqlite3/database/function.rb +48 -0
- data/lib/amalgalite/sqlite3/database/status.rb +68 -0
- data/lib/amalgalite/sqlite3/status.rb +60 -0
- data/lib/amalgalite/sqlite3/version.rb +55 -0
- data/lib/amalgalite/sqlite3.rb +6 -0
- data/lib/amalgalite/statement.rb +421 -0
- data/lib/amalgalite/table.rb +91 -0
- data/lib/amalgalite/taps/console.rb +27 -0
- data/lib/amalgalite/taps/io.rb +74 -0
- data/lib/amalgalite/taps.rb +2 -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 +166 -0
- data/lib/amalgalite/type_maps/storage_map.rb +38 -0
- data/lib/amalgalite/type_maps/text_map.rb +21 -0
- data/lib/amalgalite/version.rb +8 -0
- data/lib/amalgalite/view.rb +26 -0
- data/lib/amalgalite.rb +51 -0
- data/spec/aggregate_spec.rb +158 -0
- data/spec/amalgalite_spec.rb +4 -0
- data/spec/blob_spec.rb +78 -0
- data/spec/boolean_spec.rb +24 -0
- data/spec/busy_handler.rb +157 -0
- data/spec/data/iso-3166-country.txt +242 -0
- data/spec/data/iso-3166-schema.sql +22 -0
- data/spec/data/iso-3166-subcountry.txt +3995 -0
- data/spec/data/make-iso-db.sh +12 -0
- data/spec/database_spec.rb +505 -0
- data/spec/default_map_spec.rb +92 -0
- data/spec/function_spec.rb +78 -0
- data/spec/integeration_spec.rb +97 -0
- data/spec/iso_3166_database.rb +58 -0
- data/spec/json_spec.rb +24 -0
- data/spec/packer_spec.rb +60 -0
- data/spec/paths_spec.rb +28 -0
- data/spec/progress_handler_spec.rb +91 -0
- data/spec/requires_spec.rb +54 -0
- data/spec/rtree_spec.rb +66 -0
- data/spec/schema_spec.rb +131 -0
- data/spec/spec_helper.rb +48 -0
- data/spec/sqlite3/constants_spec.rb +108 -0
- data/spec/sqlite3/database_status_spec.rb +36 -0
- data/spec/sqlite3/status_spec.rb +22 -0
- data/spec/sqlite3/version_spec.rb +28 -0
- data/spec/sqlite3_spec.rb +53 -0
- data/spec/statement_spec.rb +168 -0
- data/spec/storage_map_spec.rb +38 -0
- data/spec/tap_spec.rb +57 -0
- data/spec/text_map_spec.rb +20 -0
- data/spec/type_map_spec.rb +14 -0
- data/spec/version_spec.rb +8 -0
- data/tasks/custom.rake +101 -0
- data/tasks/default.rake +244 -0
- data/tasks/extension.rake +28 -0
- data/tasks/this.rb +208 -0
- metadata +325 -0
@@ -0,0 +1,421 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2008 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details.
|
4
|
+
#++
|
5
|
+
#
|
6
|
+
require 'date'
|
7
|
+
require 'arrayfields'
|
8
|
+
require 'ostruct'
|
9
|
+
|
10
|
+
module Amalgalite
|
11
|
+
class Statement
|
12
|
+
|
13
|
+
include ::Amalgalite::SQLite3::Constants
|
14
|
+
|
15
|
+
attr_reader :db
|
16
|
+
attr_reader :api
|
17
|
+
|
18
|
+
class << self
|
19
|
+
# special column names that indicate that indicate the column is a rowid
|
20
|
+
def rowid_column_names
|
21
|
+
@rowid_column_names ||= %w[ ROWID OID _ROWID_ ]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Initialize a new statement on the database.
|
27
|
+
#
|
28
|
+
def initialize( db, sql )
|
29
|
+
@db = db
|
30
|
+
#prepare_method = @db.utf16? ? :prepare16 : :prepare
|
31
|
+
prepare_method = :prepare
|
32
|
+
@param_positions = {}
|
33
|
+
@stmt_api = @db.api.send( prepare_method, sql )
|
34
|
+
@blobs_to_write = []
|
35
|
+
@rowid_index = nil
|
36
|
+
@result_meta = nil
|
37
|
+
@open = true
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# is the statement open for business
|
42
|
+
#
|
43
|
+
def open?
|
44
|
+
@open
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Is the special column "ROWID", "OID", or "_ROWID_" used?
|
49
|
+
#
|
50
|
+
def using_rowid_column?
|
51
|
+
not @rowid_index.nil?
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# reset the Statement back to it state right after the constructor returned,
|
56
|
+
# except if any variables have been bound to parameters, those are still
|
57
|
+
# bound.
|
58
|
+
#
|
59
|
+
def reset!
|
60
|
+
@stmt_api.reset!
|
61
|
+
@param_positions = {}
|
62
|
+
@blobs_to_write.clear
|
63
|
+
@rowid_index = nil
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# reset the Statement back to it state right after the constructor returned,
|
68
|
+
# AND clear all parameter bindings.
|
69
|
+
#
|
70
|
+
def reset_and_clear_bindings!
|
71
|
+
reset!
|
72
|
+
@stmt_api.clear_bindings!
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# reset the statment in preparation for executing it again
|
77
|
+
#
|
78
|
+
def reset_for_next_execute!
|
79
|
+
@stmt_api.reset!
|
80
|
+
@stmt_api.clear_bindings!
|
81
|
+
@blobs_to_write.clear
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# Execute the statement with the given parameters
|
86
|
+
#
|
87
|
+
# If a block is given, then yield each returned row to the block. If no
|
88
|
+
# block is given then return all rows from the result. No matter what the
|
89
|
+
# prepared statement should be reset before returning the final time.
|
90
|
+
#
|
91
|
+
def execute( *params )
|
92
|
+
bind( *params )
|
93
|
+
begin
|
94
|
+
# save the error state at the beginning of the execution. We only want to
|
95
|
+
# reraise the error if it was raised during this execution.
|
96
|
+
s_before = $!
|
97
|
+
if block_given? then
|
98
|
+
while row = next_row
|
99
|
+
yield row
|
100
|
+
end
|
101
|
+
else
|
102
|
+
all_rows
|
103
|
+
end
|
104
|
+
ensure
|
105
|
+
s = $!
|
106
|
+
begin
|
107
|
+
reset_for_next_execute!
|
108
|
+
rescue
|
109
|
+
# rescuing nothing on purpose
|
110
|
+
end
|
111
|
+
raise s if s != s_before
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
##
|
116
|
+
# Bind parameters to the sql statement.
|
117
|
+
#
|
118
|
+
# Bindings in SQLite can have a number of formats:
|
119
|
+
#
|
120
|
+
# ?
|
121
|
+
# ?num
|
122
|
+
# :var
|
123
|
+
# @var
|
124
|
+
# $var
|
125
|
+
#
|
126
|
+
# Where 'num' is an Integer and 'var'is an alphanumerical variable.
|
127
|
+
# They may exist in the SQL for which this Statement was created.
|
128
|
+
#
|
129
|
+
# Amalgalite binds parameters to these variables in the following manner:
|
130
|
+
#
|
131
|
+
# If bind is passed in an Array, either as +bind( "foo", "bar", "baz")+ or
|
132
|
+
# as bind( ["foo", "bar", "baz"] ) then each of the params is assumed to be
|
133
|
+
# positionally bound to the statement( ?, ?num ).
|
134
|
+
#
|
135
|
+
# If bind is passed a Hash, either as +bind( :foo => 1, :bar => 'sqlite' )+
|
136
|
+
# or as bind( { :foo => 1, 'bar' => 'sqlite' }) then it is assumed that each
|
137
|
+
# parameter should be bound as a named parameter (:var, @var, $var).
|
138
|
+
#
|
139
|
+
# If bind is not passed any parameters, or nil, then nothing happens.
|
140
|
+
#
|
141
|
+
def bind( *params )
|
142
|
+
if params.nil? or params.empty? then
|
143
|
+
check_parameter_count!( 0 )
|
144
|
+
return nil
|
145
|
+
end
|
146
|
+
|
147
|
+
if params.first.instance_of?( Hash ) then
|
148
|
+
bind_named_parameters( params.first )
|
149
|
+
elsif params.first.instance_of?( Array ) then
|
150
|
+
bind_positional_parameters( *params )
|
151
|
+
else
|
152
|
+
bind_positional_parameters( params )
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
# Bind parameters to the statement based upon named parameters
|
158
|
+
#
|
159
|
+
def bind_named_parameters( params )
|
160
|
+
check_parameter_count!( params.size )
|
161
|
+
params.each_pair do | param, value |
|
162
|
+
position = param_position_of( param )
|
163
|
+
if position > 0 then
|
164
|
+
bind_parameter_to( position, value )
|
165
|
+
else
|
166
|
+
raise Amalgalite::Error, "Unable to find parameter '#{param}' in SQL statement [#{sql}]"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
##
|
172
|
+
# Bind parameters to the statements based upon positions.
|
173
|
+
#
|
174
|
+
def bind_positional_parameters( params )
|
175
|
+
check_parameter_count!( params.size )
|
176
|
+
params.each_with_index do |value, index|
|
177
|
+
position = index + 1
|
178
|
+
bind_parameter_to( position, value )
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
##
|
183
|
+
# bind a single parameter to a particular position
|
184
|
+
#
|
185
|
+
def bind_parameter_to( position, value )
|
186
|
+
bind_type = db.type_map.bind_type_of( value )
|
187
|
+
case bind_type
|
188
|
+
when DataType::FLOAT
|
189
|
+
@stmt_api.bind_double( position, value )
|
190
|
+
when DataType::INTEGER
|
191
|
+
@stmt_api.bind_int64( position, value )
|
192
|
+
when DataType::NULL
|
193
|
+
@stmt_api.bind_null( position )
|
194
|
+
when DataType::TEXT
|
195
|
+
@stmt_api.bind_text( position, value.to_s )
|
196
|
+
when DataType::BLOB
|
197
|
+
if value.incremental? then
|
198
|
+
@stmt_api.bind_zeroblob( position, value.length )
|
199
|
+
@blobs_to_write << value
|
200
|
+
else
|
201
|
+
@stmt_api.bind_blob( position, value.source )
|
202
|
+
end
|
203
|
+
else
|
204
|
+
raise ::Amalgalite::Error, "Unknown binding type of #{bind_type} from #{db.type_map.class.name}.bind_type_of"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
##
|
210
|
+
# Find and cache the binding parameter indexes
|
211
|
+
#
|
212
|
+
def param_position_of( name )
|
213
|
+
ns = name.to_s
|
214
|
+
unless pos = @param_positions[ns]
|
215
|
+
pos = @param_positions[ns] = @stmt_api.parameter_index( ns )
|
216
|
+
end
|
217
|
+
return pos
|
218
|
+
end
|
219
|
+
|
220
|
+
##
|
221
|
+
# Check and make sure that the number of parameters aligns with the number
|
222
|
+
# that sqlite expects
|
223
|
+
#
|
224
|
+
def check_parameter_count!( num )
|
225
|
+
expected = @stmt_api.parameter_count
|
226
|
+
if num != expected then
|
227
|
+
raise Amalgalite::Error, "#{sql} has #{expected} parameters, but #{num} were passed to bind."
|
228
|
+
end
|
229
|
+
return expected
|
230
|
+
end
|
231
|
+
|
232
|
+
##
|
233
|
+
# Write any blobs that have been bound to parameters to the database. This
|
234
|
+
# assumes that the blobs go into the last inserted row
|
235
|
+
#
|
236
|
+
def write_blobs
|
237
|
+
unless @blobs_to_write.empty?
|
238
|
+
@blobs_to_write.each do |blob|
|
239
|
+
blob.write_to_column!
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
##
|
245
|
+
# Iterate over the results of the statement returning each row of results
|
246
|
+
# as a hash by +column_name+. The column names are the value after an
|
247
|
+
# 'AS' in the query or default chosen by sqlite.
|
248
|
+
#
|
249
|
+
def each
|
250
|
+
while row = next_row
|
251
|
+
yield row
|
252
|
+
end
|
253
|
+
return self
|
254
|
+
end
|
255
|
+
|
256
|
+
##
|
257
|
+
# Return the next row of data, with type conversion as indicated by the
|
258
|
+
# Database#type_map
|
259
|
+
#
|
260
|
+
def next_row
|
261
|
+
row = []
|
262
|
+
case rc = @stmt_api.step
|
263
|
+
when ResultCode::ROW
|
264
|
+
result_meta.each_with_index do |col, idx|
|
265
|
+
value = nil
|
266
|
+
column_type = @stmt_api.column_type( idx )
|
267
|
+
case column_type
|
268
|
+
when DataType::TEXT
|
269
|
+
value = @stmt_api.column_text( idx )
|
270
|
+
when DataType::FLOAT
|
271
|
+
value = @stmt_api.column_double( idx )
|
272
|
+
when DataType::INTEGER
|
273
|
+
value = @stmt_api.column_int64( idx )
|
274
|
+
when DataType::NULL
|
275
|
+
value = nil
|
276
|
+
when DataType::BLOB
|
277
|
+
# if the rowid column is encountered, then we can use an incremental
|
278
|
+
# blob api, otherwise we have to use the all at once version.
|
279
|
+
if using_rowid_column? then
|
280
|
+
value = Amalgalite::Blob.new( :db_blob => SQLite3::Blob.new( db.api,
|
281
|
+
col.schema.db,
|
282
|
+
col.schema.table,
|
283
|
+
col.schema.name,
|
284
|
+
@stmt_api.column_int64( @rowid_index ),
|
285
|
+
"r"),
|
286
|
+
:column => col.schema)
|
287
|
+
else
|
288
|
+
value = Amalgalite::Blob.new( :string => @stmt_api.column_blob( idx ), :column => col.schema )
|
289
|
+
end
|
290
|
+
else
|
291
|
+
raise ::Amalgalite::Error, "BUG! : Unknown SQLite column type of #{column_type}"
|
292
|
+
end
|
293
|
+
|
294
|
+
row << db.type_map.result_value_of( col.schema.declared_data_type, value )
|
295
|
+
end
|
296
|
+
row.fields = result_fields
|
297
|
+
when ResultCode::DONE
|
298
|
+
row = nil
|
299
|
+
write_blobs
|
300
|
+
else
|
301
|
+
self.close # must close so that the error message is guaranteed to be pushed into the database handler
|
302
|
+
# and we can can call last_error_message on it
|
303
|
+
msg = "SQLITE ERROR #{rc} (#{Amalgalite::SQLite3::Constants::ResultCode.name_from_value( rc )}) : #{@db.api.last_error_message}"
|
304
|
+
raise Amalgalite::SQLite3::Error, msg
|
305
|
+
end
|
306
|
+
return row
|
307
|
+
end
|
308
|
+
|
309
|
+
##
|
310
|
+
# Return all rows from the statement as one array
|
311
|
+
#
|
312
|
+
def all_rows
|
313
|
+
rows = []
|
314
|
+
while row = next_row
|
315
|
+
rows << row
|
316
|
+
end
|
317
|
+
return rows
|
318
|
+
end
|
319
|
+
|
320
|
+
##
|
321
|
+
# Inspect the statement and gather all the meta information about the
|
322
|
+
# results, include the name of the column result column and the origin
|
323
|
+
# column. The origin column is the original database.table.column the value
|
324
|
+
# comes from.
|
325
|
+
#
|
326
|
+
# The full meta information from the origin column is also obtained for help
|
327
|
+
# in doing type conversion.
|
328
|
+
#
|
329
|
+
# As iteration over the row meta informatio happens, record if the special
|
330
|
+
# "ROWID", "OID", or "_ROWID_" column is encountered. If that column is
|
331
|
+
# encountered then we make note of it.
|
332
|
+
#
|
333
|
+
def result_meta
|
334
|
+
unless @result_meta
|
335
|
+
meta = []
|
336
|
+
column_count.times do |idx|
|
337
|
+
column_meta = ::OpenStruct.new
|
338
|
+
column_meta.name = @stmt_api.column_name( idx )
|
339
|
+
|
340
|
+
db_name = @stmt_api.column_database_name( idx )
|
341
|
+
tbl_name = @stmt_api.column_table_name( idx )
|
342
|
+
col_name = @stmt_api.column_origin_name( idx )
|
343
|
+
|
344
|
+
column_meta.schema = ::Amalgalite::Column.new( db_name, tbl_name, col_name, idx )
|
345
|
+
column_meta.schema.declared_data_type = @stmt_api.column_declared_type( idx )
|
346
|
+
|
347
|
+
# only check for rowid if we have a table name and it is not one of the
|
348
|
+
# sqlite_master tables. We could get recursion in those cases.
|
349
|
+
if not using_rowid_column? and tbl_name and
|
350
|
+
not %w[ sqlite_master sqlite_temp_master ].include?( tbl_name ) and is_column_rowid?( tbl_name, col_name ) then
|
351
|
+
@rowid_index = idx
|
352
|
+
end
|
353
|
+
|
354
|
+
meta << column_meta
|
355
|
+
end
|
356
|
+
|
357
|
+
@result_meta = meta
|
358
|
+
end
|
359
|
+
return @result_meta
|
360
|
+
end
|
361
|
+
|
362
|
+
##
|
363
|
+
# is the column indicated by the Column a 'rowid' column
|
364
|
+
#
|
365
|
+
def is_column_rowid?( table_name, column_name )
|
366
|
+
table_schema = @db.schema.tables[table_name]
|
367
|
+
return false unless table_schema
|
368
|
+
|
369
|
+
column_schema = table_schema.columns[column_name]
|
370
|
+
if column_schema then
|
371
|
+
if column_schema.primary_key? and column_schema.declared_data_type and column_schema.declared_data_type.upcase == "INTEGER" then
|
372
|
+
return true
|
373
|
+
end
|
374
|
+
else
|
375
|
+
return true if Statement.rowid_column_names.include?( column_name.upcase )
|
376
|
+
end
|
377
|
+
return false
|
378
|
+
end
|
379
|
+
|
380
|
+
##
|
381
|
+
# Return the array of field names for the result set, the field names are
|
382
|
+
# all strings
|
383
|
+
#
|
384
|
+
def result_fields
|
385
|
+
@fields ||= result_meta.collect { |m| m.name }
|
386
|
+
end
|
387
|
+
|
388
|
+
##
|
389
|
+
# Return any unsued SQL from the statement
|
390
|
+
#
|
391
|
+
def remaining_sql
|
392
|
+
@stmt_api.remaining_sql
|
393
|
+
end
|
394
|
+
|
395
|
+
|
396
|
+
##
|
397
|
+
# return the number of columns in the result of this query
|
398
|
+
#
|
399
|
+
def column_count
|
400
|
+
@stmt_api.column_count
|
401
|
+
end
|
402
|
+
|
403
|
+
##
|
404
|
+
# return the raw sql that was originally used to prepare the statement
|
405
|
+
#
|
406
|
+
def sql
|
407
|
+
@stmt_api.sql
|
408
|
+
end
|
409
|
+
|
410
|
+
##
|
411
|
+
# Close the statement. The statement is no longer valid for use after it
|
412
|
+
# has been closed.
|
413
|
+
#
|
414
|
+
def close
|
415
|
+
if open? then
|
416
|
+
@stmt_api.close
|
417
|
+
@open = false
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2008 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details.
|
4
|
+
#++
|
5
|
+
require 'set'
|
6
|
+
module Amalgalite
|
7
|
+
#
|
8
|
+
# a class representing the meta information about an SQLite table
|
9
|
+
#
|
10
|
+
class Table
|
11
|
+
# the schema object the table is associated with
|
12
|
+
attr_accessor :schema
|
13
|
+
|
14
|
+
# the table name
|
15
|
+
attr_reader :name
|
16
|
+
|
17
|
+
# the original sql that was used to create this table
|
18
|
+
attr_reader :sql
|
19
|
+
|
20
|
+
# hash of Index objects holding the meta informationa about the indexes
|
21
|
+
# on this table. The keys of the indexes variable is the index name
|
22
|
+
attr_accessor :indexes
|
23
|
+
|
24
|
+
# a hash of Column objects holding the meta information about the columns
|
25
|
+
# in this table. keys are the column names
|
26
|
+
attr_accessor :columns
|
27
|
+
|
28
|
+
def initialize( name, sql = nil )
|
29
|
+
@name = name
|
30
|
+
@sql = sql
|
31
|
+
@indexes = {}
|
32
|
+
@columns = {}
|
33
|
+
@schema = nil
|
34
|
+
@primary_key = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
# Is the table a temporary table or not
|
38
|
+
def temporary?
|
39
|
+
schema.temporary?
|
40
|
+
end
|
41
|
+
|
42
|
+
# the Columns in original definition order
|
43
|
+
def columns_in_order
|
44
|
+
@columns.values.sort_by { |c| c.order }
|
45
|
+
end
|
46
|
+
|
47
|
+
# the column names in original definition order
|
48
|
+
def column_names
|
49
|
+
columns_in_order.map { |c| c.name }
|
50
|
+
end
|
51
|
+
|
52
|
+
# the columns that make up the primary key
|
53
|
+
def primary_key_columns
|
54
|
+
@columns.values.find_all { |c| c.primary_key? }
|
55
|
+
end
|
56
|
+
|
57
|
+
# the array of colmuns that make up the primary key of the table
|
58
|
+
# since a primary key has an index, we loop over all the indexes for the
|
59
|
+
# table and pick the first one that is unique, and all the columns in the
|
60
|
+
# index have primary_key? as true.
|
61
|
+
#
|
62
|
+
# we do this instead of just looking for the columns where primary key is
|
63
|
+
# true because we want the columns in primary key order
|
64
|
+
def primary_key
|
65
|
+
unless @primary_key
|
66
|
+
pk_column_names = Set.new( primary_key_columns.collect { |c| c.name } )
|
67
|
+
unique_indexes = indexes.values.find_all { |i| i.unique? }
|
68
|
+
|
69
|
+
pk_result = []
|
70
|
+
|
71
|
+
unique_indexes.each do |idx|
|
72
|
+
idx_column_names = Set.new( idx.columns.collect { |c| c.name } )
|
73
|
+
r = idx_column_names ^ pk_column_names
|
74
|
+
if r.size == 0 then
|
75
|
+
pk_result = idx.columns
|
76
|
+
break
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# no joy, see about just using all the columns that say the are primary
|
81
|
+
# keys
|
82
|
+
if pk_result.empty? then
|
83
|
+
pk_result = self.primary_key_columns
|
84
|
+
end
|
85
|
+
@primary_key = pk_result
|
86
|
+
end
|
87
|
+
return @primary_key
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2008 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details.
|
4
|
+
#++
|
5
|
+
|
6
|
+
require 'amalgalite/taps/io'
|
7
|
+
|
8
|
+
module Amalgalite::Taps
|
9
|
+
#
|
10
|
+
# Class provide an IO tap that can write to $stdout
|
11
|
+
#
|
12
|
+
class Stdout < ::Amalgalite::Taps::IO
|
13
|
+
def initialize
|
14
|
+
super( $stdout )
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# This class provide an IO tap that can write to $stderr
|
20
|
+
#
|
21
|
+
class Stderr < ::Amalgalite::Taps::IO
|
22
|
+
def initialize
|
23
|
+
super( $stderr )
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2008 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details.
|
4
|
+
#++
|
5
|
+
|
6
|
+
require 'amalgalite/profile_tap'
|
7
|
+
require 'stringio'
|
8
|
+
|
9
|
+
module Amalgalite
|
10
|
+
module Taps
|
11
|
+
#
|
12
|
+
# An IOTap is an easy way to send all top information to any IO based
|
13
|
+
# object. Both profile and trace tap information can be captured
|
14
|
+
# This means you can send the events to STDOUT with:
|
15
|
+
#
|
16
|
+
# db.profile_tap = db.trace_tap = Amalgalite::Taps::Stdout.new
|
17
|
+
#
|
18
|
+
#
|
19
|
+
class IO
|
20
|
+
|
21
|
+
attr_reader :profile_tap
|
22
|
+
attr_reader :io
|
23
|
+
attr_reader :trace_count
|
24
|
+
|
25
|
+
def initialize( io )
|
26
|
+
@io = io
|
27
|
+
@profile_tap = ProfileTap.new( self, 'output_profile_event' )
|
28
|
+
@trace_count = 0
|
29
|
+
end
|
30
|
+
|
31
|
+
def trace( msg )
|
32
|
+
@trace_count += 1
|
33
|
+
io.puts msg
|
34
|
+
end
|
35
|
+
|
36
|
+
# need a profile method, it routes through the profile tap which calls back
|
37
|
+
# to output_profile_event
|
38
|
+
def profile( msg, time )
|
39
|
+
@profile_tap.profile(msg, time)
|
40
|
+
end
|
41
|
+
|
42
|
+
def output_profile_event( msg, time )
|
43
|
+
io.puts "#{time} : #{msg}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def dump_profile
|
47
|
+
samplers.each_pair do |k,v|
|
48
|
+
io.puts v.to_s
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def samplers
|
53
|
+
profile_tap.samplers
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# This class provides an IO tap that writes to a StringIO. The result is
|
59
|
+
# available via .to_s or .string.
|
60
|
+
#
|
61
|
+
class StringIO < ::Amalgalite::Taps::IO
|
62
|
+
def initialize
|
63
|
+
@stringio = ::StringIO.new
|
64
|
+
super( @stringio )
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_s
|
68
|
+
@stringio.string
|
69
|
+
end
|
70
|
+
alias :string :to_s
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2008 Jeremy Hinegardner
|
3
|
+
# All rights reserved. See LICENSE and/or COPYING for details.
|
4
|
+
#++
|
5
|
+
|
6
|
+
module Amalgalite
|
7
|
+
#
|
8
|
+
# A TraceTap receives tracing information from SQLite3. It receives the SQL
|
9
|
+
# statement being executed as a +msg+ just before the statement first begins
|
10
|
+
# executing.
|
11
|
+
#
|
12
|
+
# A TraceTap is a wrapper around another object and a method. The Tap object
|
13
|
+
# will receive the call to +trace+ and redirect that call to another object
|
14
|
+
# and method.
|
15
|
+
#
|
16
|
+
class TraceTap
|
17
|
+
|
18
|
+
attr_reader :delegate_obj
|
19
|
+
attr_reader :delegate_method
|
20
|
+
|
21
|
+
def initialize( wrapped_obj, send_to = 'trace' )
|
22
|
+
unless wrapped_obj.respond_to?( send_to )
|
23
|
+
raise Amalgalite::Error, "#{wrapped_obj.class.name} does not respond to #{send_to.to_s} "
|
24
|
+
end
|
25
|
+
|
26
|
+
@delegate_obj = wrapped_obj
|
27
|
+
@delegate_method = send_to
|
28
|
+
end
|
29
|
+
|
30
|
+
def trace( msg )
|
31
|
+
delegate_obj.send( delegate_method, msg )
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|