amalgalite 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/HISTORY +4 -0
  2. data/LICENSE +31 -0
  3. data/README +28 -0
  4. data/ext/amalgalite3.c +191 -0
  5. data/ext/amalgalite3.h +97 -0
  6. data/ext/amalgalite3_constants.c +179 -0
  7. data/ext/amalgalite3_database.c +458 -0
  8. data/ext/amalgalite3_statement.c +546 -0
  9. data/ext/gen_constants.rb +114 -0
  10. data/ext/mkrf_conf.rb +6 -0
  11. data/ext/sqlite3.c +87003 -0
  12. data/ext/sqlite3.h +5638 -0
  13. data/ext/sqlite3_options.h +4 -0
  14. data/ext/sqlite3ext.h +362 -0
  15. data/gemspec.rb +50 -0
  16. data/lib/amalgalite.rb +28 -0
  17. data/lib/amalgalite/blob.rb +14 -0
  18. data/lib/amalgalite/boolean.rb +42 -0
  19. data/lib/amalgalite/column.rb +83 -0
  20. data/lib/amalgalite/database.rb +505 -0
  21. data/lib/amalgalite/index.rb +27 -0
  22. data/lib/amalgalite/paths.rb +70 -0
  23. data/lib/amalgalite/profile_tap.rb +130 -0
  24. data/lib/amalgalite/schema.rb +90 -0
  25. data/lib/amalgalite/sqlite3.rb +4 -0
  26. data/lib/amalgalite/sqlite3/constants.rb +48 -0
  27. data/lib/amalgalite/sqlite3/version.rb +38 -0
  28. data/lib/amalgalite/statement.rb +307 -0
  29. data/lib/amalgalite/table.rb +34 -0
  30. data/lib/amalgalite/taps/console.rb +27 -0
  31. data/lib/amalgalite/taps/io.rb +71 -0
  32. data/lib/amalgalite/trace_tap.rb +35 -0
  33. data/lib/amalgalite/type_map.rb +60 -0
  34. data/lib/amalgalite/type_maps/default_map.rb +153 -0
  35. data/lib/amalgalite/type_maps/storage_map.rb +41 -0
  36. data/lib/amalgalite/type_maps/text_map.rb +23 -0
  37. data/lib/amalgalite/version.rb +32 -0
  38. data/lib/amalgalite/view.rb +24 -0
  39. data/spec/amalgalite_spec.rb +4 -0
  40. data/spec/boolean_spec.rb +26 -0
  41. data/spec/database_spec.rb +222 -0
  42. data/spec/default_map_spec.rb +85 -0
  43. data/spec/integeration_spec.rb +111 -0
  44. data/spec/paths_spec.rb +28 -0
  45. data/spec/schema_spec.rb +46 -0
  46. data/spec/spec_helper.rb +25 -0
  47. data/spec/sqlite3/constants_spec.rb +25 -0
  48. data/spec/sqlite3/version_spec.rb +14 -0
  49. data/spec/sqlite3_spec.rb +34 -0
  50. data/spec/statement_spec.rb +116 -0
  51. data/spec/storage_map_spec.rb +41 -0
  52. data/spec/tap_spec.rb +59 -0
  53. data/spec/text_map_spec.rb +23 -0
  54. data/spec/type_map_spec.rb +17 -0
  55. data/spec/version_spec.rb +9 -0
  56. data/tasks/announce.rake +38 -0
  57. data/tasks/config.rb +108 -0
  58. data/tasks/distribution.rake +38 -0
  59. data/tasks/documentation.rake +31 -0
  60. data/tasks/extension.rake +45 -0
  61. data/tasks/rspec.rake +32 -0
  62. data/tasks/rubyforge.rake +48 -0
  63. data/tasks/utils.rb +80 -0
  64. metadata +165 -0
