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.
@@ -0,0 +1,233 @@
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 'strscan'
34
+
35
+ module SQLite
36
+
37
+ # A ParsedStatement instance represents a tokenized version of an SQL
38
+ # statement. This makes it possible to do bind variable replacements multiple
39
+ # times, fairly efficiently.
40
+ #
41
+ # Within the SQLite interfaces, this is used only by the Statement class.
42
+ # However, it could be reused by other SQL-reliant classes easily.
43
+ class ParsedStatement
44
+
45
+ # This represents a textual token in an SQL statement. It is only used by
46
+ # ParsedStatement.
47
+ class Token # :nodoc:
48
+
49
+ # Create a new Token that encapsulates the given text.
50
+ def initialize( text )
51
+ @text = text
52
+ end
53
+
54
+ # Append the given text onto the the contents of this Token.
55
+ def <<( text )
56
+ @text << text.to_s
57
+ end
58
+
59
+ # Convert this Token into a string. The +vars+ parameter is ignored.
60
+ def to_s( vars=nil )
61
+ @text
62
+ end
63
+
64
+ end
65
+
66
+ # This represents a bind variable in a tokenized SQL stream. It is used
67
+ # only by ParsedStatement.
68
+ class BindVariable # :nodoc:
69
+
70
+ # Create a new BindVariable token encapsulating the given name. The
71
+ # name is used when looking up a bind variable to bind to the place
72
+ # holder represented by this token. The name may be either a Fixnum
73
+ # (in which case it represents an positional placeholder) or a
74
+ # a String (in which case it represents a named placeholder).
75
+ def initialize( name )
76
+ @name = name
77
+ end
78
+
79
+ # Convert the token to a string. If the +vars+ parameter is +nil+, then
80
+ # the string will be in the format <tt>?nnn</tt> (where _nnn_ is the
81
+ # index that was used to initialize this token). Otherwise, the +vars+
82
+ # parameter must be a hash, and the value bound to this token is the
83
+ # element with the key given to this token when it was created. If that
84
+ # element is +nil+, this will return the string "NULL". If the element
85
+ # is a String, then it will be quoted and escaped and returned.
86
+ # Otherwise, the "to_s" method of the element will be called and the
87
+ # result returned.
88
+ def to_s( vars=nil )
89
+ if vars.nil?
90
+ ":#{@name}"
91
+ else
92
+ var = vars[ @name ]
93
+ case var
94
+ when nil
95
+ "NULL"
96
+ when String
97
+ "'#{var.gsub(/'/,"''")}'"
98
+ else
99
+ var.to_s
100
+ end
101
+ end
102
+ end
103
+
104
+ end
105
+
106
+ # The text trailing the first recognized SQL statement that was parsed from
107
+ # the buffer given to this object. If there was no trailing SQL statement,
108
+ # this property will be the empty string.
109
+ attr_reader :trailing
110
+
111
+ # Create a new ParsedStatement. This will tokenize the given buffer. As an
112
+ # optimization, the tokenization is only performed if the string matches
113
+ # /[?:;]/, otherwise the string is used as-is.
114
+ def initialize( sql )
115
+ @bind_values = Hash.new
116
+
117
+ if sql.index( /[?:;]/ )
118
+ @tokens, @trailing = tokenize( sql )
119
+ else
120
+ @tokens, @trailing = [ Token.new(sql) ], ""
121
+ end
122
+ end
123
+
124
+ # Returns an array of the placeholders known to this statement. This will
125
+ # either be empty (if the statement has no placeholders), or will contain
126
+ # numbers (indexes) and strings (names).
127
+ def placeholders
128
+ @bind_values.keys
129
+ end
130
+
131
+ # Returns the SQL that was given to this parsed statement when it was
132
+ # created, with bind placeholders intact.
133
+ def sql
134
+ @tokens.inject( "" ) { |sql,tok| sql << tok.to_s }
135
+ end
136
+
137
+ # Returns the statement as an SQL string, with all placeholders bound to
138
+ # their corresponding values.
139
+ def to_s
140
+ @tokens.inject( "" ) { |sql,tok| sql << tok.to_s( @bind_values ) }
141
+ end
142
+
143
+ alias :to_str :to_s
144
+
145
+ # Binds the given parameters to the placeholders in the statement. It does
146
+ # this by iterating over each argument and calling #bind_param with the
147
+ # corresponding index (starting at 1). However, if any element is a hash,
148
+ # the hash is iterated through and #bind_param called for each key/value
149
+ # pair. Hash's do not increment the index.
150
+ def bind_params( *bind_vars )
151
+ index = 1
152
+ bind_vars.each do |value|
153
+ if value.is_a?( Hash )
154
+ value.each_pair { |key, value| bind_param( key, value ) }
155
+ else
156
+ bind_param index, value
157
+ index += 1
158
+ end
159
+ end
160
+ self
161
+ end
162
+
163
+ # Binds the given value to the placeholder indicated by +param+, which may
164
+ # be either a Fixnum or a String. If the indicated placeholder does not
165
+ # exist in the statement, this method does nothing.
166
+ def bind_param( param, value )
167
+ return unless @bind_values.has_key?( param )
168
+ @bind_values[ param ] = value
169
+ end
170
+
171
+ # Tokenizes the given SQL string, returning a tuple containing the array of
172
+ # tokens (optimized so that each text token contains the longest run of
173
+ # text possible), and any trailing text that follows the statement.
174
+ def tokenize( sql )
175
+ tokens = []
176
+
177
+ scanner = StringScanner.new( sql )
178
+ variable_index = 0
179
+
180
+ until scanner.eos?
181
+ tokens << " " unless tokens.empty? if scanner.scan( /\s+/ )
182
+ break if scanner.eos?
183
+
184
+ if scanner.scan( /;/ )
185
+ break
186
+ elsif scanner.scan( /---.*$/ ) || scanner.scan( %r{/\*.*?\*/}m )
187
+ # comments
188
+ next
189
+ elsif scanner.scan( /[-+*\/\w=<>!(),.]+/ )
190
+ tokens << Token.new( scanner.matched )
191
+ elsif scanner.scan( /['"]/ )
192
+ delim = scanner.matched
193
+ token = delim.dup
194
+ loop do
195
+ token << scanner.scan_until( /#{delim}/ )
196
+ match = scanner.matched
197
+ break if match.length % 2 == 1
198
+ end
199
+ tokens << Token.new( token )
200
+ elsif scanner.scan( /\?(\d+)?/ )
201
+ variable_index = ( scanner[1] ? scanner[1].to_i : variable_index+1 )
202
+ tokens << BindVariable.new( variable_index )
203
+ @bind_values[variable_index] = nil
204
+ elsif scanner.scan( /:(\w+):?/ )
205
+ name = scanner[1]
206
+ variable_index = name = name.to_i if name !~ /\D/
207
+ tokens << BindVariable.new( name )
208
+ @bind_values[name] = nil
209
+ else
210
+ raise "unknown token #{scanner.rest.inspect}"
211
+ end
212
+ end
213
+
214
+ # optimize the parsed list
215
+ tokens.pop while tokens.last == " "
216
+ optimized = []
217
+ tokens.each do |tok|
218
+ last = optimized.last
219
+ if tok.is_a?( BindVariable ) || last.nil? || last.is_a?( BindVariable )
220
+ tok = Token.new(tok) if tok == " "
221
+ optimized << tok
222
+ else
223
+ last << tok
224
+ end
225
+ end
226
+
227
+ return optimized, scanner.rest
228
+ end
229
+ private :tokenize
230
+
231
+ end
232
+
233
+ end
@@ -0,0 +1,236 @@
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
+ module SQLite
34
+
35
+ # This module is intended for inclusion solely by the Database class. It
36
+ # defines convenience methods for the various pragmas supported by SQLite.
37
+ #
38
+ # Two pragmas that have been intentionally excluded are SHOW_DATATYPES
39
+ # and EMPTY_RESULT_SETS, since these apply only to queries that use the
40
+ # SQLite "exec" function. The SQLite API does not employ that function,
41
+ # preferring instead the compile/step/finalize interface.
42
+ #
43
+ # However, if you really must have those pragmas, you can always execute
44
+ # a pragma as if it were an SQL statement.
45
+ #
46
+ # For a detailed description of these pragmas, see the SQLite documentation
47
+ # at http://sqlite.org/lang.html#pragma.
48
+ module Pragmas
49
+
50
+ # Returns +true+ or +false+ depending on the value of the named pragma.
51
+ def get_boolean_pragma( name )
52
+ get_first_value( "PRAGMA #{name}" ) != "0"
53
+ end
54
+ private :get_boolean_pragma
55
+
56
+ # Sets the given pragma to the given boolean value. The value itself
57
+ # may be +true+ or +false+, or any other commonly used string or
58
+ # integer that represents truth.
59
+ def set_boolean_pragma( name, mode )
60
+ case mode
61
+ when String
62
+ case mode.downcase
63
+ when "on", "yes", "true", "y", "t": mode = "'ON'"
64
+ when "off", "no", "false", "n", "f": mode = "'OFF'"
65
+ else
66
+ raise Exceptions::DatabaseException,
67
+ "unrecognized pragma parameter #{mode.inspect}"
68
+ end
69
+ when true, 1
70
+ mode = "ON"
71
+ when false, 0, nil
72
+ mode = "OFF"
73
+ else
74
+ raise Exceptions::DatabaseException,
75
+ "unrecognized pragma parameter #{mode.inspect}"
76
+ end
77
+
78
+ execute( "PRAGMA #{name}=#{mode}" )
79
+ end
80
+ private :set_boolean_pragma
81
+
82
+ # Requests the given pragma (and parameters), and if the block is given,
83
+ # each row of the result set will be yielded to it. Otherwise, the results
84
+ # are returned as an array.
85
+ def get_query_pragma( name, *parms, &block ) # :yields: row
86
+ if parms.empty?
87
+ execute( "PRAGMA #{name}", &block )
88
+ else
89
+ args = "'" + parms.join("','") + "'"
90
+ execute( "PRAGMA #{name}( #{args} )", &block )
91
+ end
92
+ end
93
+ private :get_query_pragma
94
+
95
+ # Return the value of the given pragma.
96
+ def get_enum_pragma( name )
97
+ get_first_value( "PRAGMA #{name}" )
98
+ end
99
+ private :get_enum_pragma
100
+
101
+ # Set the value of the given pragma to +mode+. The +mode+ parameter must
102
+ # conform to one of the values in the given +enum+ array. Each entry in
103
+ # the array is another array comprised of elements in the enumeration that
104
+ # have duplicate values. See #synchronous, #default_synchronous,
105
+ # #temp_store, and #default_temp_store for usage examples.
106
+ def set_enum_pragma( name, mode, enums )
107
+ match = enums.find { |p| p.find { |i| i.to_s.downcase == mode.to_s.downcase } }
108
+ raise Exceptions::DatabaseException,
109
+ "unrecognized #{name} #{mode.inspect}" unless match
110
+ execute( "PRAGMA #{name}='#{match.first.upcase}'" )
111
+ end
112
+ private :set_enum_pragma
113
+
114
+ # Returns the value of the given pragma as an integer.
115
+ def get_int_pragma( name )
116
+ get_first_value( "PRAGMA #{name}" ).to_i
117
+ end
118
+ private :get_int_pragma
119
+
120
+ # Set the value of the given pragma to the integer value of the +value+
121
+ # parameter.
122
+ def set_int_pragma( name, value )
123
+ execute( "PRAGMA #{name}=#{value.to_i}" )
124
+ end
125
+ private :set_int_pragma
126
+
127
+ # The enumeration of valid synchronous modes.
128
+ SYNCHRONOUS_MODES = [ [ 'full', 2 ], [ 'normal', 1 ], [ 'off', 0 ] ]
129
+
130
+ # The enumeration of valid temp store modes.
131
+ TEMP_STORE_MODES = [ [ 'default', 0 ], [ 'file', 1 ], [ 'memory', 2 ] ]
132
+
133
+ # Does an integrity check on the database. If the check fails, a
134
+ # SQLite::Exceptions::DatabaseException will be raised. Otherwise it
135
+ # returns silently.
136
+ def integrity_check
137
+ execute( "PRAGMA integrity_check" ) do |row|
138
+ raise Exceptions::DatabaseException, row[0] if row[0] != "ok"
139
+ end
140
+ end
141
+
142
+ def cache_size
143
+ get_int_pragma "cache_size"
144
+ end
145
+
146
+ def cache_size=( size )
147
+ set_int_pragma "cache_size", size
148
+ end
149
+
150
+ def default_cache_size
151
+ get_int_pragma "default_cache_size"
152
+ end
153
+
154
+ def default_cache_size=( size )
155
+ set_int_pragma "default_cache_size", size
156
+ end
157
+
158
+ def default_synchronous
159
+ get_enum_pragma "default_synchronous"
160
+ end
161
+
162
+ def default_synchronous=( mode )
163
+ set_enum_pragma "default_synchronous", mode, SYNCHRONOUS_MODES
164
+ end
165
+
166
+ def synchronous
167
+ get_enum_pragma "synchronous"
168
+ end
169
+
170
+ def synchronous=( mode )
171
+ set_enum_pragma "synchronous", mode, SYNCHRONOUS_MODES
172
+ end
173
+
174
+ def default_temp_store
175
+ get_enum_pragma "default_temp_store"
176
+ end
177
+
178
+ def default_temp_store=( mode )
179
+ set_enum_pragma "default_temp_store", mode, TEMP_STORE_MODES
180
+ end
181
+
182
+ def temp_store
183
+ get_enum_pragma "temp_store"
184
+ end
185
+
186
+ def temp_store=( mode )
187
+ set_enum_pragma "temp_store", mode, TEMP_STORE_MODES
188
+ end
189
+
190
+ def full_column_names
191
+ get_boolean_pragma "full_column_names"
192
+ end
193
+
194
+ def full_column_names=( mode )
195
+ set_boolean_pragma "full_column_names", mode
196
+ end
197
+
198
+ def parser_trace
199
+ get_boolean_pragma "parser_trace"
200
+ end
201
+
202
+ def parser_trace=( mode )
203
+ set_boolean_pragma "parser_trace", mode
204
+ end
205
+
206
+ def vdbe_trace
207
+ get_boolean_pragma "vdbe_trace"
208
+ end
209
+
210
+ def vdbe_trace=( mode )
211
+ set_boolean_pragma "vdbe_trace", mode
212
+ end
213
+
214
+ def database_list( &block ) # :yields: row
215
+ get_query_pragma "database_list", &block
216
+ end
217
+
218
+ def foreign_key_list( table, &block ) # :yields: row
219
+ get_query_pragma "foreign_key_list", table, &block
220
+ end
221
+
222
+ def index_info( index, &block ) # :yields: row
223
+ get_query_pragma "index_info", index, &block
224
+ end
225
+
226
+ def index_list( table, &block ) # :yields: row
227
+ get_query_pragma "index_list", table, &block
228
+ end
229
+
230
+ def table_info( table, &block ) # :yields: row
231
+ get_query_pragma "table_info", table, &block
232
+ end
233
+
234
+ end
235
+
236
+ end