amalgalite 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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