sequel 3.10.0 → 3.11.0

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.
Files changed (87) hide show
  1. data/CHANGELOG +68 -0
  2. data/COPYING +1 -1
  3. data/README.rdoc +87 -27
  4. data/bin/sequel +2 -4
  5. data/doc/association_basics.rdoc +1383 -0
  6. data/doc/dataset_basics.rdoc +106 -0
  7. data/doc/opening_databases.rdoc +45 -16
  8. data/doc/querying.rdoc +210 -0
  9. data/doc/release_notes/3.11.0.txt +254 -0
  10. data/doc/virtual_rows.rdoc +217 -31
  11. data/lib/sequel/adapters/ado.rb +28 -12
  12. data/lib/sequel/adapters/ado/mssql.rb +33 -1
  13. data/lib/sequel/adapters/amalgalite.rb +13 -8
  14. data/lib/sequel/adapters/db2.rb +1 -2
  15. data/lib/sequel/adapters/dbi.rb +7 -4
  16. data/lib/sequel/adapters/do.rb +14 -15
  17. data/lib/sequel/adapters/do/postgres.rb +4 -5
  18. data/lib/sequel/adapters/do/sqlite.rb +9 -0
  19. data/lib/sequel/adapters/firebird.rb +5 -10
  20. data/lib/sequel/adapters/informix.rb +2 -4
  21. data/lib/sequel/adapters/jdbc.rb +111 -49
  22. data/lib/sequel/adapters/jdbc/mssql.rb +1 -2
  23. data/lib/sequel/adapters/jdbc/mysql.rb +11 -0
  24. data/lib/sequel/adapters/jdbc/oracle.rb +4 -7
  25. data/lib/sequel/adapters/jdbc/postgresql.rb +8 -1
  26. data/lib/sequel/adapters/jdbc/sqlite.rb +12 -0
  27. data/lib/sequel/adapters/mysql.rb +14 -5
  28. data/lib/sequel/adapters/odbc.rb +2 -4
  29. data/lib/sequel/adapters/odbc/mssql.rb +2 -4
  30. data/lib/sequel/adapters/openbase.rb +1 -2
  31. data/lib/sequel/adapters/oracle.rb +4 -8
  32. data/lib/sequel/adapters/postgres.rb +4 -11
  33. data/lib/sequel/adapters/shared/mssql.rb +22 -9
  34. data/lib/sequel/adapters/shared/mysql.rb +33 -30
  35. data/lib/sequel/adapters/shared/oracle.rb +0 -5
  36. data/lib/sequel/adapters/shared/postgres.rb +13 -11
  37. data/lib/sequel/adapters/shared/sqlite.rb +56 -10
  38. data/lib/sequel/adapters/sqlite.rb +16 -9
  39. data/lib/sequel/connection_pool.rb +6 -1
  40. data/lib/sequel/connection_pool/single.rb +1 -0
  41. data/lib/sequel/core.rb +6 -1
  42. data/lib/sequel/database.rb +52 -23
  43. data/lib/sequel/database/schema_generator.rb +6 -0
  44. data/lib/sequel/database/schema_methods.rb +5 -5
  45. data/lib/sequel/database/schema_sql.rb +1 -1
  46. data/lib/sequel/dataset.rb +4 -190
  47. data/lib/sequel/dataset/actions.rb +323 -1
  48. data/lib/sequel/dataset/features.rb +18 -2
  49. data/lib/sequel/dataset/graph.rb +7 -0
  50. data/lib/sequel/dataset/misc.rb +119 -0
  51. data/lib/sequel/dataset/mutation.rb +64 -0
  52. data/lib/sequel/dataset/prepared_statements.rb +6 -0
  53. data/lib/sequel/dataset/query.rb +272 -6
  54. data/lib/sequel/dataset/sql.rb +186 -394
  55. data/lib/sequel/model.rb +4 -2
  56. data/lib/sequel/model/associations.rb +31 -14
  57. data/lib/sequel/model/base.rb +32 -13
  58. data/lib/sequel/model/exceptions.rb +8 -4
  59. data/lib/sequel/model/plugins.rb +3 -13
  60. data/lib/sequel/plugins/active_model.rb +26 -7
  61. data/lib/sequel/plugins/instance_filters.rb +98 -0
  62. data/lib/sequel/plugins/many_through_many.rb +1 -1
  63. data/lib/sequel/plugins/optimistic_locking.rb +25 -9
  64. data/lib/sequel/version.rb +1 -1
  65. data/spec/adapters/mssql_spec.rb +26 -0
  66. data/spec/adapters/mysql_spec.rb +33 -4
  67. data/spec/adapters/postgres_spec.rb +24 -1
  68. data/spec/adapters/spec_helper.rb +6 -0
  69. data/spec/adapters/sqlite_spec.rb +28 -0
  70. data/spec/core/connection_pool_spec.rb +17 -5
  71. data/spec/core/database_spec.rb +101 -1
  72. data/spec/core/dataset_spec.rb +42 -4
  73. data/spec/core/schema_spec.rb +13 -0
  74. data/spec/extensions/active_model_spec.rb +34 -11
  75. data/spec/extensions/caching_spec.rb +2 -0
  76. data/spec/extensions/instance_filters_spec.rb +55 -0
  77. data/spec/extensions/spec_helper.rb +2 -0
  78. data/spec/integration/dataset_test.rb +12 -1
  79. data/spec/integration/model_test.rb +12 -0
  80. data/spec/integration/plugin_test.rb +61 -1
  81. data/spec/integration/schema_test.rb +14 -3
  82. data/spec/model/base_spec.rb +27 -0
  83. data/spec/model/plugins_spec.rb +0 -22
  84. data/spec/model/record_spec.rb +32 -1
  85. data/spec/model/spec_helper.rb +2 -0
  86. metadata +14 -3
  87. data/lib/sequel/dataset/convenience.rb +0 -326
