sequel 3.21.0 → 3.32.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 (234) hide show
  1. data/CHANGELOG +539 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +20 -6
  4. data/Rakefile +37 -19
  5. data/bin/sequel +1 -5
  6. data/doc/advanced_associations.rdoc +17 -0
  7. data/doc/association_basics.rdoc +293 -43
  8. data/doc/dataset_basics.rdoc +4 -4
  9. data/doc/dataset_filtering.rdoc +8 -0
  10. data/doc/mass_assignment.rdoc +54 -0
  11. data/doc/migration.rdoc +15 -538
  12. data/doc/model_hooks.rdoc +72 -27
  13. data/doc/opening_databases.rdoc +89 -50
  14. data/doc/prepared_statements.rdoc +40 -13
  15. data/doc/reflection.rdoc +8 -2
  16. data/doc/release_notes/3.22.0.txt +39 -0
  17. data/doc/release_notes/3.23.0.txt +172 -0
  18. data/doc/release_notes/3.24.0.txt +420 -0
  19. data/doc/release_notes/3.25.0.txt +88 -0
  20. data/doc/release_notes/3.26.0.txt +88 -0
  21. data/doc/release_notes/3.27.0.txt +82 -0
  22. data/doc/release_notes/3.28.0.txt +304 -0
  23. data/doc/release_notes/3.29.0.txt +459 -0
  24. data/doc/release_notes/3.30.0.txt +135 -0
  25. data/doc/release_notes/3.31.0.txt +146 -0
  26. data/doc/release_notes/3.32.0.txt +202 -0
  27. data/doc/schema_modification.rdoc +547 -0
  28. data/doc/sharding.rdoc +7 -1
  29. data/doc/testing.rdoc +129 -0
  30. data/doc/transactions.rdoc +137 -0
  31. data/doc/virtual_rows.rdoc +2 -2
  32. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  33. data/lib/sequel/adapters/ado.rb +40 -18
  34. data/lib/sequel/adapters/amalgalite.rb +17 -9
  35. data/lib/sequel/adapters/db2.rb +175 -86
  36. data/lib/sequel/adapters/dbi.rb +15 -15
  37. data/lib/sequel/adapters/do/mysql.rb +5 -7
  38. data/lib/sequel/adapters/do/postgres.rb +0 -5
  39. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  40. data/lib/sequel/adapters/do.rb +17 -36
  41. data/lib/sequel/adapters/firebird.rb +27 -208
  42. data/lib/sequel/adapters/ibmdb.rb +448 -0
  43. data/lib/sequel/adapters/informix.rb +6 -23
  44. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  45. data/lib/sequel/adapters/jdbc/db2.rb +52 -0
  46. data/lib/sequel/adapters/jdbc/derby.rb +260 -0
  47. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  48. data/lib/sequel/adapters/jdbc/h2.rb +66 -30
  49. data/lib/sequel/adapters/jdbc/hsqldb.rb +177 -0
  50. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  51. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  52. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  53. data/lib/sequel/adapters/jdbc/mysql.rb +11 -11
  54. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  55. data/lib/sequel/adapters/jdbc/postgresql.rb +9 -13
  56. data/lib/sequel/adapters/jdbc/sqlite.rb +2 -6
  57. data/lib/sequel/adapters/jdbc/sqlserver.rb +71 -0
  58. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  59. data/lib/sequel/adapters/jdbc.rb +231 -78
  60. data/lib/sequel/adapters/mock.rb +339 -0
  61. data/lib/sequel/adapters/mysql.rb +85 -184
  62. data/lib/sequel/adapters/mysql2.rb +26 -25
  63. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  64. data/lib/sequel/adapters/odbc/mssql.rb +1 -6
  65. data/lib/sequel/adapters/odbc.rb +28 -9
  66. data/lib/sequel/adapters/openbase.rb +3 -5
  67. data/lib/sequel/adapters/oracle.rb +355 -53
  68. data/lib/sequel/adapters/postgres.rb +194 -33
  69. data/lib/sequel/adapters/shared/access.rb +32 -9
  70. data/lib/sequel/adapters/shared/db2.rb +328 -0
  71. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  72. data/lib/sequel/adapters/shared/informix.rb +53 -0
  73. data/lib/sequel/adapters/shared/mssql.rb +278 -107
  74. data/lib/sequel/adapters/shared/mysql.rb +203 -65
  75. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  76. data/lib/sequel/adapters/shared/oracle.rb +237 -46
  77. data/lib/sequel/adapters/shared/postgres.rb +214 -82
  78. data/lib/sequel/adapters/shared/progress.rb +5 -8
  79. data/lib/sequel/adapters/shared/sqlite.rb +200 -50
  80. data/lib/sequel/adapters/sqlite.rb +29 -21
  81. data/lib/sequel/adapters/swift/mysql.rb +12 -7
  82. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  83. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  84. data/lib/sequel/adapters/swift.rb +5 -5
  85. data/lib/sequel/adapters/tinytds.rb +160 -19
  86. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  87. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  88. data/lib/sequel/ast_transformer.rb +190 -0
  89. data/lib/sequel/connection_pool/threaded.rb +3 -2
  90. data/lib/sequel/connection_pool.rb +1 -1
  91. data/lib/sequel/core.rb +68 -5
  92. data/lib/sequel/database/connecting.rb +5 -6
  93. data/lib/sequel/database/dataset.rb +4 -4
  94. data/lib/sequel/database/dataset_defaults.rb +59 -1
  95. data/lib/sequel/database/logging.rb +1 -1
  96. data/lib/sequel/database/misc.rb +104 -19
  97. data/lib/sequel/database/query.rb +163 -59
  98. data/lib/sequel/database/schema_generator.rb +17 -7
  99. data/lib/sequel/database/schema_methods.rb +59 -29
  100. data/lib/sequel/dataset/actions.rb +240 -76
  101. data/lib/sequel/dataset/features.rb +91 -8
  102. data/lib/sequel/dataset/graph.rb +38 -11
  103. data/lib/sequel/dataset/misc.rb +50 -23
  104. data/lib/sequel/dataset/mutation.rb +12 -7
  105. data/lib/sequel/dataset/prepared_statements.rb +59 -27
  106. data/lib/sequel/dataset/query.rb +236 -43
  107. data/lib/sequel/dataset/sql.rb +676 -267
  108. data/lib/sequel/dataset.rb +8 -0
  109. data/lib/sequel/exceptions.rb +4 -0
  110. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  111. data/lib/sequel/extensions/migration.rb +6 -4
  112. data/lib/sequel/extensions/named_timezones.rb +5 -0
  113. data/lib/sequel/extensions/query.rb +1 -1
  114. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  115. data/lib/sequel/extensions/to_dot.rb +95 -83
  116. data/lib/sequel/model/associations.rb +1081 -344
  117. data/lib/sequel/model/base.rb +420 -133
  118. data/lib/sequel/model/errors.rb +1 -1
  119. data/lib/sequel/model/exceptions.rb +5 -1
  120. data/lib/sequel/model.rb +20 -10
  121. data/lib/sequel/plugins/active_model.rb +13 -1
  122. data/lib/sequel/plugins/association_pks.rb +22 -4
  123. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  124. data/lib/sequel/plugins/composition.rb +43 -10
  125. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  126. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  127. data/lib/sequel/plugins/force_encoding.rb +6 -6
  128. data/lib/sequel/plugins/identity_map.rb +114 -7
  129. data/lib/sequel/plugins/many_through_many.rb +68 -7
  130. data/lib/sequel/plugins/nested_attributes.rb +65 -10
  131. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  132. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  133. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  134. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  135. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  136. data/lib/sequel/plugins/serialization.rb +98 -49
  137. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  138. data/lib/sequel/plugins/sharding.rb +21 -54
  139. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  140. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  141. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  142. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  143. data/lib/sequel/sql.rb +109 -52
  144. data/lib/sequel/timezones.rb +57 -38
  145. data/lib/sequel/version.rb +1 -1
  146. data/spec/adapters/db2_spec.rb +146 -0
  147. data/spec/adapters/mssql_spec.rb +135 -30
  148. data/spec/adapters/mysql_spec.rb +244 -212
  149. data/spec/adapters/oracle_spec.rb +67 -77
  150. data/spec/adapters/postgres_spec.rb +267 -78
  151. data/spec/adapters/spec_helper.rb +6 -5
  152. data/spec/adapters/sqlite_spec.rb +35 -21
  153. data/spec/core/connection_pool_spec.rb +71 -92
  154. data/spec/core/core_sql_spec.rb +21 -30
  155. data/spec/core/database_spec.rb +789 -512
  156. data/spec/core/dataset_spec.rb +1176 -1061
  157. data/spec/core/expression_filters_spec.rb +159 -42
  158. data/spec/core/mock_adapter_spec.rb +419 -0
  159. data/spec/core/object_graph_spec.rb +86 -114
  160. data/spec/core/schema_generator_spec.rb +3 -3
  161. data/spec/core/schema_spec.rb +298 -38
  162. data/spec/core/spec_helper.rb +6 -48
  163. data/spec/extensions/active_model_spec.rb +13 -0
  164. data/spec/extensions/association_autoreloading_spec.rb +1 -10
  165. data/spec/extensions/association_dependencies_spec.rb +2 -12
  166. data/spec/extensions/association_pks_spec.rb +66 -32
  167. data/spec/extensions/caching_spec.rb +23 -50
  168. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  169. data/spec/extensions/columns_introspection_spec.rb +91 -0
  170. data/spec/extensions/composition_spec.rb +18 -13
  171. data/spec/extensions/dataset_associations_spec.rb +199 -0
  172. data/spec/extensions/defaults_setter_spec.rb +64 -0
  173. data/spec/extensions/hook_class_methods_spec.rb +65 -91
  174. data/spec/extensions/identity_map_spec.rb +114 -22
  175. data/spec/extensions/instance_filters_spec.rb +11 -21
  176. data/spec/extensions/instance_hooks_spec.rb +53 -0
  177. data/spec/extensions/json_serializer_spec.rb +4 -5
  178. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  179. data/spec/extensions/list_spec.rb +17 -39
  180. data/spec/extensions/many_through_many_spec.rb +283 -250
  181. data/spec/extensions/migration_spec.rb +35 -32
  182. data/spec/extensions/named_timezones_spec.rb +23 -3
  183. data/spec/extensions/nested_attributes_spec.rb +97 -88
  184. data/spec/extensions/optimistic_locking_spec.rb +9 -20
  185. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  186. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  187. data/spec/extensions/prepared_statements_spec.rb +53 -0
  188. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  189. data/spec/extensions/pretty_table_spec.rb +1 -6
  190. data/spec/extensions/query_spec.rb +6 -0
  191. data/spec/extensions/rcte_tree_spec.rb +41 -43
  192. data/spec/extensions/schema_dumper_spec.rb +5 -8
  193. data/spec/extensions/schema_spec.rb +13 -21
  194. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  195. data/spec/extensions/serialization_spec.rb +81 -43
  196. data/spec/extensions/sharding_spec.rb +66 -140
  197. data/spec/extensions/single_table_inheritance_spec.rb +18 -29
  198. data/spec/extensions/spec_helper.rb +13 -61
  199. data/spec/extensions/sql_expr_spec.rb +20 -60
  200. data/spec/extensions/tactical_eager_loading_spec.rb +9 -19
  201. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  202. data/spec/extensions/timestamps_spec.rb +6 -6
  203. data/spec/extensions/to_dot_spec.rb +4 -7
  204. data/spec/extensions/touch_spec.rb +13 -14
  205. data/spec/extensions/tree_spec.rb +11 -26
  206. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  207. data/spec/extensions/update_primary_key_spec.rb +30 -24
  208. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  209. data/spec/extensions/validation_helpers_spec.rb +16 -35
  210. data/spec/extensions/xml_serializer_spec.rb +16 -4
  211. data/spec/integration/associations_test.rb +661 -21
  212. data/spec/integration/database_test.rb +4 -2
  213. data/spec/integration/dataset_test.rb +500 -67
  214. data/spec/integration/eager_loader_test.rb +19 -21
  215. data/spec/integration/model_test.rb +80 -1
  216. data/spec/integration/plugin_test.rb +367 -119
  217. data/spec/integration/prepared_statement_test.rb +222 -125
  218. data/spec/integration/schema_test.rb +217 -39
  219. data/spec/integration/spec_helper.rb +39 -30
  220. data/spec/integration/timezone_test.rb +38 -12
  221. data/spec/integration/transaction_test.rb +182 -5
  222. data/spec/integration/type_test.rb +17 -3
  223. data/spec/model/association_reflection_spec.rb +201 -7
  224. data/spec/model/associations_spec.rb +955 -662
  225. data/spec/model/base_spec.rb +275 -95
  226. data/spec/model/dataset_methods_spec.rb +7 -27
  227. data/spec/model/eager_loading_spec.rb +677 -676
  228. data/spec/model/hooks_spec.rb +349 -67
  229. data/spec/model/model_spec.rb +222 -168
  230. data/spec/model/plugins_spec.rb +24 -13
  231. data/spec/model/record_spec.rb +381 -218
  232. data/spec/model/spec_helper.rb +13 -71
  233. data/spec/model/validations_spec.rb +11 -0
  234. metadata +117 -73
