sqlite-ruby 2.2.0 → 2.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/doc/faq/faq.html +28 -28
- data/lib/sqlite/resultset.rb +2 -2
- data/lib/sqlite/statement.rb +1 -1
- data/lib/sqlite/version.rb +1 -1
- data/test/lib/sqlite.rb +34 -0
- data/test/lib/sqlite/database.rb +682 -0
- data/test/lib/sqlite/parsed_statement.rb +233 -0
- data/test/lib/sqlite/pragmas.rb +236 -0
- data/test/lib/sqlite/resultset.rb +168 -0
- data/test/lib/sqlite/statement.rb +177 -0
- data/test/lib/sqlite/translator.rb +135 -0
- data/test/lib/sqlite/version.rb +45 -0
- data/test/lib/sqlite_api.so +0 -0
- data/test/tc_database.rb +4 -0
- metadata +13 -2
data/doc/faq/faq.html
CHANGED
@@ -62,25 +62,25 @@
|
|
62
62
|
<ul>
|
63
63
|
<li>How do I do a database query?
|
64
64
|
<ul>
|
65
|
-
<li><a href='#
|
66
|
-
<li><a href='#
|
67
|
-
<li><a href='#
|
68
|
-
<li><a href='#
|
69
|
-
<li><a href='#
|
70
|
-
<li><a href='#
|
65
|
+
<li><a href='#538599180'>I just want an array of the rows…</a></li>
|
66
|
+
<li><a href='#538599140'>I’d like to use a block to iterate through the rows…</a></li>
|
67
|
+
<li><a href='#538599100'>I need to get the column names as well as the rows…</a></li>
|
68
|
+
<li><a href='#538599060'>I need the result set object itself…</a></li>
|
69
|
+
<li><a href='#538599020'>I just want the first row of the result set…</a></li>
|
70
|
+
<li><a href='#538598980'>I just want the first value of the first row of the result set…</a></li>
|
71
71
|
</ul>
|
72
72
|
</li>
|
73
|
-
<li><a href='#
|
74
|
-
<li><a href='#
|
75
|
-
<li><a href='#
|
76
|
-
<li><a href='#
|
77
|
-
<li><a href='#
|
78
|
-
<li><a href='#
|
79
|
-
<li><a href='#
|
80
|
-
<li><a href='#
|
73
|
+
<li><a href='#538598910'>How do I prepare a statement for repeated execution?</a></li>
|
74
|
+
<li><a href='#538598870'>How do I use placeholders in an <span class="caps">SQL</span> statement?</a></li>
|
75
|
+
<li><a href='#538598830'>How do I discover metadata about a query?</a></li>
|
76
|
+
<li><a href='#538598790'>I’d like the rows to be indexible by column name.</a></li>
|
77
|
+
<li><a href='#538598750'>I’d like the values from a query to be the correct types, instead of String.</a></li>
|
78
|
+
<li><a href='#538598710'>How do I do a <span class="caps">DDL </span>(insert, update, delete) statement?</a></li>
|
79
|
+
<li><a href='#538598670'>How do I execute multiple statements in a single string?</a></li>
|
80
|
+
<li><a href='#538598630'>How do I begin/end a transaction?</a></li>
|
81
81
|
</ul>
|
82
82
|
</div>
|
83
|
-
<a name='
|
83
|
+
<a name='538599180'></a>
|
84
84
|
<div class='faq-title'>How do I do a database query? I just want an array of the rows…</div>
|
85
85
|
<div class='faq-answer'><p>Use the <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html'>Database#execute</a> method. If you don’t give it a block, it will return an array of all the rows:</p>
|
86
86
|
|
@@ -90,7 +90,7 @@
|
|
90
90
|
db = SQLite::<a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html'>Database</a>.new( "test.db" )
|
91
91
|
rows = db.execute( "select * from test" )
|
92
92
|
</pre></div>
|
93
|
-
<a name='
|
93
|
+
<a name='538599140'></a>
|
94
94
|
<div class='faq-title'>How do I do a database query? I’d like to use a block to iterate through the rows…</div>
|
95
95
|
<div class='faq-answer'><p>Use the <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html'>Database#execute</a> method. If you give it a block, each row of the result will be yielded to the block:</p>
|
96
96
|
|
@@ -102,7 +102,7 @@
|
|
102
102
|
...
|
103
103
|
end
|
104
104
|
</pre></div>
|
105
|
-
<a name='
|
105
|
+
<a name='538599100'></a>
|
106
106
|
<div class='faq-title'>How do I do a database query? I need to get the column names as well as the rows…</div>
|
107
107
|
<div class='faq-answer'><p>Use the <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html'>Database#execute2</a> method. This works just like <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html'>Database#execute</a>; if you don’t give it a block, it returns an array of rows; otherwise, it will yield each row to the block. <em>However</em>, the first row returned is always an array of the column names from the query:</p>
|
108
108
|
|
@@ -123,7 +123,7 @@
|
|
123
123
|
end
|
124
124
|
end
|
125
125
|
</pre></div>
|
126
|
-
<a name='
|
126
|
+
<a name='538599060'></a>
|
127
127
|
<div class='faq-title'>How do I do a database query? I need the result set object itself…</div>
|
128
128
|
<div class='faq-answer'><p>Sometimes you don’t want all the rows at once, and yet you’d like to be able to iterate through the results. For instance, you may want to pass the results to some other function (or series of functions) and have them pull rows from the results on demand. This is more effecient for very large queries.</p>
|
129
129
|
|
@@ -167,7 +167,7 @@
|
|
167
167
|
</pre>
|
168
168
|
|
169
169
|
<p>In general, <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html'>Database#query</a> is not a very good choice for such operations…</p></div>
|
170
|
-
<a name='
|
170
|
+
<a name='538599020'></a>
|
171
171
|
<div class='faq-title'>How do I do a database query? I just want the first row of the result set…</div>
|
172
172
|
<div class='faq-answer'><p>Easy. Just call <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html'>Database#get_first_row</a>:</p>
|
173
173
|
|
@@ -176,7 +176,7 @@
|
|
176
176
|
</pre>
|
177
177
|
|
178
178
|
<p>This also supports bind variables, just like <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html'>Database#execute</a> and friends.</p></div>
|
179
|
-
<a name='
|
179
|
+
<a name='538598980'></a>
|
180
180
|
<div class='faq-title'>How do I do a database query? I just want the first value of the first row of the result set…</div>
|
181
181
|
<div class='faq-answer'><p>Also easy. Just call <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html'>Database#get_first_value</a>:</p>
|
182
182
|
|
@@ -185,7 +185,7 @@
|
|
185
185
|
</pre>
|
186
186
|
|
187
187
|
<p>This also supports bind variables, just like <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html'>Database#execute</a> and friends.</p></div>
|
188
|
-
<a name='
|
188
|
+
<a name='538598910'></a>
|
189
189
|
<div class='faq-title'>How do I prepare a statement for repeated execution?</div>
|
190
190
|
<div class='faq-answer'><p>If the same statement is going to be executed repeatedly, you can speed things up a bit by <em>preparing</em> the statement. You do this via the <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html'>Database#prepare</a> method. It returns a <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Statement.html'>Statement</a> object, and you can then invoke #execute on that to get the <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/ResultSet.html'>ResultSet</a>:</p>
|
191
191
|
|
@@ -200,7 +200,7 @@
|
|
200
200
|
</pre>
|
201
201
|
|
202
202
|
<p>This is made more useful by the ability to bind variables to placeholders via the <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Statement.html'>Statement#bind_param</a> and <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Statement.html'>Statement#bind_params</a> methods. (See the next <span class="caps">FAQ</span> for details.)</p></div>
|
203
|
-
<a name='
|
203
|
+
<a name='538598870'></a>
|
204
204
|
<div class='faq-title'>How do I use placeholders in an <span class="caps">SQL</span> statement?</div>
|
205
205
|
<div class='faq-answer'><p>Placeholders in an <span class="caps">SQL</span> statement take any of the following formats:</p>
|
206
206
|
|
@@ -258,7 +258,7 @@
|
|
258
258
|
|
259
259
|
stmt.bind_params( "value", "name" => "bob" )
|
260
260
|
</pre></div>
|
261
|
-
<a name='
|
261
|
+
<a name='538598830'></a>
|
262
262
|
<div class='faq-title'>How do I discover metadata about a query?</div>
|
263
263
|
<div class='faq-answer'><p>If you ever want to know the names or types of the columns in a result set, you can do it in several ways.</p>
|
264
264
|
|
@@ -287,7 +287,7 @@
|
|
287
287
|
p stmt.columns
|
288
288
|
p stmt.types
|
289
289
|
</pre></div>
|
290
|
-
<a name='
|
290
|
+
<a name='538598790'></a>
|
291
291
|
<div class='faq-title'>I’d like the rows to be indexible by column name.</div>
|
292
292
|
<div class='faq-answer'><p>By default, each row from a query is returned as an Array of values. This means that you can only obtain values by their index. Sometimes, however, you would like to obtain values by their column name.</p>
|
293
293
|
|
@@ -312,7 +312,7 @@
|
|
312
312
|
p row[1] == row['column2']
|
313
313
|
end
|
314
314
|
</pre></div>
|
315
|
-
<a name='
|
315
|
+
<a name='538598750'></a>
|
316
316
|
<div class='faq-title'>I’d like the values from a query to be the correct types, instead of String.</div>
|
317
317
|
<div class='faq-answer'><p>You can turn on “type translation” by setting <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html'>Database#type_translation</a> to true:</p>
|
318
318
|
|
@@ -347,14 +347,14 @@
|
|
347
347
|
obj = db.get_first_value( "select thing from objects where name='bob'" )
|
348
348
|
p obj == h
|
349
349
|
</pre></div>
|
350
|
-
<a name='
|
350
|
+
<a name='538598710'></a>
|
351
351
|
<div class='faq-title'>How do I do a <span class="caps">DDL </span>(insert, update, delete) statement?</div>
|
352
352
|
<div class='faq-answer'><p>You can actually do inserts, updates, and deletes in exactly the same way as selects, but in general the <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html'>Database#execute</a> method will be most convenient:</p>
|
353
353
|
|
354
354
|
<pre>
|
355
355
|
db.execute( "insert into table values ( ?, ? )", *bind_vars )
|
356
356
|
</pre></div>
|
357
|
-
<a name='
|
357
|
+
<a name='538598670'></a>
|
358
358
|
<div class='faq-title'>How do I execute multiple statements in a single string?</div>
|
359
359
|
<div class='faq-answer'><p>The standard query methods (<a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html'>Database#execute</a>, <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html'>Database#execute2</a>, <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html'>Database#query</a>, and <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Statement.html'>Statement#execute</a>) will only execute the first statement in the string that is given to them. Thus, if you have a string with multiple <span class="caps">SQL</span> statements, each separated by a string, you can’t use those methods to execute them all at once.</p>
|
360
360
|
|
@@ -376,7 +376,7 @@
|
|
376
376
|
</pre>
|
377
377
|
|
378
378
|
<p>Unlike the other query methods, <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html'>Database#execute_batch</a> accepts no block. It will also only ever return <ins>nil</ins>. Thus, it is really only suitable for batch processing of <span class="caps">DDL</span> statements.</p></div>
|
379
|
-
<a name='
|
379
|
+
<a name='538598630'></a>
|
380
380
|
<div class='faq-title'>How do I begin/end a transaction?</div>
|
381
381
|
<div class='faq-answer'><p>Use <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html'>Database#transaction</a> to start a transaction. If you give it a block, the block will be automatically committed at the end of the block, unless an exception was raised, in which case the transaction will be rolled back. (Never explicitly call <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html'>Database#commit</a> or <a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html'>Database#rollback</a> inside of a transaction block—you’ll get errors when the block terminates!)</p>
|
382
382
|
|
data/lib/sqlite/resultset.rb
CHANGED
@@ -57,8 +57,8 @@ module SQLite
|
|
57
57
|
# An array of the column types for this result set (may be empty)
|
58
58
|
attr_reader :types
|
59
59
|
|
60
|
-
# Create a new ResultSet attached to the given database
|
61
|
-
#
|
60
|
+
# Create a new ResultSet attached to the given database, using the
|
61
|
+
# given sql text.
|
62
62
|
def initialize( db, sql )
|
63
63
|
@db = db
|
64
64
|
@sql = sql
|
data/lib/sqlite/statement.rb
CHANGED
data/lib/sqlite/version.rb
CHANGED
data/test/lib/sqlite.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#--
|
2
|
+
# =============================================================================
|
3
|
+
# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
|
4
|
+
# All rights reserved.
|
5
|
+
#
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
7
|
+
# modification, are permitted provided that the following conditions are met:
|
8
|
+
#
|
9
|
+
# * Redistributions of source code must retain the above copyright notice,
|
10
|
+
# this list of conditions and the following disclaimer.
|
11
|
+
#
|
12
|
+
# * Redistributions in binary form must reproduce the above copyright
|
13
|
+
# notice, this list of conditions and the following disclaimer in the
|
14
|
+
# documentation and/or other materials provided with the distribution.
|
15
|
+
#
|
16
|
+
# * The names of its contributors may not be used to endorse or promote
|
17
|
+
# products derived from this software without specific prior written
|
18
|
+
# permission.
|
19
|
+
#
|
20
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
21
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
22
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
23
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
24
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
25
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
26
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
27
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
28
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
29
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
30
|
+
# =============================================================================
|
31
|
+
#++
|
32
|
+
|
33
|
+
require 'sqlite/database'
|
34
|
+
require 'sqlite/version'
|
@@ -0,0 +1,682 @@
|
|
1
|
+
#--
|
2
|
+
# =============================================================================
|
3
|
+
# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
|
4
|
+
# All rights reserved.
|
5
|
+
#
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
7
|
+
# modification, are permitted provided that the following conditions are met:
|
8
|
+
#
|
9
|
+
# * Redistributions of source code must retain the above copyright notice,
|
10
|
+
# this list of conditions and the following disclaimer.
|
11
|
+
#
|
12
|
+
# * Redistributions in binary form must reproduce the above copyright
|
13
|
+
# notice, this list of conditions and the following disclaimer in the
|
14
|
+
# documentation and/or other materials provided with the distribution.
|
15
|
+
#
|
16
|
+
# * The names of its contributors may not be used to endorse or promote
|
17
|
+
# products derived from this software without specific prior written
|
18
|
+
# permission.
|
19
|
+
#
|
20
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
21
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
22
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
23
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
24
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
25
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
26
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
27
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
28
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
29
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
30
|
+
# =============================================================================
|
31
|
+
#++
|
32
|
+
|
33
|
+
require 'base64'
|
34
|
+
require 'sqlite_api'
|
35
|
+
require 'sqlite/pragmas'
|
36
|
+
require 'sqlite/statement'
|
37
|
+
require 'sqlite/translator'
|
38
|
+
|
39
|
+
module SQLite
|
40
|
+
|
41
|
+
# The Database class encapsulates a single connection to a SQLite database.
|
42
|
+
# Its usage is very straightforward:
|
43
|
+
#
|
44
|
+
# require 'sqlite'
|
45
|
+
#
|
46
|
+
# db = SQLite::Database.new( "data.db" )
|
47
|
+
#
|
48
|
+
# db.execute( "select * from table" ) do |row|
|
49
|
+
# p row
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# db.close
|
53
|
+
#
|
54
|
+
# It wraps the lower-level methods provides by the API module, include
|
55
|
+
# includes the Pragmas module for access to various pragma convenience
|
56
|
+
# methods.
|
57
|
+
#
|
58
|
+
# The Database class provides type translation services as well, by which
|
59
|
+
# the SQLite data types (which are all represented as strings) may be
|
60
|
+
# converted into their corresponding types (as defined in the schemas
|
61
|
+
# for their tables). This translation only occurs when querying data from
|
62
|
+
# the database--insertions and updates are all still typeless.
|
63
|
+
#
|
64
|
+
# Furthermore, the Database class has been designed to work well with the
|
65
|
+
# ArrayFields module from Ara Howard. If you require the ArrayFields
|
66
|
+
# module before performing a query, and if you have not enabled results as
|
67
|
+
# hashes, then the results will all be indexible by field name.
|
68
|
+
class Database
|
69
|
+
include SQLite::Pragmas
|
70
|
+
|
71
|
+
# Opens the database contained in the given file. This just calls #new,
|
72
|
+
# passing 0 as the mode parameter. This returns the new Database
|
73
|
+
# instance.
|
74
|
+
def self.open( file_name )
|
75
|
+
new( file_name, 0 )
|
76
|
+
end
|
77
|
+
|
78
|
+
# Quotes the given string, making it safe to use in an SQL statement.
|
79
|
+
# It replaces all instances of the single-quote character with two
|
80
|
+
# single-quote characters. The modified string is returned.
|
81
|
+
def self.quote( string )
|
82
|
+
string.gsub( /'/, "''" )
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns a string that represents the serialization of the given object.
|
86
|
+
# The string may safely be used in an SQL statement.
|
87
|
+
def self.encode( object )
|
88
|
+
Base64.encode64( Marshal.dump( object ) ).strip
|
89
|
+
end
|
90
|
+
|
91
|
+
# Unserializes the object contained in the given string. The string must be
|
92
|
+
# one that was returned by #encode.
|
93
|
+
def self.decode( string )
|
94
|
+
Marshal.load( Base64.decode64( string ) )
|
95
|
+
end
|
96
|
+
|
97
|
+
# Return +true+ if the string is a valid (ie, parsable) SQL statement, and
|
98
|
+
# +false+ otherwise.
|
99
|
+
def self.complete?( string )
|
100
|
+
SQLite::API.complete( string )
|
101
|
+
end
|
102
|
+
|
103
|
+
# The low-level opaque database handle that this object wraps.
|
104
|
+
attr_reader :handle
|
105
|
+
|
106
|
+
# A boolean that indicates whether rows in result sets should be returned
|
107
|
+
# as hashes or not. By default, rows are returned as arrays.
|
108
|
+
attr_accessor :results_as_hash
|
109
|
+
|
110
|
+
# Create a new Database object that opens the given file. The mode
|
111
|
+
# parameter has no meaning yet, and may be omitted. If the file does not
|
112
|
+
# exist, it will be created if possible.
|
113
|
+
#
|
114
|
+
# By default, the new database will return result rows as arrays
|
115
|
+
# (#results_as_hash) and has type translation disabled (#type_translation=).
|
116
|
+
def initialize( file_name, mode=0 )
|
117
|
+
@handle = SQLite::API.open( file_name, mode )
|
118
|
+
@closed = false
|
119
|
+
@results_as_hash = false
|
120
|
+
@type_translation = false
|
121
|
+
@translator = nil
|
122
|
+
end
|
123
|
+
|
124
|
+
# Return the type translator employed by this database instance. Each
|
125
|
+
# database instance has its own type translator; this allows for different
|
126
|
+
# type handlers to be installed in each instance without affecting other
|
127
|
+
# instances. Furthermore, the translators are instantiated lazily, so that
|
128
|
+
# if a database does not use type translation, it will not be burdened by
|
129
|
+
# the overhead of a useless type translator. (See the Translator class.)
|
130
|
+
def translator
|
131
|
+
@translator ||= Translator.new
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns +true+ if type translation is enabled for this database, or
|
135
|
+
# +false+ otherwise.
|
136
|
+
def type_translation
|
137
|
+
@type_translation
|
138
|
+
end
|
139
|
+
|
140
|
+
# Enable or disable type translation for this database.
|
141
|
+
def type_translation=( mode )
|
142
|
+
@type_translation = mode
|
143
|
+
end
|
144
|
+
|
145
|
+
# Closes this database. No checks are done to ensure that a database is not
|
146
|
+
# closed more than once, and closing a database more than once can be
|
147
|
+
# catastrophic.
|
148
|
+
def close
|
149
|
+
SQLite::API.close( @handle )
|
150
|
+
@closed = true
|
151
|
+
end
|
152
|
+
|
153
|
+
# Returns +true+ if this database instance has been closed (see #close).
|
154
|
+
def closed?
|
155
|
+
@closed
|
156
|
+
end
|
157
|
+
|
158
|
+
# Returns a Statement object representing the given SQL. This does not
|
159
|
+
# execute the statement; it merely prepares the statement for execution.
|
160
|
+
def prepare( sql )
|
161
|
+
Statement.new( self, sql )
|
162
|
+
end
|
163
|
+
|
164
|
+
# Executes the given SQL statement. If additional parameters are given,
|
165
|
+
# they are treated as bind variables, and are bound to the placeholders in
|
166
|
+
# the query.
|
167
|
+
#
|
168
|
+
# Each placeholder must match one of the following formats:
|
169
|
+
#
|
170
|
+
# * <tt>?</tt>
|
171
|
+
# * <tt>?nnn</tt>
|
172
|
+
# * <tt>:word</tt>
|
173
|
+
# * <tt>:word:</tt>
|
174
|
+
#
|
175
|
+
# where _nnn_ is an integer value indicating the index of the bind
|
176
|
+
# variable to be bound at that position, and _word_ is an alphanumeric
|
177
|
+
# identifier for that placeholder. For "<tt>?</tt>", an index is
|
178
|
+
# automatically assigned of one greater than the previous index used
|
179
|
+
# (or 1, if it is the first).
|
180
|
+
#
|
181
|
+
# Note that if any of the values passed to this are hashes, then the
|
182
|
+
# key/value pairs are each bound separately, with the key being used as
|
183
|
+
# the name of the placeholder to bind the value to.
|
184
|
+
#
|
185
|
+
# The block is optional. If given, it will be invoked for each row returned
|
186
|
+
# by the query. Otherwise, any results are accumulated into an array and
|
187
|
+
# returned wholesale.
|
188
|
+
#
|
189
|
+
# See also #execute2, #execute_batch and #query for additional ways of
|
190
|
+
# executing statements.
|
191
|
+
def execute( sql, *bind_vars )
|
192
|
+
stmt = prepare( sql )
|
193
|
+
stmt.bind_params( *bind_vars )
|
194
|
+
result = stmt.execute
|
195
|
+
begin
|
196
|
+
if block_given?
|
197
|
+
result.each { |row| yield row }
|
198
|
+
else
|
199
|
+
return result.inject( [] ) { |arr,row| arr << row; arr }
|
200
|
+
end
|
201
|
+
ensure
|
202
|
+
result.close
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Executes the given SQL statement, exactly as with #execute. However, the
|
207
|
+
# first row returned (either via the block, or in the returned array) is
|
208
|
+
# always the names of the columns. Subsequent rows correspond to the data
|
209
|
+
# from the result set.
|
210
|
+
#
|
211
|
+
# Thus, even if the query itself returns no rows, this method will always
|
212
|
+
# return at least one row--the names of the columns.
|
213
|
+
#
|
214
|
+
# See also #execute, #execute_batch and #query for additional ways of
|
215
|
+
# executing statements.
|
216
|
+
def execute2( sql, *bind_vars )
|
217
|
+
stmt = prepare( sql )
|
218
|
+
stmt.bind_params( *bind_vars )
|
219
|
+
result = stmt.execute
|
220
|
+
begin
|
221
|
+
if block_given?
|
222
|
+
yield result.columns
|
223
|
+
result.each { |row| yield row }
|
224
|
+
else
|
225
|
+
return result.inject( [ result.columns ] ) { |arr,row| arr << row; arr }
|
226
|
+
end
|
227
|
+
ensure
|
228
|
+
result.close
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
# Executes all SQL statements in the given string. By contrast, the other
|
233
|
+
# means of executing queries will only execute the first statement in the
|
234
|
+
# string, ignoring all subsequent statements. This will execute each one
|
235
|
+
# in turn. The same bind parameters, if given, will be applied to each
|
236
|
+
# statement.
|
237
|
+
#
|
238
|
+
# This always returns +nil+, making it unsuitable for queries that return
|
239
|
+
# rows.
|
240
|
+
def execute_batch( sql, *bind_vars )
|
241
|
+
loop do
|
242
|
+
stmt = prepare( sql )
|
243
|
+
stmt.bind_params *bind_vars
|
244
|
+
stmt.execute
|
245
|
+
sql = stmt.remainder
|
246
|
+
break if sql.length < 1
|
247
|
+
end
|
248
|
+
nil
|
249
|
+
end
|
250
|
+
|
251
|
+
# This does like #execute and #execute2 (binding variables and so forth),
|
252
|
+
# but instead of yielding each row from the result set, this will yield the
|
253
|
+
# ResultSet instance itself (q.v.). If no block is given, the ResultSet
|
254
|
+
# instance will be returned.
|
255
|
+
def query( sql, *bind_vars, &block ) # :yields: result_set
|
256
|
+
stmt = prepare( sql )
|
257
|
+
stmt.bind_params( *bind_vars )
|
258
|
+
stmt.execute( &block )
|
259
|
+
end
|
260
|
+
|
261
|
+
# A convenience method for obtaining the first row of a result set, and
|
262
|
+
# discarding all others. It is otherwise identical to #execute.
|
263
|
+
#
|
264
|
+
# See also #get_first_value.
|
265
|
+
def get_first_row( sql, *bind_vars )
|
266
|
+
execute( sql, *bind_vars ) { |row| return row }
|
267
|
+
nil
|
268
|
+
end
|
269
|
+
|
270
|
+
# A convenience method for obtaining the first value of the first row of a
|
271
|
+
# result set, and discarding all other values and rows. It is otherwise
|
272
|
+
# identical to #execute.
|
273
|
+
#
|
274
|
+
# See also #get_first_row.
|
275
|
+
def get_first_value( sql, *bind_vars )
|
276
|
+
execute( sql, *bind_vars ) { |row| return row[0] }
|
277
|
+
nil
|
278
|
+
end
|
279
|
+
|
280
|
+
# Obtains the unique row ID of the last row to be inserted by this Database
|
281
|
+
# instance.
|
282
|
+
def last_insert_row_id
|
283
|
+
SQLite::API.last_insert_row_id( @handle )
|
284
|
+
end
|
285
|
+
|
286
|
+
# Returns the number of changes made to this database instance by the last
|
287
|
+
# operation performed. Note that a "delete from table" without a where
|
288
|
+
# clause will not affect this value.
|
289
|
+
def changes
|
290
|
+
SQLite::API.changes( @handle )
|
291
|
+
end
|
292
|
+
|
293
|
+
# Interrupts the currently executing operation, causing it to abort.
|
294
|
+
def interrupt
|
295
|
+
SQLite::API.interrupt( @handle )
|
296
|
+
end
|
297
|
+
|
298
|
+
# Register a busy handler with this database instance. When a requested
|
299
|
+
# resource is busy, this handler will be invoked. If the handler returns
|
300
|
+
# +false+, the operation will be aborted; otherwise, the resource will
|
301
|
+
# be requested again.
|
302
|
+
#
|
303
|
+
# The handler will be invoked with the name of the resource that was
|
304
|
+
# busy, and the number of times it has been retried.
|
305
|
+
#
|
306
|
+
# See also #busy_timeout.
|
307
|
+
def busy_handler( &block ) # :yields: resource, retries
|
308
|
+
SQLite::API.busy_handler( @handle, block )
|
309
|
+
end
|
310
|
+
|
311
|
+
# Indicates that if a request for a resource terminates because that
|
312
|
+
# resource is busy, SQLite should wait for the indicated number of
|
313
|
+
# milliseconds before trying again. By default, SQLite does not retry
|
314
|
+
# busy resources. To restore the default behavior, send 0 as the
|
315
|
+
# +ms+ parameter.
|
316
|
+
#
|
317
|
+
# See also #busy_handler.
|
318
|
+
def busy_timeout( ms )
|
319
|
+
SQLite::API.busy_timeout( @handle, ms )
|
320
|
+
end
|
321
|
+
|
322
|
+
# Creates a new function for use in SQL statements. It will be added as
|
323
|
+
# +name+, with the given +arity+. (For variable arity functions, use
|
324
|
+
# -1 for the arity.) If +type+ is non-nil, it should either be an
|
325
|
+
# integer (indicating that the type of the function is always the
|
326
|
+
# type of the argument at that index), or one of the symbols
|
327
|
+
# <tt>:numeric</tt>, <tt>:text</tt>, <tt>:args</tt> (in which case
|
328
|
+
# the function is, respectively, numeric, textual, or the same type as
|
329
|
+
# its arguments).
|
330
|
+
#
|
331
|
+
# The block should accept at least one parameter--the FunctionProxy
|
332
|
+
# instance that wraps this function invocation--and any other
|
333
|
+
# arguments it needs (up to its arity).
|
334
|
+
#
|
335
|
+
# The block does not return a value directly. Instead, it will invoke
|
336
|
+
# the FunctionProxy#set_result method on the +func+ parameter and
|
337
|
+
# indicate the return value that way.
|
338
|
+
#
|
339
|
+
# Example:
|
340
|
+
#
|
341
|
+
# db.create_function( "maim", 1, :text ) do |func, value|
|
342
|
+
# if value.nil?
|
343
|
+
# func.set_value nil
|
344
|
+
# else
|
345
|
+
# func.set_value value.split(//).sort.join
|
346
|
+
# end
|
347
|
+
# end
|
348
|
+
#
|
349
|
+
# puts db.get_first_value( "select maim(name) from table" )
|
350
|
+
def create_function( name, arity, type=nil, &block ) # :yields: func, *args
|
351
|
+
case type
|
352
|
+
when :numeric
|
353
|
+
type = SQLite::API::NUMERIC
|
354
|
+
when :text
|
355
|
+
type = SQLite::API::TEXT
|
356
|
+
when :args
|
357
|
+
type = SQLite::API::ARGS
|
358
|
+
end
|
359
|
+
|
360
|
+
callback = proc do |func,*args|
|
361
|
+
begin
|
362
|
+
block.call( FunctionProxy.new( func ), *args )
|
363
|
+
rescue Exception => e
|
364
|
+
SQLite::API.set_result_error( func, "#{e.message} (#{e.class})" )
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
SQLite::API.create_function( @handle, name, arity, callback )
|
369
|
+
SQLite::API.function_type( @handle, name, type ) if type
|
370
|
+
|
371
|
+
self
|
372
|
+
end
|
373
|
+
|
374
|
+
# Creates a new aggregate function for use in SQL statements. Aggregate
|
375
|
+
# functions are functions that apply over every row in the result set,
|
376
|
+
# instead of over just a single row. (A very common aggregate function
|
377
|
+
# is the "count" function, for determining the number of rows that match
|
378
|
+
# a query.)
|
379
|
+
#
|
380
|
+
# The new function will be added as +name+, with the given +arity+. (For
|
381
|
+
# variable arity functions, use -1 for the arity.) If +type+ is non-nil,
|
382
|
+
# it should be a value as described in #create_function.
|
383
|
+
#
|
384
|
+
# The +step+ parameter must be a proc object that accepts as its first
|
385
|
+
# parameter a FunctionProxy instance (representing the function
|
386
|
+
# invocation), with any subsequent parameters (up to the function's arity).
|
387
|
+
# The +step+ callback will be invoked once for each row of the result set.
|
388
|
+
#
|
389
|
+
# The +finalize+ parameter must be a +proc+ object that accepts only a
|
390
|
+
# single parameter, the FunctionProxy instance representing the current
|
391
|
+
# function invocation. It should invoke FunctionProxy#set_result to
|
392
|
+
# store the result of the function.
|
393
|
+
#
|
394
|
+
# Example:
|
395
|
+
#
|
396
|
+
# step = proc do |func, value|
|
397
|
+
# func[ :total ] ||= 0
|
398
|
+
# func[ :total ] += ( value ? value.length : 0 )
|
399
|
+
# end
|
400
|
+
#
|
401
|
+
# finalize = proc do |func|
|
402
|
+
# func.set_result( func[ :total ] || 0 )
|
403
|
+
# end
|
404
|
+
#
|
405
|
+
# db.create_aggregate( "lengths", 1, step, finalize, :numeric )
|
406
|
+
#
|
407
|
+
# puts db.get_first_value( "select lengths(name) from table" )
|
408
|
+
#
|
409
|
+
# See also #create_aggregate_handler for a more object-oriented approach to
|
410
|
+
# aggregate functions.
|
411
|
+
def create_aggregate( name, arity, step, finalize, type=nil )
|
412
|
+
case type
|
413
|
+
when :numeric
|
414
|
+
type = SQLite::API::NUMERIC
|
415
|
+
when :text
|
416
|
+
type = SQLite::API::TEXT
|
417
|
+
when :args
|
418
|
+
type = SQLite::API::ARGS
|
419
|
+
end
|
420
|
+
|
421
|
+
step_callback = proc do |func,*args|
|
422
|
+
ctx = SQLite::API.aggregate_context( func )
|
423
|
+
unless ctx[:__error]
|
424
|
+
begin
|
425
|
+
step.call( FunctionProxy.new( func, ctx ), *args )
|
426
|
+
rescue Exception => e
|
427
|
+
ctx[:__error] = e
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
finalize_callback = proc do |func|
|
433
|
+
ctx = SQLite::API.aggregate_context( func )
|
434
|
+
unless ctx[:__error]
|
435
|
+
begin
|
436
|
+
finalize.call( FunctionProxy.new( func, ctx ) )
|
437
|
+
rescue Exception => e
|
438
|
+
SQLite::API.set_result_error( func, "#{e.message} (#{e.class})" )
|
439
|
+
end
|
440
|
+
else
|
441
|
+
e = ctx[:__error]
|
442
|
+
SQLite::API.set_result_error( func, "#{e.message} (#{e.class})" )
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
SQLite::API.create_aggregate( @handle, name, arity,
|
447
|
+
step_callback, finalize_callback )
|
448
|
+
|
449
|
+
SQLite::API.function_type( @handle, name, type ) if type
|
450
|
+
|
451
|
+
self
|
452
|
+
end
|
453
|
+
|
454
|
+
# This is another approach to creating an aggregate function (see
|
455
|
+
# #create_aggregate). Instead of explicitly specifying the name,
|
456
|
+
# callbacks, arity, and type, you specify a factory object
|
457
|
+
# (the "handler") that knows how to obtain all of that information. The
|
458
|
+
# handler should respond to the following messages:
|
459
|
+
#
|
460
|
+
# +function_type+:: corresponds to the +type+ parameter of
|
461
|
+
# #create_aggregate. This is an optional message, and if
|
462
|
+
# the handler does not respond to it, the function type
|
463
|
+
# will not be set for this function.
|
464
|
+
# +arity+:: corresponds to the +arity+ parameter of #create_aggregate. This
|
465
|
+
# message is optional, and if the handler does not respond to it,
|
466
|
+
# the function will have an arity of -1.
|
467
|
+
# +name+:: this is the name of the function. The handler _must_ implement
|
468
|
+
# this message.
|
469
|
+
# +new+:: this must be implemented by the handler. It should return a new
|
470
|
+
# instance of the object that will handle a specific invocation of
|
471
|
+
# the function.
|
472
|
+
#
|
473
|
+
# The handler instance (the object returned by the +new+ message, described
|
474
|
+
# above), must respond to the following messages:
|
475
|
+
#
|
476
|
+
# +step+:: this is the method that will be called for each step of the
|
477
|
+
# aggregate function's evaluation. It should implement the same
|
478
|
+
# signature as the +step+ callback for #create_aggregate.
|
479
|
+
# +finalize+:: this is the method that will be called to finalize the
|
480
|
+
# aggregate function's evaluation. It should implement the
|
481
|
+
# same signature as the +finalize+ callback for
|
482
|
+
# #create_aggregate.
|
483
|
+
#
|
484
|
+
# Example:
|
485
|
+
#
|
486
|
+
# class LengthsAggregateHandler
|
487
|
+
# def self.function_type; :numeric; end
|
488
|
+
# def self.arity; 1; end
|
489
|
+
#
|
490
|
+
# def initialize
|
491
|
+
# @total = 0
|
492
|
+
# end
|
493
|
+
#
|
494
|
+
# def step( ctx, name )
|
495
|
+
# @total += ( name ? name.length : 0 )
|
496
|
+
# end
|
497
|
+
#
|
498
|
+
# def finalize( ctx )
|
499
|
+
# ctx.set_result( @total )
|
500
|
+
# end
|
501
|
+
# end
|
502
|
+
#
|
503
|
+
# db.create_aggregate_handler( LengthsAggregateHandler )
|
504
|
+
# puts db.get_first_value( "select lengths(name) from A" )
|
505
|
+
def create_aggregate_handler( handler )
|
506
|
+
type = nil
|
507
|
+
arity = -1
|
508
|
+
|
509
|
+
type = handler.function_type if handler.respond_to?(:function_type)
|
510
|
+
arity = handler.arity if handler.respond_to?(:arity)
|
511
|
+
name = handler.name
|
512
|
+
|
513
|
+
case type
|
514
|
+
when :numeric
|
515
|
+
type = SQLite::API::NUMERIC
|
516
|
+
when :text
|
517
|
+
type = SQLite::API::TEXT
|
518
|
+
when :args
|
519
|
+
type = SQLite::API::ARGS
|
520
|
+
end
|
521
|
+
|
522
|
+
step = proc do |func,*args|
|
523
|
+
ctx = SQLite::API.aggregate_context( func )
|
524
|
+
unless ctx[ :__error ]
|
525
|
+
ctx[ :handler ] ||= handler.new
|
526
|
+
begin
|
527
|
+
ctx[ :handler ].step( FunctionProxy.new( func, ctx ), *args )
|
528
|
+
rescue Exception => e
|
529
|
+
ctx[ :__error ] = e
|
530
|
+
end
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
finalize = proc do |func|
|
535
|
+
ctx = SQLite::API.aggregate_context( func )
|
536
|
+
unless ctx[ :__error ]
|
537
|
+
ctx[ :handler ] ||= handler.new
|
538
|
+
begin
|
539
|
+
ctx[ :handler ].finalize( FunctionProxy.new( func, ctx ) )
|
540
|
+
rescue Exception => e
|
541
|
+
ctx[ :__error ] = e
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
if ctx[ :__error ]
|
546
|
+
e = ctx[ :__error ]
|
547
|
+
SQLite::API.set_result_error( func, "#{e.message} (#{e.class})" )
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
SQLite::API.create_aggregate( @handle, name, arity, step, finalize )
|
552
|
+
SQLite::API.function_type( @handle, name, type ) if type
|
553
|
+
|
554
|
+
self
|
555
|
+
end
|
556
|
+
|
557
|
+
# Begins a new transaction. Note that nested transactions are not allowed
|
558
|
+
# by SQLite, so attempting to nest a transaction will result in a runtime
|
559
|
+
# exception.
|
560
|
+
#
|
561
|
+
# If a block is given, the database instance is yielded to it, and the
|
562
|
+
# transaction is committed when the block terminates. If the block
|
563
|
+
# raises an exception, a rollback will be performed instead. Note that if
|
564
|
+
# a block is given, #commit and #rollback should never be called
|
565
|
+
# explicitly or you'll get an error when the block terminates.
|
566
|
+
#
|
567
|
+
# If a block is not given, it is the caller's responsibility to end the
|
568
|
+
# transaction explicitly, either by calling #commit, or by calling
|
569
|
+
# #rollback.
|
570
|
+
def transaction
|
571
|
+
execute "begin transaction"
|
572
|
+
@transaction_active = true
|
573
|
+
|
574
|
+
if block_given?
|
575
|
+
abort = false
|
576
|
+
begin
|
577
|
+
yield self
|
578
|
+
rescue Exception
|
579
|
+
abort = true
|
580
|
+
raise
|
581
|
+
ensure
|
582
|
+
abort and rollback or commit
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
true
|
587
|
+
end
|
588
|
+
|
589
|
+
# Commits the current transaction. If there is no current transaction,
|
590
|
+
# this will cause an error to be raised. This returns +true+, in order
|
591
|
+
# to allow it to be used in idioms like
|
592
|
+
# <tt>abort? and rollback or commit</tt>.
|
593
|
+
def commit
|
594
|
+
execute "commit transaction"
|
595
|
+
@transaction_active = false
|
596
|
+
true
|
597
|
+
end
|
598
|
+
|
599
|
+
# Rolls the current transaction back. If there is no current transaction,
|
600
|
+
# this will cause an error to be raised. This returns +true+, in order
|
601
|
+
# to allow it to be used in idioms like
|
602
|
+
# <tt>abort? and rollback or commit</tt>.
|
603
|
+
def rollback
|
604
|
+
execute "rollback transaction"
|
605
|
+
@transaction_active = false
|
606
|
+
true
|
607
|
+
end
|
608
|
+
|
609
|
+
# Returns +true+ if there is a transaction active, and +false+ otherwise.
|
610
|
+
def transaction_active?
|
611
|
+
@transaction_active
|
612
|
+
end
|
613
|
+
|
614
|
+
# A helper class for dealing with custom functions (see #create_function,
|
615
|
+
# #create_aggregate, and #create_aggregate_handler). It encapsulates the
|
616
|
+
# opaque function object that represents the current invocation. It also
|
617
|
+
# provides more convenient access to the API functions that operate on
|
618
|
+
# the function object.
|
619
|
+
#
|
620
|
+
# This class will almost _always_ be instantiated indirectly, by working
|
621
|
+
# with the create methods mentioned above.
|
622
|
+
class FunctionProxy
|
623
|
+
|
624
|
+
# Create a new FunctionProxy that encapsulates the given +func+ object.
|
625
|
+
# If context is non-nil, the functions context will be set to that. If
|
626
|
+
# it is non-nil, it must quack like a Hash. If it is nil, then none of
|
627
|
+
# the context functions will be available.
|
628
|
+
def initialize( func, context=nil )
|
629
|
+
@func = func
|
630
|
+
@context = context
|
631
|
+
end
|
632
|
+
|
633
|
+
# Set the result of the function to the given value. The function will
|
634
|
+
# then return this value.
|
635
|
+
def set_result( result )
|
636
|
+
SQLite::API.set_result( @func, result )
|
637
|
+
end
|
638
|
+
|
639
|
+
# Set the result of the function to the given error message, which must
|
640
|
+
# be a string. The function will then return that error.
|
641
|
+
def set_error( error )
|
642
|
+
SQLite::API.set_result_error( @func, error )
|
643
|
+
end
|
644
|
+
|
645
|
+
# (Only available to aggregate functions.) Returns the number of rows
|
646
|
+
# that the aggregate has processed so far. This will include the current
|
647
|
+
# row, and so will always return at least 1.
|
648
|
+
def count
|
649
|
+
ensure_aggregate!
|
650
|
+
SQLite::API.aggregate_count( @func )
|
651
|
+
end
|
652
|
+
|
653
|
+
# Returns the value with the given key from the context. This is only
|
654
|
+
# available to aggregate functions.
|
655
|
+
def []( key )
|
656
|
+
ensure_aggregate!
|
657
|
+
@context[ key ]
|
658
|
+
end
|
659
|
+
|
660
|
+
# Sets the value with the given key in the context. This is only
|
661
|
+
# available to aggregate functions.
|
662
|
+
def []=( key, value )
|
663
|
+
ensure_aggregate!
|
664
|
+
@context[ key ] = value
|
665
|
+
end
|
666
|
+
|
667
|
+
# A function for performing a sanity check, to ensure that the function
|
668
|
+
# being invoked is an aggregate function. This is implied by the
|
669
|
+
# existence of the context variable.
|
670
|
+
def ensure_aggregate!
|
671
|
+
unless @context
|
672
|
+
raise Exceptions::MisuseException, "function is not an aggregate"
|
673
|
+
end
|
674
|
+
end
|
675
|
+
private :ensure_aggregate!
|
676
|
+
|
677
|
+
end
|
678
|
+
|
679
|
+
end
|
680
|
+
|
681
|
+
end
|
682
|
+
|