@@ -1,29 +1,227 @@
1
1
  = Virtual Row Blocks
2
2
 
3
- Dataset methods filter, order, and select all take blocks that either yield
4
- instances of Sequel::SQL::VirtualRow (if the block takes an argument), or
5
- are evaluated in the context of an instance of Sequel::SQL::VirtualRow. These are referred to as
3
+ Dataset methods filter, order, and select all take blocks that are referred to as
6
4
  virtual row blocks. Many other dataset methods pass the blocks
7
5
  they are given into one of those three methods, so there are actually
8
- many Sequel methods that take virtual row blocks.
6
+ many Sequel::Dataset methods that take virtual row blocks.
9
7
 
10
- VirtualRow is a class that returns SQL::Indentifiers,
11
- SQL::QualifiedIdentifiers, SQL::Functions, or SQL::WindowFunctions depending on how it is
12
- called. This is best shown by example:
8
+ == Why Virtual Rows
9
+
10
+ Virtual Rows were created to work around the issue that some parts of
11
+ Sequel's standard DSL could not be used on ruby 1.9. For example, the
12
+ following Sequel code works on ruby 1.8, but not ruby 1.9:
13
+
14
+ dataset.filter(:a > :b[:c])
15
+ # WHERE a > b(c)
16
+
17
+ This code does not work on ruby 1.9 for two reasons. First, Symbol#>
18
+ (like other inequality methods) is already defined in ruby 1.9, so Sequel
19
+ does not override it to return an SQL inequality expression. Second, Symbol#[]
20
+ is already defined on ruby 1.9, so Sequel does not override it to return an
21
+ SQL function expression.
22
+
23
+ Prior to the introduction of virtual rows, the way to handle this was
24
+ to use the methods that work on both ruby 1.8 and ruby 1.9:
25
+
26
+ dataset.filter(:a.identifier > :b.sql_function(:c))
27
+ # WHERE a > b(c)
28
+
29
+ However, that code is a little verbose. The virtual row DSL makes such code
30
+ more concise:
31
+
32
+ dataset.filter{a > b(c)}
33
+
34
+ Another use of virtual rows is when you turn off Sequel's core extensions off
35
+ using the SEQUEL_NO_CORE_EXTENSIONS constant or environment variable. With
36
+ the core extensions turned off, much of the standard Sequel DSL is not
37
+ available. For example, you are no longer able to do:
38
+
39
+ dataset.filter(:a & (:b | ~:c))
40
+ # WHERE a AND (b OR NOT c)
41
+
42
+ Because Symbol#&, Symbol#| and Symbol#~ are not defined when the core
43
+ extensions are turned off. However, with virtual rows allow almost the same
44
+ syntax even without the core extensions:
45
+
46
+ dataset.filter{a & (b | ~c)}
47
+
48
+ == Regular Procs vs Instance Evaled Procs
49
+
50
+ Virtual row blocks behave differently depending on whether the block accepts
51
+ an argument. If the block accepts an argument, it is called with an instance
52
+ of Sequel::SQL::VirtualRow. If it does not accept an argument, it is
53
+ evaluated in the context of an instance of Sequel::SQL::VirtualRow.
13
54
 