@@ -11,16 +11,11 @@ module Sequel
11
11
  # :dataserver and :username options.
12
12
  def connect(server)
13
13
  opts = server_opts(server)
14
- opts[:dataserver] = opts[:host]
15
14
  opts[:username] = opts[:user]
15
+ set_mssql_unicode_strings
16
16
  TinyTds::Client.new(opts)
17
17
  end
18
18
 
19
- # Return instance of Sequel::TinyTDS::Dataset with the given options.
20
- def dataset(opts = nil)
21
- TinyTDS::Dataset.new(self, opts)
22
- end
23
-
24
19
  # Execute the given +sql+ on the server. If the :return option
25
20
  # is present, its value should be a method symbol that is called
26
21
  # on the TinyTds::Result object returned from executing the
@@ -32,13 +27,36 @@ module Sequel
32
27
  begin
33
28
  m = opts[:return]
34
29
  r = nil
35
- log_yield(sql) do
36
- r = c.execute(sql)
37
- return r.send(m) if m
30
+ if (args = opts[:arguments]) && !args.empty?
31
+ types = []
32
+ values = []
33
+ args.each_with_index do |(k, v), i|
34
+ v, type = ps_arg_type(v)
35
+ types << "@#{k} #{type}"
36
+ values << "@#{k} = #{v}"
37
+ end
38
+ case m
39
+ when :do
40
+ sql = "#{sql}; SELECT @@ROWCOUNT AS AffectedRows"
41
+ single_value = true
42
+ when :insert
43
+ sql = "#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"
44
+ single_value = true
45
+ end
46
+ sql = "EXEC sp_executesql N'#{c.escape(sql)}', N'#{c.escape(types.join(', '))}', #{values.join(', ')}"
47
+ log_yield(sql) do
48
+ r = c.execute(sql)
49
+ r.each{|row| return row.values.first} if single_value
50
+ end
51
+ else
52
+ log_yield(sql) do
53
+ r = c.execute(sql)
54
+ return r.send(m) if m
55
+ end
38
56
  end