@@ -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
+ module Amalgalite
7
+ #
8
+ # a class representing the meta information about an SQLite index
9
+ #
10
+ class Index
11
+ # the name of the index
12
+ attr_reader :name
13
+
14
+ # the sql statement that created the index
15
+ attr_reader :sql
16
+
17
+ # the table the index is for
18
+ attr_accessor :table
19
+
20
+ def initialize( name, sql, table )
21
+ @name = name
22
+ @sql = sql
23
+ @table = table
24
+ end
25
+ end
26
+ end
27
+
@@ -0,0 +1,70 @@
1
+ #--
2
+ # Copyright (c) 2008 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+ module Amalgalite
6
+ #
7
+ # Paths contains helpful methods to determine paths of files inside the
8
+ # Amalgalite library
9
+ #
10
+ module Paths
11
+ #
12
+ # The root directory of the project is considered to be the parent directory
13
+ # of the 'lib' directory.
14
+ #
15
+ # returns:: [String] The full expanded path of the parent directory of 'lib'
16
+ # going up the path from the current file. Trailing
17
+ # File::SEPARATOR is guaranteed.
18
+ #
19
+ def self.root_dir
20
+ unless @root_dir
21
+ path_parts = ::File.expand_path(__FILE__).split(::File::SEPARATOR)
22
+ lib_index = path_parts.rindex("lib")
23
+ @root_dir = path_parts[0...lib_index].join(::File::SEPARATOR) + ::File::SEPARATOR
24
+ end
25
+ return @root_dir
26
+ end
27
+
28
+ # returns:: [String] The full expanded path of the +config+ directory
29
+ # below _root_dir_. All parameters passed in are joined onto the
30
+ # result. Trailing File::SEPARATOR is guaranteed if _args_ are
31
+ # *not* present.
32
+ #
33
+ def self.config_path(*args)
34
+ self.sub_path("config", *args)
35
+ end
36
+
37
+ # returns:: [String] The full expanded path of the +data+ directory below
38
+ # _root_dir_. All parameters passed in are joined onto the
39
+ # result. Trailing File::SEPARATOR is guaranteed if
40
+ # _*args_ are *not* present.
41
+ #
42
+ def self.data_path(*args)
43
+ self.sub_path("data", *args)
44
+ end
45
+
46
+ # returns:: [String] The full expanded path of the +lib+ directory below
47
+ # _root_dir_. All parameters passed in are joined onto the
48
+ # result. Trailing File::SEPARATOR is guaranteed if
49
+ # _*args_ are *not* present.
50
+ #
51
+ def self.lib_path(*args)
52
+ self.sub_path("lib", *args)
53
+ end
54
+
55
+ # returns:: [String] The full expanded path of the +ext+ directory below
56
+ # _root_dir_. All parameters passed in are joined onto the
57
+ # result. Trailing File::SEPARATOR is guaranteed if
58
+ # _*args_ are *not* present.
59
+ #
60
+ def self.ext_path(*args)
61
+ self.sub_path("ext", *args)
62
+ end
63
+
64
+ def self.sub_path(sub,*args)
65
+ sp = ::File.join(root_dir, sub) + File::SEPARATOR
66
+ sp = ::File.join(sp, *args) if args
67
+ end
68
+ end
69
+ end
70
+
@@ -0,0 +1,130 @@
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 ProfileSampler is a sampler of profile times. It aggregates up profile
9
+ # events that happen for the same source. It is based upon the RFuzz::Sampler
10
+ # class from the rfuzz gem
11
+ #
12
+ class ProfileSampler
13
+ #
14
+ # create a new sampler with the given name
15
+ #
16
+ def initialize( name )
17
+ @name = name
18
+ reset!
19
+ end
20
+
21
+ ##
22
+ # reset the internal state so it may be used again
23
+ #
24
+ def reset!
25
+ @sum = 0.0
26
+ @sumsq = 0.0
27
+ @n = 0
28
+ @min = 0.0
29
+ @max = 0.0
30
+ end
31
+
32
+ ##
33
+ # add a sample to the calculations
34
+ #
35
+ def sample( value )
36
+ @sum += value
37
+ @sumsq += (value * value)
38
+ if @n == 0 then
39
+ @min = @max = value
40
+ else
41
+ @min = value if value < @min
42
+ @max = value if value > @max
43
+ end
44
+ @n += 1
45
+ end
46
+
47
+ ##
48
+ # return the mean of the data
49
+ #
50
+ def mean
51
+ @sum / @n
52
+ end
53
+
54
+ ##
55
+ # returns the standard deviation of the data
56
+ #
57
+ def stddev
58
+ begin
59
+ Math.sqrt( (@sumsq - ( @sum * @sum / @n)) / (@n-1) )
60
+ rescue Errno::EDOM
61
+ return 0.0
62
+ end
63
+ end
64
+
65
+ ##
66
+ # return all the values as an array
67
+ #
68
+ def to_a
69
+ [ @name, @sum, @sumsq, @n, mean, stddev, @min, @max ]
70
+ end
71
+
72
+ ##
73
+ # return all the values as a hash
74
+ #
75
+ def to_h
76
+ { 'name' => @name, 'n' => @n,
77
+ 'sum' => @sum, 'sumsq' => @sumsq, 'mean' => mean,
78
+ 'stddev' => stddev, 'min' => @min, 'max' => @max }
79
+ end
80
+
81
+ ##
82
+ # return a string containing the sampler summary
83
+ #
84
+ def to_s
85
+ "[%s] => sum: %d, sumsq: %d, n: %d, mean: %0.6f, stddev: %0.6f, min: %d, max: %d" % to_a
86
+ end
87
+
88
+ end
89
+
90
+ #
91
+ # A Profile Tap recives +profile+ events from SQLite which involve the number of
92
+ # nanoseconds in wall-clock time it took for a particular thing to happen. In
93
+ # general this +thing+ is an SQL statement.
94
+ #
95
+ # It has a well known +profile+ method which when invoked will write the event
96
+ # to a delegate object.
97
+ #
98
+ #
99
+ class ProfileTap
100
+
101
+ attr_reader :samplers
102
+
103
+ #
104
+ # Create a new ProfileTap object that wraps the given object and calls the
105
+ # method named in +send_to+ ever time a profile event happens.
106
+ #
107
+ def initialize( wrapped_obj, send_to = 'profile' )
108
+ unless wrapped_obj.respond_to?( send_to )
109
+ raise Amalgalite::Error, "#{wrapped_obj.class.name} does not respond to #{send_to.to_s} "
110
+ end
111
+
112
+ @delegate_obj = wrapped_obj
113
+ @delegate_method = send_to
114
+ @samplers = {}
115
+ end
116
+
117
+ #
118
+ # Record the profile information and send the delegate object the msg and
119
+ # time information.
120
+ #
121
+ def profile( msg, time )
122
+ unless sampler = @samplers[msg]
123
+ msg = msg.gsub(/\s+/,' ')
124
+ sampler = @samplers[msg] = ProfileSampler.new( msg )
125
+ end
126
+ sampler.sample( time )
127
+ @delegate_obj.send( @delegate_method, msg, time )
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,90 @@
1
+ #--
2
+ # Copyright (c) 2008 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+
6
+ require 'amalgalite/table'
7
+ require 'amalgalite/index'
8
+ require 'amalgalite/column'
9
+ require 'amalgalite/view'
10
+
11
+ module Amalgalite
12
+ #
13
+ # An object view of the schema in the SQLite database. If the schema changes
14
+ # after this class is created, it has no knowledge of that.
15
+ #
16
+ class Schema
17
+
18
+ attr_reader :catalog
19
+ attr_reader :schema
20
+ attr_reader :tables
21
+ attr_reader :views
22
+
23
+ #
24
+ # Create a new instance of Schema
25
+ #
26
+ def initialize( db, catalog = 'main', schema = 'sqlite')
27
+ @db = db
28
+ @catalog = catalog
29
+ @schema = schema
30
+
31
+ load_schema!
32
+ end
33
+
34
+ #
35
+ # load the schema from the database
36
+ def load_schema!
37
+ load_tables
38
+ load_views
39
+ end
40
+
41
+ ##
42
+ # load all the tables
43
+ #
44
+ def load_tables
45
+ @tables = {}
46
+ @db.execute("SELECT tbl_name, sql FROM sqlite_master WHERE type = 'table'") do |table_info|
47
+ table = Amalgalite::Table.new( table_info['tbl_name'], table_info['sql'] )
48
+ table.columns = load_columns( table )
49
+
50
+ @db.prepare("SELECT name, sql FROM sqlite_master WHERE type ='index' and tbl_name = @name") do |idx_stmt|
51
+ idx_stmt.execute( "@name" => table.name) do |idx_info|
52
+ table.indexes << Amalgalite::Index.new( idx_info['name'], idx_info['sql'], table )
53
+ end
54
+ end
55
+ @tables[table.name] = table
56
+ end
57
+
58
+ @tables
59
+ end
60
+
61
+ ##
62
+ # load all the columns for a particular table
63
+ #
64
+ def load_columns( table )
65
+ cols = {}
66
+ @db.execute("PRAGMA table_info(#{table.name})") do |row|
67
+ col = Amalgalite::Column.new( "main", row['name'], table )
68
+
69
+ col.default_value = row['dflt_value']
70
+ @db.api.table_column_metadata( "main", table.name, col.name ).each_pair do |key, value|
71
+ col.send("#{key}=", value)
72
+ end
73
+ cols[col.name] = col
74
+ end
75
+ cols
76
+ end
77
+
78
+ ##
79
+ # load all the views for the database
80
+ #
81
+ def load_views
82
+ @views = {}
83
+ @db.execute("SELECT name, sql FROM sqlite_master WHERE type = 'view'") do |view_info|
84
+ view = Amalgalite::View.new( view_info['name'], view_info['sql'] )
85
+ @views[view.name] = view
86
+ end
87
+ @views
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,4 @@
1
+ require 'amalgalite3'
2
+ require 'amalgalite/version'
3
+ require 'amalgalite/sqlite3/version'
4
+ require 'amalgalite/sqlite3/constants'
@@ -0,0 +1,48 @@
1
+ #--
2
+ # Copyright (c) 2008 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+
6
+ require 'amalgalite3'
7
+ module Amalgalite::SQLite3
8
+ ##
9
+ # module containing all constants used from the SQLite C extension
10
+ #
11
+ module Constants
12
+ ##
13
+ # DataType defines the namespace for all possible SQLite data types.
14
+ #
15
+ module DataType
16
+ end
17
+ DataType.freeze
18
+
19
+ ##
20
+ # Open defines the namespace for all possible flags to the Database.open
21
+ # method
22
+ #
23
+ module Open
24
+ end
25
+ Open.freeze
26
+
27
+ ##
28
+ # ResultCode defines the namespace for all possible result codes from an
29
+ # SQLite API call.
30
+ #
31
+ module ResultCode
32
+ #
33
+ # convert an integer value into the string representation of the associated
34
+ # ResultCode constant.
35
+ #
36
+ def self.from_int( value )
37
+ unless @const_map_from_int
38
+ @const_map_from_int = {}
39
+ constants.each do |const_name|
40
+ c_int = const_get( const_name )
41
+ @const_map_from_int[c_int] = const_name
42
+ end
43
+ end
44
+ return @const_map_from_int[ value ]
45
+ end
46
+ end # end ResultCode
47
+ end
48
+ end
@@ -0,0 +1,38 @@
1
+ #--
2
+ # Copyright (c) 2008 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+ require 'amalgalite3'
6
+ module Amalgalite
7
+ module SQLite3
8
+ module Version
9
+ # Sqlite3 version number is equal to
10
+ # MAJOR * 1_000_000 + MINOR * 1_000 + RELEASE
11
+
12
+ # major version number of the SQLite C library
13
+ MAJOR = (to_i / 1_000_000).freeze
14
+
15
+ # minor version number of the SQLite C library
16
+ MINOR = ((to_i % 1_000_000) / 1_000).freeze
17
+
18
+ # release version number of the SQLite C library
19
+ RELEASE = (to_i % 1_000).freeze
20
+
21
+ #
22
+ # call-seq:
23
+ # Amalgalite::SQLite3::Version.to_a -> [ MAJOR, MINOR, RELEASE ]
24
+ #
25
+ # Return the SQLite C library version number as an array of MAJOR, MINOR,
26
+ # RELEASE
27
+ #
28
+ def self.to_a
29
+ [ MAJOR, MINOR, RELEASE ]
30
+ end
31
+
32
+ end
33
+
34
+ # Version of SQLite that ships with Amalgalite
35
+ VERSION = Version.to_s.freeze
36
+ end
37
+ Version.freeze
38
+ end
@@ -0,0 +1,307 @@
1
+ #--
2
+ # Copyright (c) 2008 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+ #
6
+ require 'amalgalite3'
7
+ require 'date'
8
+ require 'arrayfields'
9
+ require 'ostruct'
10
+
11
+ module Amalgalite
12
+ class Statement
13
+
14
+ include ::Amalgalite::SQLite3::Constants
15
+
16
+ attr_reader :db
17
+ attr_reader :sql
18
+ attr_reader :api
19
+
20
+ ##
21
+ # Initialize a new statement on the database.
22
+ #
23
+ def initialize( db, sql )
24
+ @db = db
25
+ prepare_method = @db.utf16? ? :prepare16 : :prepare
26
+ @param_positions = {}
27
+ @stmt_api = @db.api.send( prepare_method, sql )
28
+ end
29
+
30
+ ##
31
+ # reset the Statement back to it state right after the constructor returned,
32
+ # except if any variables have been bound to parameters, those are still
33
+ # bound.
34
+ #
35
+ def reset!
36
+ @stmt_api.reset!
37
+ @column_names = nil
38
+ @param_positions = {}
39
+ end
40
+
41
+ ##
42
+ # reset the Statement back to it state right after the constructor returned,
43
+ # AND clear all parameter bindings.
44
+ #
45
+ def reset_and_clear_bindings!
46
+ reset!
47
+ @stmt_api.clear_bindings!
48
+ end
49
+
50
+ ##
51
+ # Execute the statement with the given parameters
52
+ #
53
+ # If a block is given, then yield each returned row to the block. If no
54
+ # block is given then return all rows from the result
55
+ #
56
+ def execute( *params )
57
+ bind( *params )
58
+ if block_given? then
59
+ while row = next_row
60
+ yield row
61
+ end
62
+ else
63
+ all_rows
64
+ end
65
+ end
66
+
67
+ ##
68
+ # Bind parameters to the sql statement.
69
+ #
70
+ # Bindings in SQLite can have a number of formats:
71
+ #
72
+ # ?
73
+ # ?num
74
+ # :var
75
+ # @var
76
+ # $var
77
+ #
78
+ # Where 'num' is an Integer and 'var'is an alphanumerical variable.
79
+ # They may exist in the SQL for which this Statement was created.
80
+ #
81
+ # Amalgalite binds parameters to these variables in the following manner:
82
+ #
83
+ # If bind is passed in an Array, either as +bind( "foo", "bar", "baz")+ or
84
+ # as bind( ["foo", "bar", "baz"] ) then each of the params is assumed to be
85
+ # positionally bound to the statement( ?, ?num ).
86
+ #
87
+ # If bind is passed a Hash, either as +bind( :foo => 1, :bar => 'sqlite' )+
88
+ # or as bind( { :foo => 1, 'bar' => 'sqlite' }) then it is assumed that each
89
+ # parameter should be bound as a named parameter (:var, @var, $var).
90
+ #
91
+ # If bind is not passed any parameters, or nil, then nothing happens.
92
+ #
93
+ def bind( *params )
94
+ if params.nil? or params.empty? then
95
+ check_parameter_count!( 0 )
96
+ return nil
97
+ end
98
+
99
+ if params.first.instance_of?( Hash ) then
100
+ bind_named_parameters( params.first )
101
+ else
102
+ bind_positional_parameters( params )
103
+ end
104
+ end
105
+
106
+ ##
107
+ # Bind parameters to the statement based upon named parameters
108
+ #
109
+ def bind_named_parameters( params )
110
+ check_parameter_count!( params.size )
111
+ params.each_pair do | param, value |
112
+ position = param_position_of( param )
113
+ if position > 0 then
114
+ bind_parameter_to( position, value )
115
+ else
116
+ raise Amalgalite::Error, "Unable to find parameter '#{param}' in SQL statement [#{sql}]"
117
+ end
118
+ end
119
+ end
120
+
121
+ ##
122
+ # Bind parameters to the statements based upon positions.
123
+ #
124
+ def bind_positional_parameters( params )
125
+ check_parameter_count!( params.size )
126
+ params.each_with_index do |value, index|
127
+ position = index + 1
128
+ bind_parameter_to( position, value )
129
+ end
130
+ end
131
+
132
+ ##
133
+ # bind a single parameter to a particular position
134
+ #
135
+ def bind_parameter_to( position, value )
136
+ bind_type = db.type_map.bind_type_of( value )
137
+ case bind_type
138
+ when DataType::FLOAT
139
+ @stmt_api.bind_double( position, value )
140
+ when DataType::INTEGER
141
+ @stmt_api.bind_int64( position, value )
142
+ when DataType::NULL
143
+ @stmt_api.bind_null( position )
144
+ when DataType::TEXT
145
+ @stmt_api.bind_text( position, value.to_s )
146
+ when DataType::BLOB
147
+ raise NotImplemented, "Blob binding is not implemented yet"
148
+ else
149
+ raise ::Amalgalite::Error, "Unknown binding type of #{bind_type} from #{db.type_map.class.name}.bind_type_of"
150
+ end
151
+ end
152
+
153
+
154
+ ##
155
+ # Find and cache the binding parameter indexes
156
+ #
157
+ def param_position_of( name )
158
+ ns = name.to_s
159
+ unless pos = @param_positions[ns]
160
+ pos = @param_positions[ns] = @stmt_api.parameter_index( ns )
161
+ end
162
+ return pos
163
+ end
164
+
165
+ ##
166
+ # Check and make sure that the number of parameters aligns with the number
167
+ # that sqlite expects
168
+ #
169
+ def check_parameter_count!( num )
170
+ expected = @stmt_api.parameter_count
171
+ if num != expected then
172
+ raise Amalgalite::Error, "#{sql} has #{expected} parameters, but #{num} were passed to bind."
173
+ end
174
+ return expected
175
+ end
176
+
177
+
178
+ ##
179
+ # Iterate over the results of the statement returning each row of results
180
+ # as a hash by +column_name+. The column names are the value after an
181
+ # 'AS' in the query or default chosen by sqlite.
182
+ #
183
+ def each
184
+ while row = next_row
185
+ yield row
186
+ end
187
+ return self
188
+ end
189
+
190
+ ##
191
+ # Return the next row of data, with type conversion as indicated by the
192
+ # Database#type_map
193
+ #
194
+ def next_row
195
+ row = []
196
+ case rc = @stmt_api.step
197
+ when ResultCode::ROW
198
+ result_meta.each_with_index do |col, idx|
199
+ value = nil
200
+ column_type = @stmt_api.column_type( idx )
201
+ case column_type
202
+ when DataType::TEXT
203
+ value = @stmt_api.column_text( idx )
204
+ when DataType::FLOAT
205
+ value = @stmt_api.column_double( idx )
206
+ when DataType::INTEGER
207
+ value = @stmt_api.column_int64( idx )
208
+ when DataType::NULL
209
+ value = nil
210
+ when DataType::BLOB
211
+ raise NotImplemented, "returning a blob is not supported yet"
212
+ else
213
+ raise ::Amalgalite::Error, "BUG! : Unknown SQLite column type of #{column_type}"
214
+ end
215
+
216
+ row << db.type_map.result_value_of( col.schema.declared_data_type, value )
217
+ end
218
+ row.fields = result_fields
219
+ when ResultCode::DONE
220
+ row = nil
221
+ else
222
+ raise Amalgalite::SQLite3::Error,
223
+ "Received unexepcted result code #{rc} : #{Amalgalite::SQLite3::Constants::ResultCode.from_int( rc )}"
224
+ end
225
+ return row
226
+ end
227
+
228
+ ##
229
+ # Return all rows from the statement as one array
230
+ #
231
+ def all_rows
232
+ rows = []
233
+ while row = next_row
234
+ rows << row
235
+ end
236
+ return rows
237
+ end
238
+
239
+ ##
240
+ # Inspect the statement and gather all the meta information about the
241
+ # results, include the name of the column result column and the origin
242
+ # column. The origin column is the original database.table.column the value
243
+ # comes from.
244
+ #
245
+ # The full meta information from teh origin column is also obtained for help
246
+ # in doing type conversion.
247
+ #
248
+ def result_meta
249
+ unless @result_meta
250
+ meta = []
251
+ column_count.times do |idx|
252
+ column_meta = ::OpenStruct.new
253
+ column_meta.name = @stmt_api.column_name( idx )
254
+
255
+ db_name = @stmt_api.column_database_name( idx )
256
+ tbl_name = @stmt_api.column_table_name( idx )
257
+ col_name = @stmt_api.column_origin_name( idx )
258
+
259
+ column_meta.schema = ::Amalgalite::Column.new( db_name, tbl_name, col_name )
260
+ column_meta.schema.declared_data_type = @stmt_api.column_declared_type( idx )
261
+
262
+ meta << column_meta
263
+ end
264
+ @result_meta = meta
265
+ end
266
+ return @result_meta
267
+ end
268
+
269
+ ##
270
+ # Return the array of field names for the result set, the field names are
271
+ # all strings
272
+ #
273
+ def result_fields
274
+ @fields ||= result_meta.collect { |m| m.name }
275
+ end
276
+
277
+ ##
278
+ # Return any unsued SQL from the statement
279
+ #
280
+ def remaining_sql
281
+ @stmt_api.remaining_sql
282
+ end
283
+
284
+
285
+ ##
286
+ # return the number of columns in the result of this query
287
+ #
288
+ def column_count
289
+ @stmt_api.column_count
290
+ end
291
+
292
+ ##
293
+ # return the raw sql that was originally used to prepare the statement
294
+ #
295
+ def sql
296
+ @stmt_api.sql
297
+ end
298
+
299
+ ##
300
+ # Close the statement. The statement is no longer valid for use after it
301
+ # has been closed.
302
+ #
303
+ def close
304
+ @stmt_api.close
305
+ end
306
+ end
307
+ end