14
55
  ds = DB[:items]
15
- ds.filter{column > 1} # column > 1
16
- ds.filter{table__column > 1} # table.column > 1
17
- ds.filter{function(1) > 1} # function(1) > 1
18
- ds.select{[column1, sum(column2).as(sum)]} # column1, sum(column2) AS sum
19
- ds.select{version{}} # version()
20
- ds.select{count(:*){}} # count(*)
21
- ds.select{count(:distinct, col1){}} # count(DISTINCT col1)
22
- ds.select{rank(:over){}} # rank() OVER ()
23
- ds.select{count(:over, :*=>true){}} # count(*) OVER ()
24
- ds.select{sum(:over, :args=>col1, :partition=>col2, :order=>col3){}} # sum(col1) OVER (PARTITION BY col2 ORDER BY col3)
25
-
26
- Basically, the rules are:
56
+ # Regular proc
57
+ ds.filter{|o| o.column > 1}
58
+ # WHERE column > 1
59
+
60
+ # Instance-evaled proc
61
+ ds.filter{column > 1}
62
+ # WHERE column > 1
63
+
64
+ If you aren't familiar with the difference between regular blocks and instance
65
+ evaled blocks, you should probably consult a general ruby reference, but briefly,
66
+ with regular procs, methods called without an explicit receiver inside the
67
+ proc call the method on the receiver in the surrounding scope, while instance
68
+ evaled procs call the method on the receiver of the instance_eval call. However,
69
+ in both cases, local variables available in the surrounding scope will be available
70
+ inside the proc. If that doesn't make sense, maybe this example will help:
71
+
72
+ def self.a
73
+ 42
74
+ end
75
+ b = 32
76
+
77
+ # Regular proc
78
+ ds.filter{|o| o.c > a - b}
79
+ # WHERE c > 10
80
+
81
+ # Instance-evaled proc
82
+ ds.filter{c > a - b}
83
+ # WHERE c > (a - 32)
84
+
85
+ There are two related differences here. First is the usage of "o.c" vs "c",
86
+ and second is the difference between the the use of "a". In the regular proc,
87
+ you couldn't call c without an explicit receiver in the proc, unless the self of the
88
+ surrounding scope responded to it. For a, note how ruby calls the method on
89
+ the receiver of the surrounding scope in the regular proc, which returns an integer,
90
+ and does the substraction before Sequel gets access to it. In the instance evaled
91
+ proc, calling a without a receiver calls the a method on the VirtualRow instance.
92
+ For b, note that it operates the same in both cases, as it is a local variable.
93
+
94
+ Basically, the choice for whether to use a regular proc or an instance evaled proc is
95
+ completely up to you. The same things can be accomplished with both.
96
+ Instance evaled procs tend to produce shorter code, but by modifying the scope
97
+ can be more difficult for a new user to understand. That being said, I usually
98
+ use instance evaled procs unless I need to call methods on the receiver of the
99
+ surrounding scope inside the proc.
100
+
101
+ == Local Variables vs Method Calls
102
+
103
+ If you have a method that accepts 0 arguments and has the same name as a local
104
+ variable, you can call it with () to differentiate the method call from the
105
+ local variable access. This is mostly useful in instance_evaled procs:
106
+
107
+ b = 32
108
+ ds.filter{b() > b}
109
+ # WHERE b > 32
110
+
111
+ == VirtualRow Methods
112
+
113
+ VirtualRow is a class that returns SQL::Identifiers, SQL::QualifiedIdentifiers,
114
+ SQL::Functions, or SQL::WindowFunctions depending on how it is called.
115
+
116
+ == SQL::Identifiers - Regular columns
117
+
118
+ SQL::Identifiers can be thought of as regular column references in SQL,
119
+ not qualified by any table. You get an SQL::Identifier if the method is called
120
+ without a block or arguments, and doesn't have a double underscore in the method
121
+ name:
122
+
123
+ ds.filter{|o| o.column > 1}
124
+ ds.filter{column > 1}
125
+ # WHERE column > 1
126
+
127
+ == SQL::QualifiedIdentifiers - Qualified columns
128
+
129
+ SQL::QualifiedIdentifiers can be thought of as column references in SQL that
130
+ are qualified to a specific table. You get an SQL::QualifiedIdentifier if
131
+ the method is called without a block or arguments, and has a double underscore
132
+ in the method name:
133
+
134
+ ds.filter{|o| o.table__column > 1}
135
+ ds.filter{table__column > 1}
136
+ # WHERE table.column > 1
137
+
138
+ Using the double underscore for SQL::QualifiedIdentifiers was done to make
139
+ usage very similar to using symbols, which also translate the double underscore
140
+ into a qualified column.
141
+
142
+ == SQL::Functions - SQL function calls
143
+
144
+ SQL::Functions can be thought of as function calls in SQL. You get a simple
145
+ function call if you call a method with arguments and without a block:
146
+
147
+ ds.filter{|o| o.function(1) > 1}
148
+ ds.filter{function(1) > 1}
149
+ # WHERE function(1) > 1
150
+
151
+ To call a SQL function with multiple arguments, just use those arguments in
152
+ your function call:
153
+
154
+ ds.filter{|o| o.function(1, o.a) > 1}
155
+ ds.filter{function(1, a) > 1}
156
+ # WHERE function(1, a) > 1
157
+
158
+ If the SQL function does not accept any arguments, you need to provide an empty
159
+ block to the method to distinguish it from a call that will produce an
160
+ SQL::Identifier:
161
+
162
+ ds.select{|o| o.version{}}
163
+ ds.select{version{}}
164
+ # SELECT version()
165
+
166
+ To use the SQL wildcard (*) as the sole argument in a function call (most often
167
+ used with the count function), you should provide :* as the sole argument to
168
+ the method, and provide an empty block to the method:
169
+
170
+ ds.select{|o| o.count(:*){}}
171
+ ds.select{count(:*){}}
172
+ # SELECT count(*)
173
+
174
+ To append the DISTINCT keyword before the method arguments, you need to make
175
+ :distinct the first argument of the method call, and provide an empty block to
176
+ the method:
177
+
178
+ ds.select{|o| o.count(:distinct, o.col1){}}
179
+ ds.select{count(:distinct, col1){}}
180
+ # SELECT count(DISTINCT col1)
181
+
182
+ To use multiple columns with the DISTINCT keyword, use multiple arguments in
183
+ the method call:
184
+
185
+ ds.select{|o| o.count(:distinct, o.col1, o.col2){}}
186
+ ds.select{count(:distinct, col1, col2){}}
187
+ # SELECT count(DISTINCT col1, col2)
188
+
189
+ == SQL::WindowFunctions - SQL window function calls
190
+
191
+ SQL::WindowFunctions can be thought of as calls to SQL window functions. Not
192
+ all databases support them, but they are very helpful for certain types of
193
+ queries. To use them, you need to make :over the first argument of the method
194
+ call, with an optional hash as the second argument: Here are some examples of use:
195
+
196
+ ds.select{|o| o.rank(:over){}}
197
+ ds.select{rank(:over){}}
198
+ # SELECT rank() OVER ()
199
+
200
+ ds.select{|o| o.count(:over, :*=>true){}}
201
+ ds.select{count(:over, :*=>true){}}
202
+ # SELECT count(*) OVER ()
203
+
204
+ ds.select{|o| o.sum(:over, :args=>o.col1, :partition=>o.col2, :order=>o.col3){}}
205
+ ds.select{sum(:over, :args=>col1, :partition=>col2, :order=>col3){}}
206
+ # SELECT sum(col1) OVER (PARTITION BY col2 ORDER BY col3)
207
+
208
+ == Returning multiple values
209
+
210
+ It's common when using select and order virtual row blocks to want to
211
+ return multiple values. If you want to do that, you just need to return a single
212
+ array:
213
+
214
+ ds.select{|o| [o.column1, o.sum(o.column2).as(o.sum)]}
215
+ ds.select{[column1, sum(column2).as(sum)]}
216
+ # SELECT column1, sum(column2) AS sum
217
+
218
+ Note that if you forget the array brackets, you'll end up with a syntax error:
219
+
220
+ # Invalid ruby syntax
221
+ ds.select{|o| o.column1, o.sum(o.column2).as(o.sum)}
222
+ ds.select{column1, sum(column2).as(sum)}
223
+
224
+ == Alternative Description of the VirtualRow method call rules
27
225
 