39
57
  yield(r) if block_given?
40
58
  rescue TinyTds::Error => e
41
- raise_error(e)
59
+ raise_error(e, :disconnect=>!c.active?)
42
60
  ensure
43
61
  r.cancel if r && c.sqlsent?
44
62
  end
@@ -74,51 +92,174 @@ module Sequel
74
92
  super
75
93
  end
76
94
 
95
+ # tiny_tds uses TinyTds::Error as the base error class.
96
+ def database_error_classes
97
+ [TinyTds::Error]
98
+ end
99
+
77
100
  # Close the TinyTds::Client object.
78
101
  def disconnect_connection(c)
79
102
  c.close
80
103
  end
104
+
105
+ # Return true if the :conn argument is present and not active.
106
+ def disconnect_error?(e, opts)
107
+ super || (opts[:conn] && !opts[:conn].active?)
108
+ end
109
+
110
+ # Return a 2 element array with the literal value and type to use
111
+ # in the prepared statement call for the given value and connection.
112
+ def ps_arg_type(v)
113
+ case v
114
+ when Fixnum
115
+ [v, 'int']
116
+ when Bignum
117
+ [v, 'bigint']
118
+ when Float
119
+ [v, 'double precision']
120
+ when Numeric
121
+ [v, 'numeric']
122
+ when Time
123
+ if v.is_a?(SQLTime)
124
+ [literal(v), 'time']
125
+ else
126
+ [literal(v), 'datetime']
127
+ end
128
+ when DateTime
129
+ [literal(v), 'datetime']
130
+ when Date
131
+ [literal(v), 'date']
132
+ when nil
133
+ ['NULL', 'nvarchar(max)']
134
+ when true
135
+ ['1', 'int']
136
+ when false
137
+ ['0', 'int']
138
+ when SQL::Blob
139
+ [literal(v), 'varbinary(max)']
140
+ else
141
+ [literal(v), 'nvarchar(max)']
142
+ end
143
+ end
81
144
  end
