libsql 0.1.0-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTING.md +60 -0
  3. data/HISTORY.md +6 -0
  4. data/LICENSE +31 -0
  5. data/Manifest.txt +96 -0
  6. data/README.md +59 -0
  7. data/Rakefile +28 -0
  8. data/TODO.md +57 -0
  9. data/examples/a.rb +9 -0
  10. data/examples/blob.rb +106 -0
  11. data/examples/define_aggregate.rb +75 -0
  12. data/examples/define_function.rb +104 -0
  13. data/examples/fts5.rb +152 -0
  14. data/examples/gem-db.rb +94 -0
  15. data/examples/schema-info.rb +34 -0
  16. data/ext/libsql/c/extconf.rb +86 -0
  17. data/ext/libsql/c/gen_constants.rb +353 -0
  18. data/ext/libsql/c/libsql_blob.c +240 -0
  19. data/ext/libsql/c/libsql_constants.c +1518 -0
  20. data/ext/libsql/c/libsql_database.c +1188 -0
  21. data/ext/libsql/c/libsql_ext.c +383 -0
  22. data/ext/libsql/c/libsql_ext.h +149 -0
  23. data/ext/libsql/c/libsql_statement.c +649 -0
  24. data/ext/libsql/c/notes.txt +134 -0
  25. data/ext/libsql/c/sqlite3.c +247030 -0
  26. data/ext/libsql/c/sqlite3.h +13436 -0
  27. data/lib/libsql/aggregate.rb +73 -0
  28. data/lib/libsql/blob.rb +186 -0
  29. data/lib/libsql/boolean.rb +42 -0
  30. data/lib/libsql/busy_timeout.rb +47 -0
  31. data/lib/libsql/column.rb +99 -0
  32. data/lib/libsql/csv_table_importer.rb +75 -0
  33. data/lib/libsql/database.rb +933 -0
  34. data/lib/libsql/function.rb +61 -0
  35. data/lib/libsql/index.rb +43 -0
  36. data/lib/libsql/libsql_ext.so +0 -0
  37. data/lib/libsql/memory_database.rb +15 -0
  38. data/lib/libsql/paths.rb +80 -0
  39. data/lib/libsql/profile_tap.rb +131 -0
  40. data/lib/libsql/progress_handler.rb +21 -0
  41. data/lib/libsql/schema.rb +225 -0
  42. data/lib/libsql/sqlite3/constants.rb +95 -0
  43. data/lib/libsql/sqlite3/database/function.rb +48 -0
  44. data/lib/libsql/sqlite3/database/status.rb +68 -0
  45. data/lib/libsql/sqlite3/libsql_version.rb +32 -0
  46. data/lib/libsql/sqlite3/status.rb +60 -0
  47. data/lib/libsql/sqlite3/version.rb +55 -0
  48. data/lib/libsql/sqlite3.rb +7 -0
  49. data/lib/libsql/statement.rb +421 -0
  50. data/lib/libsql/table.rb +91 -0
  51. data/lib/libsql/taps/console.rb +27 -0
  52. data/lib/libsql/taps/io.rb +74 -0
  53. data/lib/libsql/taps.rb +2 -0
  54. data/lib/libsql/trace_tap.rb +35 -0
  55. data/lib/libsql/type_map.rb +63 -0
  56. data/lib/libsql/type_maps/default_map.rb +166 -0
  57. data/lib/libsql/type_maps/storage_map.rb +38 -0
  58. data/lib/libsql/type_maps/text_map.rb +21 -0
  59. data/lib/libsql/version.rb +8 -0
  60. data/lib/libsql/view.rb +26 -0
  61. data/lib/libsql-ruby.rb +1 -0
  62. data/lib/libsql.rb +51 -0
  63. data/spec/aggregate_spec.rb +158 -0
  64. data/spec/blob_spec.rb +78 -0
  65. data/spec/boolean_spec.rb +24 -0
  66. data/spec/busy_handler.rb +157 -0
  67. data/spec/data/iso-3166-country.txt +242 -0
  68. data/spec/data/iso-3166-schema.sql +22 -0
  69. data/spec/data/iso-3166-subcountry.txt +3995 -0
  70. data/spec/data/make-iso-db.sh +12 -0
  71. data/spec/database_spec.rb +505 -0
  72. data/spec/default_map_spec.rb +92 -0
  73. data/spec/function_spec.rb +78 -0
  74. data/spec/integeration_spec.rb +97 -0
  75. data/spec/iso_3166_database.rb +58 -0
  76. data/spec/json_spec.rb +24 -0
  77. data/spec/libsql_spec.rb +4 -0
  78. data/spec/paths_spec.rb +28 -0
  79. data/spec/progress_handler_spec.rb +91 -0
  80. data/spec/rtree_spec.rb +66 -0
  81. data/spec/schema_spec.rb +131 -0
  82. data/spec/spec_helper.rb +48 -0
  83. data/spec/sqlite3/constants_spec.rb +108 -0
  84. data/spec/sqlite3/database_status_spec.rb +36 -0
  85. data/spec/sqlite3/libsql_version_spec.rb +16 -0
  86. data/spec/sqlite3/status_spec.rb +22 -0
  87. data/spec/sqlite3/version_spec.rb +28 -0
  88. data/spec/sqlite3_spec.rb +53 -0
  89. data/spec/statement_spec.rb +168 -0
  90. data/spec/storage_map_spec.rb +38 -0
  91. data/spec/tap_spec.rb +57 -0
  92. data/spec/text_map_spec.rb +20 -0
  93. data/spec/type_map_spec.rb +14 -0
  94. data/spec/version_spec.rb +8 -0
  95. data/tasks/custom.rake +134 -0
  96. data/tasks/default.rake +257 -0
  97. data/tasks/extension.rake +29 -0
  98. data/tasks/this.rb +208 -0
  99. metadata +328 -0