28
226
  * If a block is given:
29
227
  * The block is currently not called. This may change in a future version.
@@ -45,15 +243,3 @@ Basically, the rules are:
45
243
  SQL::QualifiedIdentifier with the table and column.
46
244
  * Otherwise, create an SQL::Identifier with the name of the
47
245
  method.
48
-
49
- If you use a virtual row block that doesn't take an argument,
50
- the block is instance_evaled, so you can't reference methods
51
- in the enclosing scope. If you need to call methods of the
52
- enclosing scope, you should assign the results to local variables
53
- before the block, or just make the block take an argument and use
54
- that. If you want to create identifiers or qualified identifiers
55
- with the same name as existing local variables, make sure ruby
56
- knows it is a method call instead of a local variable reference
57
- by not ommiting the parentheses at the end of the method call.
58
- If you want to return multiple arguments (common for select and
59
- order), have the block return an array of arguments.
@@ -16,16 +16,26 @@ module Sequel
16
16
  end
17
17
  end
18
18
 
19
- # Connect to the database. In addition to the usual database options,
19
+ # In addition to the usual database options,
20
20
  # the following options have an effect:
21
21
  #
22
- # * :command_timeout - Sets the time in seconds to wait while attempting
23
- # to execute a command before cancelling the attempt and generating
24
- # an error. Specifically, it sets the ADO CommandTimeout property.
25
- # If this property is not set, the default of 30 seconds is used.
26
- # * :conn_string - The full ADO connection string. If this is provided,
27
- # the usual options are ignored.
28
- # * :provider - Sets the Provider of this ADO connection (for example, "SQLOLEDB")
22
+ # :command_timeout :: Sets the time in seconds to wait while attempting
23
+ # to execute a command before cancelling the attempt and generating
24
+ # an error. Specifically, it sets the ADO CommandTimeout property.
25
+ # If this property is not set, the default of 30 seconds is used.
26
+ # :driver :: The driver to use in the ADO connection string. If not provided, a default
27
+ # of "SQL Server" is used.
28
+ # :conn_string :: The full ADO connection string. If this is provided,
29
+ # the usual options are ignored.
30
+ # :provider :: Sets the Provider of this ADO connection (for example, "SQLOLEDB").
31
+ # If you don't specify a provider, the default one used by WIN32OLE
32
+ # has major problems, such as creating a new native database connection
33
+ # for every query, which breaks things such as temporary tables.
34
+ #
35
+ # Pay special attention to the :provider option, as without specifying a provider,
36
+ # many things will be broken. The SQLNCLI10 provider appears to work well if you
37
+ # are connecting to Microsoft SQL Server, but it is not the default as that would
38
+ # break backwards compatability.
29
39
  def connect(server)