82
145
 
83
146
  class Dataset < Sequel::Dataset
84
147
  include Sequel::MSSQL::DatasetMethods
148
+
149
+ Database::DatasetClass = self
150
+
151
+ # SQLite already supports named bind arguments, so use directly.
152
+ module ArgumentMapper
153
+ include Sequel::Dataset::ArgumentMapper
154
+
155
+ protected
156
+
157
+ # Return a hash with the same values as the given hash,
158
+ # but with the keys converted to strings.
159
+ def map_to_prepared_args(hash)
160
+ args = {}
161
+ hash.each{|k,v| args[k.to_s.gsub('.', '__')] = v}
162
+ args
163
+ end
164
+
165
+ private
166
+
167
+ # SQLite uses a : before the name of the argument for named
168
+ # arguments.
169
+ def prepared_arg(k)
170
+ LiteralString.new("@#{k.to_s.gsub('.', '__')}")
171
+ end
172
+
173
+ # Always assume a prepared argument.
174
+ def prepared_arg?(k)
175
+ true
176
+ end
177
+ end
178
+
179
+ # SQLite prepared statement uses a new prepared statement each time
180
+ # it is called, but it does use the bind arguments.
181
+ module PreparedStatementMethods
182
+ include ArgumentMapper
183
+
184
+ private
185
+
186
+ # Run execute_select on the database with the given SQL and the stored
187
+ # bind arguments.
188
+ def execute(sql, opts={}, &block)
189
+ super(prepared_sql, {:arguments=>bind_arguments}.merge(opts), &block)
190
+ end
191
+
192
+ # Same as execute, explicit due to intricacies of alias and super.
193
+ def execute_dui(sql, opts={}, &block)
194
+ super(prepared_sql, {:arguments=>bind_arguments}.merge(opts), &block)
195
+ end
196
+
197
+ # Same as execute, explicit due to intricacies of alias and super.
198
+ def execute_insert(sql, opts={}, &block)
199
+ super(prepared_sql, {:arguments=>bind_arguments}.merge(opts), &block)
200
+ end
201
+ end
85
202
 
86
203
  # Yield hashes with symbol keys, attempting to optimize for
87
204
  # various cases.
88
205
  def fetch_rows(sql)
89
206
  execute(sql) do |result|
90
207
  each_opts = {:cache_rows=>false}
91
- each_opts[:timezone] = :utc if Sequel.database_timezone == :utc
92
- offset = @opts[:offset]
93
- @columns = cols = result.fields.map{|c| output_identifier(c)}
94
- if identifier_output_method
208
+ each_opts[:timezone] = :utc if db.timezone == :utc
209
+ rn = row_number_column if offset = @opts[:offset]
210
+ columns = cols = result.fields.map{|c| output_identifier(c)}
211
+ if offset
212
+ rn = row_number_column
213
+ columns = columns.dup
214
+ columns.delete(rn)
215
+ end
216
+ @columns = columns
217
+ #if identifier_output_method
95
218
  each_opts[:as] = :array
