sqlite3-ruby 1.2.3-x86-mingw32
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.
Potentially problematic release.
This version of sqlite3-ruby might be problematic. Click here for more details.
- data/README.rdoc +51 -0
- data/doc/faq/faq.html +408 -0
- data/doc/faq/faq.rb +145 -0
- data/doc/faq/faq.yml +426 -0
- data/ext/sqlite3_api/MANIFEST +4 -0
- data/ext/sqlite3_api/Makefile +142 -0
- data/ext/sqlite3_api/extconf.rb +16 -0
- data/ext/sqlite3_api/mkmf.log +66 -0
- data/ext/sqlite3_api/sqlite3_api.i +358 -0
- data/ext/sqlite3_api/sqlite3_api.so +0 -0
- data/ext/sqlite3_api/sqlite3_api_wrap.c +3094 -0
- data/ext/sqlite3_api/sqlite3_api_wrap.o +0 -0
- data/ext/sqlite3_api/win32/build.bat +7 -0
- data/lib/sqlite3.rb +1 -0
- data/lib/sqlite3/constants.rb +49 -0
- data/lib/sqlite3/database.rb +715 -0
- data/lib/sqlite3/driver/dl/api.rb +152 -0
- data/lib/sqlite3/driver/dl/driver.rb +307 -0
- data/lib/sqlite3/driver/native/driver.rb +211 -0
- data/lib/sqlite3/errors.rb +68 -0
- data/lib/sqlite3/pragmas.rb +271 -0
- data/lib/sqlite3/resultset.rb +176 -0
- data/lib/sqlite3/statement.rb +230 -0
- data/lib/sqlite3/translator.rb +109 -0
- data/lib/sqlite3/value.rb +57 -0
- data/lib/sqlite3/version.rb +14 -0
- data/test/bm.rb +140 -0
- data/test/driver/dl/tc_driver.rb +292 -0
- data/test/mocks.rb +45 -0
- data/test/native-vs-dl.rb +126 -0
- data/test/tc_database.rb +198 -0
- data/test/tc_errors.rb +21 -0
- data/test/tc_integration.rb +1044 -0
- data/test/tests.rb +6 -0
- metadata +96 -0
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'sqlite3/constants'
|
2
|
+
require 'sqlite3/errors'
|
3
|
+
|
4
|
+
module SQLite3
|
5
|
+
|
6
|
+
# The ResultSet object encapsulates the enumerability of a query's output.
|
7
|
+
# It is a simple cursor over the data that the query returns. It will
|
8
|
+
# very rarely (if ever) be instantiated directly. Instead, client's should
|
9
|
+
# obtain a ResultSet instance via Statement#execute.
|
10
|
+
class ResultSet
|
11
|
+
include Enumerable
|
12
|
+
|
13
|
+
# The class of which we return an object in case we want an Array as
|
14
|
+
# result. (ArrayFields is installed.)
|
15
|
+
class ArrayWithTypes < Array
|
16
|
+
attr_accessor :types
|
17
|
+
end
|
18
|
+
|
19
|
+
# The class of which we return an object in case we want an Array as
|
20
|
+
# result. (ArrayFields is not installed.)
|
21
|
+
class ArrayWithTypesAndFields < Array
|
22
|
+
attr_accessor :types
|
23
|
+
attr_accessor :fields
|
24
|
+
end
|
25
|
+
|
26
|
+
# The class of which we return an object in case we want a Hash as
|
27
|
+
# result.
|
28
|
+
class HashWithTypes < Hash
|
29
|
+
attr_accessor :types
|
30
|
+
end
|
31
|
+
|
32
|
+
# Create a new ResultSet attached to the given database, using the
|
33
|
+
# given sql text.
|
34
|
+
def initialize( db, stmt )
|
35
|
+
@db = db
|
36
|
+
@driver = @db.driver
|
37
|
+
@stmt = stmt
|
38
|
+
commence
|
39
|
+
end
|
40
|
+
|
41
|
+
# A convenience method for compiling the virtual machine and stepping
|
42
|
+
# to the first row of the result set.
|
43
|
+
def commence
|
44
|
+
result = @driver.step( @stmt.handle )
|
45
|
+
if result == Constants::ErrorCode::ERROR
|
46
|
+
@driver.reset( @stmt.handle )
|
47
|
+
end
|
48
|
+
check result
|
49
|
+
@first_row = true
|
50
|
+
end
|
51
|
+
private :commence
|
52
|
+
|
53
|
+
def check( result )
|
54
|
+
@eof = ( result == Constants::ErrorCode::DONE )
|
55
|
+
found = ( result == Constants::ErrorCode::ROW )
|
56
|
+
Error.check( result, @db ) unless @eof || found
|
57
|
+
end
|
58
|
+
private :check
|
59
|
+
|
60
|
+
# Reset the cursor, so that a result set which has reached end-of-file
|
61
|
+
# can be rewound and reiterated.
|
62
|
+
def reset( *bind_params )
|
63
|
+
@stmt.must_be_open!
|
64
|
+
@stmt.reset!(false)
|
65
|
+
@driver.reset( @stmt.handle )
|
66
|
+
@stmt.bind_params( *bind_params )
|
67
|
+
@eof = false
|
68
|
+
commence
|
69
|
+
end
|
70
|
+
|
71
|
+
# Query whether the cursor has reached the end of the result set or not.
|
72
|
+
def eof?
|
73
|
+
@eof
|
74
|
+
end
|
75
|
+
|
76
|
+
# Obtain the next row from the cursor. If there are no more rows to be
|
77
|
+
# had, this will return +nil+. If type translation is active on the
|
78
|
+
# corresponding database, the values in the row will be translated
|
79
|
+
# according to their types.
|
80
|
+
#
|
81
|
+
# The returned value will be an array, unless Database#results_as_hash has
|
82
|
+
# been set to +true+, in which case the returned value will be a hash.
|
83
|
+
#
|
84
|
+
# For arrays, the column names are accessible via the +fields+ property,
|
85
|
+
# and the column types are accessible via the +types+ property.
|
86
|
+
#
|
87
|
+
# For hashes, the column names are the keys of the hash, and the column
|
88
|
+
# types are accessible via the +types+ property.
|
89
|
+
def next
|
90
|
+
return nil if @eof
|
91
|
+
|
92
|
+
@stmt.must_be_open!
|
93
|
+
|
94
|
+
unless @first_row
|
95
|
+
result = @driver.step( @stmt.handle )
|
96
|
+
check result
|
97
|
+
end
|
98
|
+
|
99
|
+
@first_row = false
|
100
|
+
|
101
|
+
unless @eof
|
102
|
+
row = []
|
103
|
+
@driver.data_count( @stmt.handle ).times do |column|
|
104
|
+
type = @driver.column_type( @stmt.handle, column )
|
105
|
+
|
106
|
+
if type == Constants::ColumnType::TEXT
|
107
|
+
row << @driver.column_text( @stmt.handle, column )
|
108
|
+
elsif type == Constants::ColumnType::NULL
|
109
|
+
row << nil
|
110
|
+
elsif type == Constants::ColumnType::BLOB
|
111
|
+
row << @driver.column_blob( @stmt.handle, column )
|
112
|
+
else
|
113
|
+
row << @driver.column_text( @stmt.handle, column )
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
if @db.type_translation
|
118
|
+
row = @stmt.types.zip( row ).map do |type, value|
|
119
|
+
@db.translator.translate( type, value )
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
if @db.results_as_hash
|
124
|
+
new_row = HashWithTypes[ *( @stmt.columns.zip( row ).to_a.flatten ) ]
|
125
|
+
row.each_with_index { |value,idx| new_row[idx] = value }
|
126
|
+
row = new_row
|
127
|
+
else
|
128
|
+
if row.respond_to?(:fields)
|
129
|
+
row = ArrayWithTypes.new(row)
|
130
|
+
else
|
131
|
+
row = ArrayWithTypesAndFields.new(row)
|
132
|
+
end
|
133
|
+
row.fields = @stmt.columns
|
134
|
+
end
|
135
|
+
|
136
|
+
row.types = @stmt.types
|
137
|
+
|
138
|
+
return row
|
139
|
+
end
|
140
|
+
|
141
|
+
nil
|
142
|
+
end
|
143
|
+
|
144
|
+
# Required by the Enumerable mixin. Provides an internal iterator over the
|
145
|
+
# rows of the result set.
|
146
|
+
def each
|
147
|
+
while row=self.next
|
148
|
+
yield row
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Closes the statement that spawned this result set.
|
153
|
+
# <em>Use with caution!</em> Closing a result set will automatically
|
154
|
+
# close any other result sets that were spawned from the same statement.
|
155
|
+
def close
|
156
|
+
@stmt.close
|
157
|
+
end
|
158
|
+
|
159
|
+
# Queries whether the underlying statement has been closed or not.
|
160
|
+
def closed?
|
161
|
+
@stmt.closed?
|
162
|
+
end
|
163
|
+
|
164
|
+
# Returns the types of the columns returned by this result set.
|
165
|
+
def types
|
166
|
+
@stmt.types
|
167
|
+
end
|
168
|
+
|
169
|
+
# Returns the names of the columns returned by this result set.
|
170
|
+
def columns
|
171
|
+
@stmt.columns
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
require 'sqlite3/errors'
|
2
|
+
require 'sqlite3/resultset'
|
3
|
+
|
4
|
+
class String
|
5
|
+
def to_blob
|
6
|
+
SQLite3::Blob.new( self )
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module SQLite3
|
11
|
+
|
12
|
+
# A class for differentiating between strings and blobs, when binding them
|
13
|
+
# into statements.
|
14
|
+
class Blob < String; end
|
15
|
+
|
16
|
+
# A statement represents a prepared-but-unexecuted SQL query. It will rarely
|
17
|
+
# (if ever) be instantiated directly by a client, and is most often obtained
|
18
|
+
# via the Database#prepare method.
|
19
|
+
class Statement
|
20
|
+
|
21
|
+
# This is any text that followed the first valid SQL statement in the text
|
22
|
+
# with which the statement was initialized. If there was no trailing text,
|
23
|
+
# this will be the empty string.
|
24
|
+
attr_reader :remainder
|
25
|
+
|
26
|
+
# The underlying opaque handle used to access the SQLite @driver.
|
27
|
+
attr_reader :handle
|
28
|
+
|
29
|
+
# Create a new statement attached to the given Database instance, and which
|
30
|
+
# encapsulates the given SQL text. If the text contains more than one
|
31
|
+
# statement (i.e., separated by semicolons), then the #remainder property
|
32
|
+
# will be set to the trailing text.
|
33
|
+
def initialize( db, sql, utf16=false )
|
34
|
+
@db = db
|
35
|
+
@driver = @db.driver
|
36
|
+
@closed = false
|
37
|
+
@results = @columns = nil
|
38
|
+
result, @handle, @remainder = @driver.prepare( @db.handle, sql )
|
39
|
+
Error.check( result, @db )
|
40
|
+
end
|
41
|
+
|
42
|
+
# Closes the statement by finalizing the underlying statement
|
43
|
+
# handle. The statement must not be used after being closed.
|
44
|
+
def close
|
45
|
+
must_be_open!
|
46
|
+
@closed = true
|
47
|
+
@driver.finalize( @handle )
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns true if the underlying statement has been closed.
|
51
|
+
def closed?
|
52
|
+
@closed
|
53
|
+
end
|
54
|
+
|
55
|
+
# Binds the given variables to the corresponding placeholders in the SQL
|
56
|
+
# text.
|
57
|
+
#
|
58
|
+
# See Database#execute for a description of the valid placeholder
|
59
|
+
# syntaxes.
|
60
|
+
#
|
61
|
+
# Example:
|
62
|
+
#
|
63
|
+
# stmt = db.prepare( "select * from table where a=? and b=?" )
|
64
|
+
# stmt.bind_params( 15, "hello" )
|
65
|
+
#
|
66
|
+
# See also #execute, #bind_param, Statement#bind_param, and
|
67
|
+
# Statement#bind_params.
|
68
|
+
def bind_params( *bind_vars )
|
69
|
+
index = 1
|
70
|
+
bind_vars.flatten.each do |var|
|
71
|
+
if Hash === var
|
72
|
+
var.each { |key, val| bind_param key, val }
|
73
|
+
else
|
74
|
+
bind_param index, var
|
75
|
+
index += 1
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Binds value to the named (or positional) placeholder. If +param+ is a
|
81
|
+
# Fixnum, it is treated as an index for a positional placeholder.
|
82
|
+
# Otherwise it is used as the name of the placeholder to bind to.
|
83
|
+
#
|
84
|
+
# See also #bind_params.
|
85
|
+
def bind_param( param, value )
|
86
|
+
must_be_open!
|
87
|
+
reset! if active?
|
88
|
+
if Fixnum === param
|
89
|
+
case value
|
90
|
+
when Bignum then
|
91
|
+
@driver.bind_int64( @handle, param, value )
|
92
|
+
when Integer then
|
93
|
+
if value >= (2 ** 31)
|
94
|
+
@driver.bind_int64( @handle, param, value )
|
95
|
+
else
|
96
|
+
@driver.bind_int( @handle, param, value )
|
97
|
+
end
|
98
|
+
when Numeric then
|
99
|
+
@driver.bind_double( @handle, param, value.to_f )
|
100
|
+
when Blob then
|
101
|
+
@driver.bind_blob( @handle, param, value )
|
102
|
+
when nil then
|
103
|
+
@driver.bind_null( @handle, param )
|
104
|
+
else
|
105
|
+
@driver.bind_text( @handle, param, value )
|
106
|
+
end
|
107
|
+
else
|
108
|
+
param = param.to_s
|
109
|
+
param = ":#{param}" unless param[0] == ?:
|
110
|
+
index = @driver.bind_parameter_index( @handle, param )
|
111
|
+
raise Exception, "no such bind parameter '#{param}'" if index == 0
|
112
|
+
bind_param index, value
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Execute the statement. This creates a new ResultSet object for the
|
117
|
+
# statement's virtual machine. If a block was given, the new ResultSet will
|
118
|
+
# be yielded to it; otherwise, the ResultSet will be returned.
|
119
|
+
#
|
120
|
+
# Any parameters will be bound to the statement using #bind_params.
|
121
|
+
#
|
122
|
+
# Example:
|
123
|
+
#
|
124
|
+
# stmt = db.prepare( "select * from table" )
|
125
|
+
# stmt.execute do |result|
|
126
|
+
# ...
|
127
|
+
# end
|
128
|
+
#
|
129
|
+
# See also #bind_params, #execute!.
|
130
|
+
def execute( *bind_vars )
|
131
|
+
must_be_open!
|
132
|
+
reset! if active?
|
133
|
+
|
134
|
+
bind_params(*bind_vars) unless bind_vars.empty?
|
135
|
+
@results = ResultSet.new( @db, self )
|
136
|
+
|
137
|
+
if block_given?
|
138
|
+
yield @results
|
139
|
+
else
|
140
|
+
return @results
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Execute the statement. If no block was given, this returns an array of
|
145
|
+
# rows returned by executing the statement. Otherwise, each row will be
|
146
|
+
# yielded to the block.
|
147
|
+
#
|
148
|
+
# Any parameters will be bound to the statement using #bind_params.
|
149
|
+
#
|
150
|
+
# Example:
|
151
|
+
#
|
152
|
+
# stmt = db.prepare( "select * from table" )
|
153
|
+
# stmt.execute! do |row|
|
154
|
+
# ...
|
155
|
+
# end
|
156
|
+
#
|
157
|
+
# See also #bind_params, #execute.
|
158
|
+
def execute!( *bind_vars )
|
159
|
+
result = execute( *bind_vars )
|
160
|
+
rows = [] unless block_given?
|
161
|
+
while row = result.next
|
162
|
+
if block_given?
|
163
|
+
yield row
|
164
|
+
else
|
165
|
+
rows << row
|
166
|
+
end
|
167
|
+
end
|
168
|
+
rows
|
169
|
+
end
|
170
|
+
|
171
|
+
# Resets the statement. This is typically done internally, though it might
|
172
|
+
# occassionally be necessary to manually reset the statement.
|
173
|
+
def reset!(clear_result=true)
|
174
|
+
@driver.reset(@handle)
|
175
|
+
@results = nil if clear_result
|
176
|
+
end
|
177
|
+
|
178
|
+
# Returns true if the statement is currently active, meaning it has an
|
179
|
+
# open result set.
|
180
|
+
def active?
|
181
|
+
not @results.nil?
|
182
|
+
end
|
183
|
+
|
184
|
+
# Return an array of the column names for this statement. Note that this
|
185
|
+
# may execute the statement in order to obtain the metadata; this makes it
|
186
|
+
# a (potentially) expensive operation.
|
187
|
+
def columns
|
188
|
+
get_metadata unless @columns
|
189
|
+
return @columns
|
190
|
+
end
|
191
|
+
|
192
|
+
# Return an array of the data types for each column in this statement. Note
|
193
|
+
# that this may execute the statement in order to obtain the metadata; this
|
194
|
+
# makes it a (potentially) expensive operation.
|
195
|
+
def types
|
196
|
+
get_metadata unless @types
|
197
|
+
return @types
|
198
|
+
end
|
199
|
+
|
200
|
+
# A convenience method for obtaining the metadata about the query. Note
|
201
|
+
# that this will actually execute the SQL, which means it can be a
|
202
|
+
# (potentially) expensive operation.
|
203
|
+
def get_metadata
|
204
|
+
must_be_open!
|
205
|
+
|
206
|
+
@columns = []
|
207
|
+
@types = []
|
208
|
+
|
209
|
+
column_count = @driver.column_count( @handle )
|
210
|
+
column_count.times do |column|
|
211
|
+
@columns << @driver.column_name( @handle, column )
|
212
|
+
@types << @driver.column_decltype( @handle, column )
|
213
|
+
end
|
214
|
+
|
215
|
+
@columns.freeze
|
216
|
+
@types.freeze
|
217
|
+
end
|
218
|
+
private :get_metadata
|
219
|
+
|
220
|
+
# Performs a sanity check to ensure that the statement is not
|
221
|
+
# closed. If it is, an exception is raised.
|
222
|
+
def must_be_open! # :nodoc:
|
223
|
+
if @closed
|
224
|
+
raise SQLite3::Exception, "cannot use a closed statement"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
module SQLite3
|
5
|
+
|
6
|
+
# The Translator class encapsulates the logic and callbacks necessary for
|
7
|
+
# converting string data to a value of some specified type. Every Database
|
8
|
+
# instance may have a Translator instance, in order to assist in type
|
9
|
+
# translation (Database#type_translation).
|
10
|
+
#
|
11
|
+
# Further, applications may define their own custom type translation logic
|
12
|
+
# by registering translator blocks with the corresponding database's
|
13
|
+
# translator instance (Database#translator).
|
14
|
+
class Translator
|
15
|
+
|
16
|
+
# Create a new Translator instance. It will be preinitialized with default
|
17
|
+
# translators for most SQL data types.
|
18
|
+
def initialize
|
19
|
+
@translators = Hash.new( proc { |type,value| value } )
|
20
|
+
@type_name_cache = {}
|
21
|
+
register_default_translators
|
22
|
+
end
|
23
|
+
|
24
|
+
# Add a new translator block, which will be invoked to process type
|
25
|
+
# translations to the given type. The type should be an SQL datatype, and
|
26
|
+
# may include parentheses (i.e., "VARCHAR(30)"). However, any parenthetical
|
27
|
+
# information is stripped off and discarded, so type translation decisions
|
28
|
+
# are made solely on the "base" type name.
|
29
|
+
#
|
30
|
+
# The translator block itself should accept two parameters, "type" and
|
31
|
+
# "value". In this case, the "type" is the full type name (including
|
32
|
+
# parentheses), so the block itself may include logic for changing how a
|
33
|
+
# type is translated based on the additional data. The "value" parameter
|
34
|
+
# is the (string) data to convert.
|
35
|
+
#
|
36
|
+
# The block should return the translated value.
|
37
|
+
def add_translator( type, &block ) # :yields: type, value
|
38
|
+
@translators[ type_name( type ) ] = block
|
39
|
+
end
|
40
|
+
|
41
|
+
# Translate the given string value to a value of the given type. In the
|
42
|
+
# absense of an installed translator block for the given type, the value
|
43
|
+
# itself is always returned. Further, +nil+ values are never translated,
|
44
|
+
# and are always passed straight through regardless of the type parameter.
|
45
|
+
def translate( type, value )
|
46
|
+
unless value.nil?
|
47
|
+
@translators[ type_name( type ) ].call( type, value )
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# A convenience method for working with type names. This returns the "base"
|
52
|
+
# type name, without any parenthetical data.
|
53
|
+
def type_name( type )
|
54
|
+
@type_name_cache[type] ||= begin
|
55
|
+
type = "" if type.nil?
|
56
|
+
type = $1 if type =~ /^(.*?)\(/
|
57
|
+
type.upcase
|
58
|
+
end
|
59
|
+
end
|
60
|
+
private :type_name
|
61
|
+
|
62
|
+
# Register the default translators for the current Translator instance.
|
63
|
+
# This includes translators for most major SQL data types.
|
64
|
+
def register_default_translators
|
65
|
+
[ "time",
|
66
|
+
"timestamp" ].each { |type| add_translator( type ) { |t, v| Time.parse( v ) } }
|
67
|
+
|
68
|
+
add_translator( "date" ) { |t,v| Date.parse(v) }
|
69
|
+
add_translator( "datetime" ) { |t,v| DateTime.parse(v) }
|
70
|
+
|
71
|
+
[ "decimal",
|
72
|
+
"float",
|
73
|
+
"numeric",
|
74
|
+
"double",
|
75
|
+
"real",
|
76
|
+
"dec",
|
77
|
+
"fixed" ].each { |type| add_translator( type ) { |t,v| v.to_f } }
|
78
|
+
|
79
|
+
[ "integer",
|
80
|
+
"smallint",
|
81
|
+
"mediumint",
|
82
|
+
"int",
|
83
|
+
"bigint" ].each { |type| add_translator( type ) { |t,v| v.to_i } }
|
84
|
+
|
85
|
+
[ "bit",
|
86
|
+
"bool",
|
87
|
+
"boolean" ].each do |type|
|
88
|
+
add_translator( type ) do |t,v|
|
89
|
+
!( v.strip.gsub(/00+/,"0") == "0" ||
|
90
|
+
v.downcase == "false" ||
|
91
|
+
v.downcase == "f" ||
|
92
|
+
v.downcase == "no" ||
|
93
|
+
v.downcase == "n" )
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
add_translator( "tinyint" ) do |type, value|
|
98
|
+
if type =~ /\(\s*1\s*\)/
|
99
|
+
value.to_i == 1
|
100
|
+
else
|
101
|
+
value.to_i
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
private :register_default_translators
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|