30
40
  opts = server_opts(server)
31
41
  s = opts[:conn_string] || "driver=#{opts[:driver]};server=#{opts[:host]};database=#{opts[:database]}#{";uid=#{opts[:user]};pwd=#{opts[:password]}" if opts[:user]}"
@@ -41,10 +51,9 @@ module Sequel
41
51
  end
42
52
 
43
53
  def execute(sql, opts={})
44
- log_info(sql)
45
54
  synchronize(opts[:server]) do |conn|
46
55
  begin
47
- r = conn.Execute(sql)
56
+ r = log_yield(sql){conn.Execute(sql)}
48
57
  yield(r) if block_given?
49
58
  rescue ::WIN32OLERuntimeError => e
50
59
  raise_error(e)
@@ -56,9 +65,11 @@ module Sequel
56
65
 
57
66
  private
58
67
 
59
- # The ADO adapter doesn't support transactions, since it appears not to
60
- # use a single native connection for each connection in the pool
68
+ # The ADO adapter's default provider doesn't support transactions, since it
69
+ # creates a new native connection for each query. So Sequel only attempts
70
+ # to use transactions if an explicit :provider is given.
61
71
  def _transaction(conn)
72
+ return super if opts[:provider]
62
73
  th = Thread.current
63
74
  begin