96
219
  result.each(each_opts) do |r|
97
220
  h = {}
98
221
  cols.zip(r).each{|k, v| h[k] = v}
99
- h.delete(row_number_column) if offset
222
+ h.delete(rn) if rn
100
223
  yield h
101
224
  end
225
+ =begin
226
+ # Temporarily disable this optimization, as tiny_tds uses string keys
227
+ # if result.fields is called before result.each(:symbolize_keys=>true).
228
+ # See https://github.com/rails-sqlserver/tiny_tds/issues/57
102
229
  else
103
230
  each_opts[:symbolize_keys] = true
104
231
  if offset
105
232
  result.each(each_opts) do |r|
106
- r.delete(row_number_column) if offset
233
+ r.delete(rn) if rn
107
234
  yield r
108
235
  end
109
236
  else
110
- result.each(each_opts, &block)
237
+ result.each(each_opts, &Proc.new)
111
238
  end
112
239
  end
240
+ =end
113
241
  end
114
242
  self
115
243
  end
116
244
 
245
+ # Create a named prepared statement that is stored in the
246
+ # database (and connection) for reuse.
247
+ def prepare(type, name=nil, *values)
248
+ ps = to_prepared_statement(type, values)
249
+ ps.extend(PreparedStatementMethods)
250
+ if name
251
+ ps.prepared_statement_name = name
252
+ db.prepared_statements[name] = ps
253
+ end
254
+ ps
255
+ end
256
+
117
257
  private
118
258
 
119
259
  # Properly escape the given string +v+.
120
- def literal_string(v)
121
- db.synchronize{|c| "N'#{c.escape(v)}'"}
260
+ def literal_string_append(sql, v)
261
+ sql << 'N' if mssql_unicode_strings
262
+ sql << "'" << db.synchronize{|c| c.escape(v)} << "'"
122
263
  end
123
264
  end
124
265
  end
@@ -0,0 +1,66 @@
1
+ module Sequel
2
+ module EmulateOffsetWithRowNumber
3
+ # When a subselect that uses :offset is used in IN or NOT IN,
4
+ # use a nested subselect that only includes the first column
5
+ # instead of the ROW_NUMBER column added by the emulated offset support.
6
+ def complex_expression_sql_append(sql, op, args)
7
+ case op
8
+ when :IN, :"NOT IN"
9
+ ds = args.at(1)
10
+ if ds.is_a?(Sequel::Dataset) && ds.opts[:offset]
11
+ c = ds.opts[:select].first
12
+ case c
13
+ when Symbol
14
+ t, cl, a = split_symbol(c)
15
+ if a
16
+ c = SQL::Identifier.new(a)
17
+ elsif t
18
+ c = SQL::Identifier.new(cl)
19
+ end
20
+ when SQL::AliasedExpression
21
+ c = SQL::Identifier.new(c.aliaz)
22
+ when SQL::QualifiedIdentifier
23
+ c = SQL::Identifier.new(c.column)
24
+ end
25
+ super(sql, op, [args.at(0), ds.from_self.select(c)])
26
+ else
27
+ super
28
+ end
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ # Emulate OFFSET support with the ROW_NUMBER window function
35
+ #
36
+ # The implementation is ugly, cloning the current dataset and modifying
37
+ # the clone to add a ROW_NUMBER window function (and some other things),
38
+ # then using the modified clone in a subselect which is selected from.
39
+ #
40
+ # If offset is used, an order must be provided, because the use of ROW_NUMBER
41
+ # requires an order.
42
+ def select_sql
43
+ return super unless o = @opts[:offset]
44
+ raise(Error, "#{db.database_type} requires an order be provided if using an offset") unless order = @opts[:order]
45
+ dsa1 = dataset_alias(1)
46
+ rn = row_number_column
47
+ sql = @opts[:append_sql] || ''
48
+ subselect_sql_append(sql, unlimited.
49
+ unordered.
50
+ select_append{ROW_NUMBER(:over, :order=>order){}.as(rn)}.
51
+ from_self(:alias=>dsa1).
52
+ limit(@opts[:limit]).
53
+ where(SQL::Identifier.new(rn) > o).
54
+ order(rn))
55
+ sql
56
+ end
57
+
58
+ private
59
+
60
+ # This emulation adds an extra row number column that should be
61
+ # eliminated.
62
+ def offset_returns_row_number_column?
63
+ true
64
+ end
65
+ end
66
+ end
@@ -1,9 +1,6 @@
1
1
  module Sequel
2
2
  class Dataset
3
3
  module StoredProcedureMethods
4
- SQL_QUERY_TYPE = Hash.new{|h,k| h[k] = k}
5
- SQL_QUERY_TYPE[:first] = SQL_QUERY_TYPE[:all] = :select
6
-
7
4
  # The name of the stored procedure to call
8
5
  attr_accessor :sproc_name
9
6
 
@@ -44,14 +41,7 @@ module Sequel
44
41
  # ignored anyway).
45
42
  def sproc_type=(type)
46
43
  @sproc_type = type
