-
Notifications
You must be signed in to change notification settings - Fork 203
/
Copy pathstatement.rb
190 lines (170 loc) · 5.93 KB
/
statement.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
require "sqlite3/errors"
require "sqlite3/resultset"
class String
def to_blob
SQLite3::Blob.new(self)
end
end
module SQLite3
# A statement represents a prepared-but-unexecuted SQL query. It will rarely
# (if ever) be instantiated directly by a client, and is most often obtained
# via the Database#prepare method.
class Statement
include Enumerable
# This is any text that followed the first valid SQL statement in the text
# with which the statement was initialized. If there was no trailing text,
# this will be the empty string.
attr_reader :remainder
# call-seq: SQLite3::Statement.new(db, sql)
#
# Create a new statement attached to the given Database instance, and which
# encapsulates the given SQL text. If the text contains more than one
# statement (i.e., separated by semicolons), then the #remainder property
# will be set to the trailing text.
def initialize(db, sql)
raise ArgumentError, "prepare called on a closed database" if db.closed?
sql = sql.encode(Encoding::UTF_8) if sql && sql.encoding != Encoding::UTF_8
@connection = db
@columns = nil
@types = nil
@remainder = prepare db, sql
end
# Binds the given variables to the corresponding placeholders in the SQL
# text.
#
# See Database#execute for a description of the valid placeholder
# syntaxes.
#
# Example:
#
# stmt = db.prepare( "select * from table where a=? and b=?" )
# stmt.bind_params( 15, "hello" )
#
# See also #execute, #bind_param, Statement#bind_param, and
# Statement#bind_params.
def bind_params(*bind_vars)
index = 1
bind_vars.flatten.each do |var|
if Hash === var
var.each { |key, val| bind_param key, val }
else
bind_param index, var
index += 1
end
end
end
# Execute the statement. This creates a new ResultSet object for the
# statement's virtual machine. If a block was given, the new ResultSet will
# be yielded to it; otherwise, the ResultSet will be returned.
#
# Any parameters will be bound to the statement using #bind_params.
#
# Example:
#
# stmt = db.prepare( "select * from table" )
# stmt.execute do |result|
# ...
# end
#
# See also #bind_params, #execute!.
def execute(*bind_vars)
reset! if active? || done?
bind_params(*bind_vars) unless bind_vars.empty?
results = @connection.build_result_set self
step if column_count == 0
yield results if block_given?
results
end
# Execute the statement. If no block was given, this returns an array of
# rows returned by executing the statement. Otherwise, each row will be
# yielded to the block.
#
# Any parameters will be bound to the statement using #bind_params.
#
# Example:
#
# stmt = db.prepare( "select * from table" )
# stmt.execute! do |row|
# ...
# end
#
# See also #bind_params, #execute.
def execute!(*bind_vars, &block)
execute(*bind_vars)
block ? each(&block) : to_a
end
# Returns true if the statement is currently active, meaning it has an
# open result set.
def active?
!done?
end
# Return an array of the column names for this statement. Note that this
# may execute the statement in order to obtain the metadata; this makes it
# a (potentially) expensive operation.
def columns
get_metadata unless @columns
@columns
end
def each
loop do
val = step
break self if done?
yield val
end
end
# Return an array of the data types for each column in this statement. Note
# that this may execute the statement in order to obtain the metadata; this
# makes it a (potentially) expensive operation.
def types
must_be_open!
get_metadata unless @types
@types
end
# Performs a sanity check to ensure that the statement is not
# closed. If it is, an exception is raised.
def must_be_open! # :nodoc:
if closed?
raise SQLite3::Exception, "cannot use a closed statement"
end
end
# Returns a Hash containing information about the statement.
# The contents of the hash are implementation specific and may change in
# the future without notice. The hash includes information about internal
# statistics about the statement such as:
# - +fullscan_steps+: the number of times that SQLite has stepped forward
# in a table as part of a full table scan
# - +sorts+: the number of sort operations that have occurred
# - +autoindexes+: the number of rows inserted into transient indices
# that were created automatically in order to help joins run faster
# - +vm_steps+: the number of virtual machine operations executed by the
# prepared statement
# - +reprepares+: the number of times that the prepare statement has been
# automatically regenerated due to schema changes or changes to bound
# parameters that might affect the query plan
# - +runs+: the number of times that the prepared statement has been run
# - +filter_misses+: the number of times that the Bloom filter returned
# a find, and thus the join step had to be processed as normal
# - +filter_hits+: the number of times that a join step was bypassed
# because a Bloom filter returned not-found
def stat key = nil
if key
stat_for(key)
else
stats_as_hash
end
end
private
# A convenience method for obtaining the metadata about the query. Note
# that this will actually execute the SQL, which means it can be a
# (potentially) expensive operation.
def get_metadata
@columns = Array.new(column_count) do |column|
column_name column
end
@types = Array.new(column_count) do |column|
val = column_decltype(column)
val&.downcase
end
end
end
end