64
75
  @transactions << th
@@ -81,6 +92,11 @@ module Sequel
81
92
  s.getRows.transpose.each{|r| yield cols.inject({}){|m,c| m[c] = r.shift; m}} unless s.eof
82
93
  end
83
94
  end
95
+
96
+ # ADO returns nil for all for delete and update statements.
97
+ def provides_accurate_rows_matched?
98
+ false
99
+ end
84
100
  end
85
101
  end
86
102
  end
@@ -7,11 +7,36 @@ module Sequel
7
7
  module MSSQL
8
8
  module DatabaseMethods
9
9
  include Sequel::MSSQL::DatabaseMethods
10
+ # Query to use to get the number of rows affected by an update or
11
+ # delete query.
12
+ ROWS_AFFECTED = "SELECT @@ROWCOUNT AS AffectedRows"
10
13
 
11
14
  # Return instance of Sequel::ADO::MSSQL::Dataset with the given opts.
12
15
  def dataset(opts=nil)
13
16
  Sequel::ADO::MSSQL::Dataset.new(self, opts)
14
17
  end
18
+
19
+ # Just execute so it doesn't attempt to return the number of rows modified.
20
+ def execute_ddl(sql, opts={})
21
+ execute(sql, opts)
22
+ end
23
+ alias execute_insert execute_ddl
24
+
25
+ # Issue a separate query to get the rows modified. ADO appears to
26
+ # use pass by reference with an integer variable, which is obviously
27
+ # not supported directly in ruby, and I'm not aware of a workaround.
28
+ def execute_dui(sql, opts={})
29
+ return super unless @opts[:provider]
30
+ synchronize(opts[:server]) do |conn|
31
+ begin
32
+ log_yield(sql){conn.Execute(sql)}
33
+ res = log_yield(ROWS_AFFECTED){conn.Execute(ROWS_AFFECTED)}
34
+ res.getRows.transpose.each{|r| return r.shift}
35
+ rescue ::WIN32OLERuntimeError => e
36
+ raise_error(e)
37
+ end
38
+ end
39
+ end
15
40
  end
16
41
 
17
42
  class Dataset < ADO::Dataset
@@ -19,11 +44,18 @@ module Sequel
19
44
 
20
45
  # Use a nasty hack of multiple SQL statements in the same call and