47
- meta_def("#{sql_query_type}_sql"){|*a| ''}
48
- end
49
-
50
- private
51
-
52
- # The type of query (:select, :insert, :delete, :update).
53
- def sql_query_type
54
- SQL_QUERY_TYPE[@sproc_type]
44
+ @opts[:sql] = ''
55
45
  end
56
46
  end
57
47
 
@@ -0,0 +1,190 @@
1
+ module Sequel
2
+ # The +ASTTransformer+ class is designed to handle the abstract syntax trees
3
+ # that Sequel uses internally and produce modified copies of them. By itself
4
+ # it only produces a straight copy. It's designed to be subclassed and have
5
+ # subclasses returned modified copies of the specific nodes that need to
6
+ # be modified.
7
+ class ASTTransformer
8
+ # Return +obj+ or a potentially transformed version of it.
9
+ def transform(obj)
10
+ v(obj)
11
+ end
12
+
13
+ private
14
+
15
+ # Recursive version that handles all of Sequel's internal object types
16
+ # and produces copies of them.
17
+ def v(o)
18
+ case o
19
+ when Symbol, Numeric, String, Class, TrueClass, FalseClass, NilClass
20
+ o
21
+ when Array
22
+ o.map{|x| v(x)}
23
+ when Hash
24
+ h = {}
25
+ o.each{|k, val| h[v(k)] = v(val)}
26
+ h
27
+ when SQL::ComplexExpression
28
+ SQL::ComplexExpression.new(o.op, *v(o.args))
29
+ when SQL::Identifier
30
+ SQL::Identifier.new(v(o.value))
31
+ when SQL::QualifiedIdentifier
32
+ SQL::QualifiedIdentifier.new(v(o.table), v(o.column))
33
+ when SQL::OrderedExpression
34
+ SQL::OrderedExpression.new(v(o.expression), o.descending, :nulls=>o.nulls)
35
+ when SQL::AliasedExpression
36
+ SQL::AliasedExpression.new(v(o.expression), o.aliaz)
37
+ when SQL::CaseExpression
38
+ args = [v(o.conditions), v(o.default)]
39
+ args << v(o.expression) if o.expression?
40
+ SQL::CaseExpression.new(*args)
41
+ when SQL::Cast
42
+ SQL::Cast.new(v(o.expr), o.type)
43
+ when SQL::Function
44
+ SQL::Function.new(o.f, *v(o.args))
45
+ when SQL::Subscript
46
+ SQL::Subscript.new(v(o.f), v(o.sub))
47
+ when SQL::WindowFunction
48
+ SQL::WindowFunction.new(v(o.function), v(o.window))
49
+ when SQL::Window
50
+ opts = o.opts.dup
51
+ opts[:partition] = v(opts[:partition]) if opts[:partition]
52
+ opts[:order] = v(opts[:order]) if opts[:order]
53
+ SQL::Window.new(opts)
54
+ when SQL::PlaceholderLiteralString
55
+ args = if o.args.is_a?(Hash)
56
+ h = {}
57
+ o.args.each{|k,val| h[k] = v(val)}
58
+ h
59
+ else
60
+ v(o.args)
61
+ end
62
+ SQL::PlaceholderLiteralString.new(o.str, args, o.parens)
63
+ when SQL::JoinOnClause
64
+ SQL::JoinOnClause.new(v(o.on), o.join_type, v(o.table), v(o.table_alias))
65
+ when SQL::JoinUsingClause
66
+ SQL::JoinUsingClause.new(v(o.using), o.join_type, v(o.table), v(o.table_alias))
67
+ when SQL::JoinClause
68
+ SQL::JoinClause.new(o.join_type, v(o.table), v(o.table_alias))
69
+ else
70
+ o
71
+ end
72
+ end
73
+ end
74
+
75
+ # Handles qualifying existing datasets, so that unqualified columns
76
+ # in the dataset are qualified with a given table name.
77
+ class Qualifier < ASTTransformer
78
+ # Store the dataset to use as the basis for qualification,
79
+ # and the table used to qualify unqualified columns.
80
+ def initialize(ds, table)
81
+ @ds = ds
82
+ @table = table
83
+ end
84
+
85
+ private
86
+
87
+ # Turn <tt>SQL::Identifier</tt>s and symbols that aren't implicitly
88
+ # qualified into <tt>SQL::QualifiedIdentifier</tt>s. For symbols that
89
+ # are not implicitly qualified by are implicitly aliased, return an
90
+ # <tt>SQL::AliasedExpression</tt>s with a qualified version of the symbol.
91
+ def v(o)
92
+ case o
93
+ when Symbol
94
+ t, column, aliaz = @ds.send(:split_symbol, o)
95
+ if t
96
+ o
97
+ elsif aliaz
98
+ SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(@table, SQL::Identifier.new(column)), aliaz)
99
+ else
100
+ SQL::QualifiedIdentifier.new(@table, o)
101
+ end
102
+ when SQL::Identifier
103
+ SQL::QualifiedIdentifier.new(@table, o)
104
+ when SQL::QualifiedIdentifier, SQL::JoinClause
105
+ # Return these directly, so we don't accidentally qualify symbols in them.
106
+ o
107
+ else
108
+ super
109
+ end
110
+ end
111
+ end
112
+
113
+ # +Unbinder+ is used to take a dataset filter and return a modified version
114
+ # that unbinds already bound values and returns a dataset with bound value
115
+ # placeholders and a hash of bind values. You can then prepare the dataset
116
+ # and use the bound variables to execute it with the same values.
117
+ #
118
+ # This class only does a limited form of unbinding where the variable names
119
+ # and values can be associated unambiguously. The only cases it handles
120
+ # are <tt>SQL::ComplexExpression<tt> with an operator in +UNBIND_OPS+, a
121
+ # first argument that's an instance of a member of +UNBIND_KEY_CLASSES+, and
122
+ # a second argument that's an instance of a member of +UNBIND_VALUE_CLASSES+.
123
+ #
124
+ # So it can handle cases like:
125
+ #
126
+ # DB.filter(:a=>1).exclude(:b=>2).where{c > 3}
127
+ #
128
+ # But it cannot handle cases like:
129
+ #
130
+ # DB.filter(:a + 1 < 0)
131
+ class Unbinder < ASTTransformer
132
+ # The <tt>SQL::ComplexExpression<tt> operates that will be considered
133
+ # for transformation.
134
+ UNBIND_OPS = [:'=', :'!=', :<, :>, :<=, :>=]
135
+
136
+ # The key classes (first argument of the ComplexExpression) that will
137
+ # considered for transformation.
138
+ UNBIND_KEY_CLASSES = [Symbol, SQL::Identifier, SQL::QualifiedIdentifier]
139
+
140
+ # The value classes (second argument of the ComplexExpression) that
141
+ # will be considered for transformation.
142
+ UNBIND_VALUE_CLASSES = [Numeric, String, Date, Time]
143
+
144
+ # The hash of bind variables that were extracted from the dataset filter.
145
+ attr_reader :binds
146
+
147
+ # Intialize an empty +binds+ hash.
148
+ def initialize
149
+ @binds = {}
150
+ end
151
+
152
+ private
153
+
154
+ # Create a suitable bound variable key for the object, which should be
155
+ # an instance of one of the +UNBIND_KEY_CLASSES+.
156
+ def bind_key(obj)
157
+ case obj
158
+ when Symbol, String
159
+ obj
160
+ when SQL::Identifier
161
+ bind_key(obj.value)
162
+ when SQL::QualifiedIdentifier
163
+ :"#{bind_key(obj.table)}.#{bind_key(obj.column)}"
164
+ else
165
+ raise Error, "unhandled object in Sequel::Unbinder#bind_key: #{obj}"
166
+ end
167
+ end
168
+
169
+ # Handle <tt>SQL::ComplexExpression</tt> instances with suitable ops
170
+ # and arguments, substituting the value with a bound variable placeholder
171
+ # and assigning it an entry in the +binds+ hash with a matching key.
172
+ def v(o)
173
+ if o.is_a?(SQL::ComplexExpression) && UNBIND_OPS.include?(o.op)
174
+ l, r = o.args
175
+ if UNBIND_KEY_CLASSES.any?{|c| l.is_a?(c)} && UNBIND_VALUE_CLASSES.any?{|c| r.is_a?(c)} && !r.is_a?(LiteralString)
176
+ key = bind_key(l)
177
+ if (old = binds[key]) && old != r
178
+ raise UnbindDuplicate, "two different values for #{key.inspect}: #{[r, old].inspect}"
179
+ end
180
+ binds[key] = r
181
+ SQL::ComplexExpression.new(o.op, l, :"$#{key}")
182
+ else
183
+ super
184
+ end
185
+ else
186
+ super
187
+ end
188
+ end
189
+ end
190
+ end
@@ -83,9 +83,10 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
83
83
  end