@@ -0,0 +1,48 @@
1
+ module ::Libsql::SQLite3
2
+ class Database
3
+ ##
4
+ # A wrapper around a proc for use as an SQLite Ddatabase fuction
5
+ #
6
+ # f = Function.new( 'md5', lambda { |x| Digest::MD5.hexdigest( x.to_s ) } )
7
+ #
8
+ class Function
9
+
10
+ # the name of the function, and how it will be called in SQL
11
+ attr_reader :name
12
+
13
+ # The unique signature of this function. This is used to determin if the
14
+ # function is already registered or not
15
+ #
16
+ def self.signature( name, arity )
17
+ "#{name}/#{arity}"
18
+ end
19
+
20
+ # Initialize with the name and the Proc
21
+ #
22
+ def initialize( name, _proc )
23
+ @name = name
24
+ @function = _proc
25
+ end
26
+
27
+ # The unique signature of this function
28
+ #
29
+ def signature
30
+ @signature ||= Function.signature( name, arity )
31
+ end
32
+ alias :to_s :signature
33
+
34
+ # The arity of SQL function, -1 means it is takes a variable number of
35
+ # arguments.
36
+ #
37
+ def arity
38
+ @function.arity
39
+ end
40
+
41
+ # Invoke the proc
42
+ #
43
+ def call( *args )
44
+ @function.call( *args )
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,68 @@
1
+ require 'libsql/sqlite3/constants'
2
+ module ::Libsql::SQLite3
3
+ class Database
4
+ #
5
+ # A Stat represents a single Database Status code and its current highwater mark.
6
+ #
7
+ # Some stats may not have a current or a highwater value, in those cases
8
+ # the associated _has_current?_ or _has_highwater?_ method returns false and the
9
+ # _current_ or _highwater_ method also returns +nil+.
10
+ #
11
+ class Stat
12
+ attr_reader :name
13
+ attr_reader :code
14
+
15
+ def initialize( api_db, name )
16
+ @name = name
17
+ @code = ::Libsql::SQLite3::Constants::DBStatus.value_from_name( name )
18
+ @current = nil
19
+ @highwater = nil
20
+ @api_db = api_db
21
+ end
22
+
23
+ def current
24
+ update!
25
+ return @current
26
+ end
27
+
28
+ def highwater
29
+ update!
30
+ return @highwater
31
+ end
32
+
33
+ #
34
+ # reset the given stat's highwater mark. This will also populate the
35
+ # _@current_ and _@highwater_ instance variables
36
+ #
37
+ def reset!
38
+ update!( true )
39
+ end
40
+ end
41
+
42
+ #
43
+ # Top level Status object holding all the Stat objects indicating the DBStatus
44
+ # of the SQLite3 C library.
45
+ #
46
+ class DBStatus
47
+ ::Libsql::SQLite3::Constants::DBStatus.constants.each do |const_name|
48
+ method_name = const_name.downcase
49
+ module_eval( <<-code, __FILE__, __LINE__ )
50
+ def #{method_name}
51
+ @#{method_name} ||= ::Libsql::SQLite3::Database::Stat.new( self.api_db, '#{method_name}' )
52
+ end
53
+ code
54
+ end
55
+
56
+ attr_reader :api_db
57
+
58
+ def initialize( api_db )
59
+ @api_db = api_db
60
+ end
61
+ end
62
+
63
+ # return the DBstatus object for the sqlite database
64
+ def status
65
+ @status ||= DBStatus.new( self )
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,32 @@
1
+ #--
2
+ # Copyright (c) 2023 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+ module ::Libsql
6
+ module SQLite3
7
+ module LibsqlVersion
8
+ def self.compiled_matches_runtime?
9
+ self.compiled_version == self.runtime_version
10
+ end
11
+ end
12
+
13
+ # Version of libsql that ships with
14
+ LIBSQL_VERSION = LibsqlVersion.to_s.freeze
15
+ end
16
+ end
17
+
18
+ unless ::Libsql::SQLite3::LibsqlVersion.compiled_matches_runtime? then
19
+ warn <<eom
20
+ You are seeing something odd. The compiled version of libsql that
21
+ is embedded in this extension is for some reason, not being used.
22
+ The version in the extension is #{::Libsql::SQLite3::LibsqlVersion.compiled_version} and the version that
23
+ as been loaded as a shared library is #{::Libsql::SQLite3::LibsqlVersion.runtime_version}. These versions
24
+ should be the same, but they are not.
25
+
26
+ One known issue is if you are using this libary in conjunction with
27
+ Hitimes on Mac OS X. You should make sure that "require 'libsql'"
28
+ appears before "require 'hitimes'" in your ruby code.
29
+
30
+ This is a non-trivial problem, and I am working on it.
31
+ eom
32
+ end
@@ -0,0 +1,60 @@
1
+ require 'libsql/sqlite3/constants'
2
+ module ::Libsql::SQLite3
3
+
4
+ #
5
+ # A Stat represents a single Status code and its current highwater mark.
6
+ #
7
+ # Some stats may not have a current or a highwater value, in those cases
8
+ # the associated _has_current?_ or _has_highwater?_ method returns false and the
9
+ # _current_ or _highwater_ method also returns +nil+.
10
+ #
11
+ class Stat
12
+ attr_reader :name
13
+ attr_reader :code
14
+
15
+ def initialize( name )
16
+ @name = name
17
+ @code = ::Libsql::SQLite3::Constants::Status.value_from_name( name )
18
+ @current = nil
19
+ @highwater = nil
20
+ end
21
+
22
+ def current
23
+ update!
24
+ return @current
25
+ end
26
+
27
+ def highwater
28
+ update!
29
+ return @highwater
30
+ end
31
+
32
+ #
33
+ # reset the given stat's highwater mark. This will also populate the
34
+ # _@current_ and _@highwater_ instance variables
35
+ #
36
+ def reset!
37
+ update!( true )
38
+ end
39
+ end
40
+
41
+ #
42
+ # Top level Status object holding all the Stat objects indicating the Status
43
+ # of the SQLite3 C library.
44
+ #
45
+ class Status
46
+ ::Libsql::SQLite3::Constants::Status.constants.each do |const_name|
47
+ method_name = const_name.downcase
48
+ module_eval( <<-code, __FILE__, __LINE__ )
49
+ def #{method_name}
50
+ @#{method_name} ||= ::Libsql::SQLite3::Stat.new( '#{method_name}' )
51
+ end
52
+ code
53
+ end
54
+ end
55
+
56
+ # return the status object for the sqlite database
57
+ def self.status
58
+ @status ||= Status.new
59
+ end
60
+ end
@@ -0,0 +1,55 @@
1
+ #--
2
+ # Copyright (c) 2023 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+ module ::Libsql
6
+ module SQLite3
7
+ module Version
8
+ # Sqlite3 version number is equal to
9
+ # MAJOR * 1_000_000 + MINOR * 1_000 + RELEASE
10
+
11
+ # major version number of the SQLite C library
12
+ MAJOR = (to_i / 1_000_000).freeze
13
+
14
+ # minor version number of the SQLite C library
15
+ MINOR = ((to_i % 1_000_000) / 1_000).freeze
16
+
17
+ # release version number of the SQLite C library
18
+ RELEASE = (to_i % 1_000).freeze
19
+
20
+ #
21
+ # call-seq:
22
+ # ::Libsql::SQLite3::Version.to_a -> [ MAJOR, MINOR, RELEASE ]
23
+ #
24
+ # Return the SQLite C library version number as an array of MAJOR, MINOR,
25
+ # RELEASE
26
+ #
27
+ def self.to_a
28
+ [ MAJOR, MINOR, RELEASE ]
29
+ end
30
+
31
+ def self.compiled_matches_runtime?
32
+ self.compiled_version == self.runtime_version
33
+ end
34
+ end
35
+
36
+ # Version of SQLite that ships with ::Libsql
37
+ VERSION = Version.to_s.freeze
38
+ end
39
+ end
40
+
41
+ unless ::Libsql::SQLite3::Version.compiled_matches_runtime? then
42
+ warn <<eom
43
+ You are seeing something odd. The compiled version of SQLite that
44
+ is embedded in this extension is for some reason, not being used.
45
+ The version in the extension is #{::Libsql::SQLite3::Version.compiled_version} and the version that
46
+ as been loaded as a shared library is #{::Libsql::SQLite3::Version.runtime_version}. These versions
47
+ should be the same, but they are not.
48
+
49
+ One known issue is if you are using this libary in conjunction with
50
+ Hitimes on Mac OS X. You should make sure that "require 'libsql'"
51
+ appears before "require 'hitimes'" in your ruby code.
52
+
53
+ This is a non-trivial problem, and I am working on it.
54
+ eom
55
+ end
@@ -0,0 +1,7 @@
1
+ require 'libsql/version'
2
+ require 'libsql/sqlite3/version'
3
+ require 'libsql/sqlite3/libsql_version'
4
+ require 'libsql/sqlite3/constants'
5
+ require 'libsql/sqlite3/status'
6
+ require 'libsql/sqlite3/database/status'
7
+ require 'libsql/sqlite3/database/function'
@@ -0,0 +1,421 @@
1
+ #--
2
+ # Copyright (c) 2023 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 ::Libsql
11
+ class Statement
12
+
13
+ include ::Libsql::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
+ # ::Libsql 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 ::Libsql::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 ::Libsql::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 ::Libsql::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 = ::Libsql::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 = ::Libsql::Blob.new( :string => @stmt_api.column_blob( idx ), :column => col.schema )
289
+ end
290
+ else
291
+ raise ::Libsql::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} (#{::Libsql::SQLite3::Constants::ResultCode.name_from_value( rc )}) : #{@db.api.last_error_message}"
304
+ raise ::Libsql::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 = ::Libsql::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