21
46
  # having the last one return the most recently inserted id. This
22
- # is necessary as ADO doesn't provide a consistent native connection.
47
+ # is necessary as ADO's default :provider uses a separate native
48
+ # connection for each query.
23
49
  def insert(*values)
24
50
  return super if @opts[:sql]
25
51
  with_sql("SET NOCOUNT ON; #{insert_sql(*values)}; SELECT CAST(SCOPE_IDENTITY() AS INTEGER)").single_value
26
52
  end
53
+
54
+ # If you use a better :provider option for the database, you can get an
55
+ # accurate number of rows matched.
56
+ def provides_accurate_rows_matched?
57
+ !!db.opts[:provider]
58
+ end
27
59
  end
28
60
  end
29
61
  end
@@ -84,37 +84,42 @@ module Sequel
84
84
 
85
85
  # Run the given SQL with the given arguments. Returns nil.
86
86
  def execute_ddl(sql, opts={})
87
- _execute(sql, opts){|conn| conn.execute_batch(sql);}
87
+ _execute(sql, opts){|conn| log_yield(sql){conn.execute_batch(sql)}}
88
88
  nil
89
89
  end
90
90
 
91
91
  # Run the given SQL with the given arguments and return the number of changed rows.
92
92
  def execute_dui(sql, opts={})
93
- _execute(sql, opts){|conn| conn.execute_batch(sql); conn.row_changes}
93
+ _execute(sql, opts){|conn| log_yield(sql){conn.execute_batch(sql)}; conn.row_changes}
94
94
  end
95
95
 
96
96
  # Run the given SQL with the given arguments and return the last inserted row id.
97
97
  def execute_insert(sql, opts={})
98
- _execute(sql, opts){|conn| conn.execute_batch(sql); conn.last_insert_rowid}
98
+ _execute(sql, opts){|conn| log_yield(sql){conn.execute_batch(sql)}; conn.last_insert_rowid}
99
99
  end
100
100
 
101
101
  # Run the given SQL with the given arguments and yield each row.
102
- def execute(sql, opts={}, &block)
103
- _execute(sql, opts){|conn| conn.prepare(sql, &block)}
102
+ def execute(sql, opts={})
103
+ _execute(sql, opts) do |conn|
104
+ begin
105
+ yield(stmt = log_yield(sql){conn.prepare(sql)})
106
+ ensure
107
+ stmt.close if stmt
108
+ end
109
+ end
104
110
  end
105
111
 
106
112
  # Run the given SQL with the given arguments and return the first value of the first row.
107
113
  def single_value(sql, opts={})
108
- _execute(sql, opts){|conn| conn.first_value_from(sql)}
114
+ _execute(sql, opts){|conn| log_yield(sql){conn.first_value_from(sql)}}
109
115
  end
110
116
 
111
117
  private
112
118
 
113
- # Log the SQL and yield an available connection. Rescue
119
+ # Yield an available connection. Rescue
114
120
  # any Amalgalite::Errors and turn them into DatabaseErrors.
115
121
  def _execute(sql, opts)
116
122
  begin
117
- log_info(sql)
118
123
  synchronize(opts[:server]){|conn| yield conn}
119
124
  rescue ::Amalgalite::Error, ::Amalgalite::SQLite3::Error => e
120
125
  raise_error(e)
@@ -33,13 +33,12 @@ module Sequel
33
33
  end
34
34
 
35
35
  def execute(sql, opts={})
36
- log_info(sql)
37
36
  synchronize(opts[:server]) do |conn|
38
37
  rc, sth = SQLAllocHandle(SQL_HANDLE_STMT, @handle)
39
38
  check_error(rc, "Could not allocate statement")
40
39
 
41
40
  begin
42
- rc = SQLExecDirect(sth, sql)
41
+ rc = log_yield(sql){SQLExecDirect(sth, sql)}
43
42
  check_error(rc, "Could not execute statement")
44
43
 
45
44
  yield(sth) if block_given?