84
84
  yield conn
85
85
  rescue Sequel::DatabaseDisconnectError
86
- @disconnection_proc.call(conn) if @disconnection_proc && conn
87
- @allocated.delete(t)
86
+ oconn = conn
88
87
  conn = nil
88
+ @disconnection_proc.call(oconn) if @disconnection_proc && oconn
89
+ @allocated.delete(t)
89
90
  raise
90
91
  ensure
91
92
  sync{release(t)} if conn
@@ -53,7 +53,7 @@ class Sequel::ConnectionPool
53
53
 
54
54
  # Return a connection pool class based on the given options.
55
55
  def connection_pool_class(opts)
56
- opts[:pool_class] || CONNECTION_POOL_MAP[[!!opts[:single_threaded], !!opts[:servers]]]
56
+ CONNECTION_POOL_MAP[opts[:pool_class]] || opts[:pool_class] || CONNECTION_POOL_MAP[[!!opts[:single_threaded], !!opts[:servers]]]
57
57
  end
58
58
  end
59
59
  extend ClassMethods
data/lib/sequel/core.rb CHANGED
@@ -25,6 +25,7 @@
25
25
  module Sequel
26
26
  @convert_two_digit_years = true
27
27
  @datetime_class = Time
28
+ @empty_array_handle_nulls = true
28
29
  @virtual_row_instance_eval = true
29
30
  @require_thread = nil
30
31
 
@@ -54,6 +55,27 @@ module Sequel
54
55
  # days on +DateTime+).
55
56
  attr_accessor :datetime_class
56
57
 
58
+ # Sets whether or not to attempt to handle NULL values correctly when given
59
+ # an empty array. By default:
60
+ #
61
+ # DB[:a].filter(:b=>[])
62
+ # # SELECT * FROM a WHERE (b != b)
63
+ # DB[:a].exclude(:b=>[])
64
+ # # SELECT * FROM a WHERE (b = b)
65
+ #
66
+ # However, some databases (e.g. MySQL) will perform very poorly
67
+ # with this type of query. You can set this to false to get the
68
+ # following behavior:
69
+ #
70
+ # DB[:a].filter(:b=>[])
71
+ # # SELECT * FROM a WHERE 1 = 0
72
+ # DB[:a].exclude(:b=>[])
73
+ # # SELECT * FROM a WHERE 1 = 1
74
+ #
75
+ # This may not handle NULLs correctly, but can be much faster on
76
+ # some databases.
77
+ attr_accessor :empty_array_handle_nulls
78
+
57
79
  # For backwards compatibility, has no effect.
58
80
  attr_accessor :virtual_row_instance_eval
59
81
 
@@ -188,7 +210,7 @@ module Sequel
188
210
  # This is used to ensure that the files loaded are from the same version of
189
211
  # Sequel as this file.
190
212
  def self.require(files, subdir=nil)
191
- Array(files).each{|f| super("#{File.dirname(__FILE__)}/#{"#{subdir}/" if subdir}#{f}")}
213
+ Array(files).each{|f| super("#{File.dirname(__FILE__).untaint}/#{"#{subdir}/" if subdir}#{f}")}
192
214
  end
193
215
 
194
216
  # Set whether to set the single threaded mode for all databases by default. By default,
@@ -228,17 +250,58 @@ module Sequel
228
250
  end
229
251
  end
230
252
 
231
- # Converts the given +string+ into a +Time+ object.
253
+ # Converts the given +string+ into a <tt>Sequel::SQLTime</tt> object.
232
254
  #
233
- # Sequel.string_to_datetime('10:20:30') # Time.parse('10:20:30')
255
+ # v = Sequel.string_to_time('10:20:30') # Sequel::SQLTime.parse('10:20:30')
256
+ # DB.literal(v) # => '10:20:30'
234
257
  def self.string_to_time(string)
235
258
  begin
236
- Time.parse(string)
259
+ SQLTime.parse(string)
237
260
  rescue => e
238
261
  raise convert_exception_class(e, InvalidValue)
239
262
  end
240
263
  end
241
264
 
265
+ # Uses a transaction on all given databases with the given options. This:
266
+ #
267
+ # Sequel.transaction([DB1, DB2, DB3]){...}
268
+ #
269
+ # is equivalent to:
270
+ #
271
+ # DB1.transaction do
272
+ # DB2.transaction do
273
+ # DB3.transaction do
274
+ # ...
275
+ # end
276
+ # end
277
+ # end
278
+ #
279
+ # except that if Sequel::Rollback is raised by the block, the transaction is
280
+ # rolled back on all databases instead of just the last one.
281
+ #
282
+ # Note that this method cannot guarantee that all databases will commit or
283
+ # rollback. For example, if DB3 commits but attempting to commit on DB2
284
+ # fails (maybe because foreign key checks are deferred), there is no way
285
+ # to uncommit the changes on DB3. For that kind of support, you need to
286
+ # have two-phase commit/prepared transactions (which Sequel supports on
287
+ # some databases).
288
+ def self.transaction(dbs, opts={}, &block)
289
+ unless opts[:rollback]
290
+ rescue_rollback = true
291
+ opts = opts.merge(:rollback=>:reraise)
292
+ end
293
+ pr = dbs.reverse.inject(block){|bl, db| proc{db.transaction(opts, &bl)}}
294
+ if rescue_rollback
295
+ begin
296
+ pr.call
297
+ rescue Sequel::Rollback => e
298
+ nil
299
+ end
300
+ else
301
+ pr.call
302
+ end
303
+ end
304
+
242
305
  # Same as Sequel.require, but wrapped in a mutex in order to be thread safe.
243
306
  def self.ts_require(*args)
244
307
  check_requiring_thread{require(*args)}
@@ -292,7 +355,7 @@ module Sequel
292
355
 
293
356
  private_class_method :adapter_method, :def_adapter_method
294
357
 
295
- require(%w"metaprogramming sql connection_pool exceptions dataset database timezones version")
358
+ require(%w"metaprogramming sql connection_pool exceptions dataset database timezones ast_transformer version")
296
359
  require('core_sql') if !defined?(::SEQUEL_NO_CORE_EXTENSIONS) && !ENV.has_key?('SEQUEL_NO_CORE_EXTENSIONS')
297
360
 
298
361
  # Add the database adapter class methods to Sequel via metaprogramming