sequel 3.21.0 → 3.29.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 (200) hide show
  1. data/CHANGELOG +413 -3
  2. data/README.rdoc +20 -6
  3. data/Rakefile +23 -16
  4. data/bin/sequel +1 -5
  5. data/doc/association_basics.rdoc +210 -43
  6. data/doc/dataset_basics.rdoc +4 -4
  7. data/doc/mass_assignment.rdoc +54 -0
  8. data/doc/migration.rdoc +15 -538
  9. data/doc/model_hooks.rdoc +72 -27
  10. data/doc/opening_databases.rdoc +86 -50
  11. data/doc/prepared_statements.rdoc +40 -13
  12. data/doc/reflection.rdoc +8 -2
  13. data/doc/release_notes/3.22.0.txt +39 -0
  14. data/doc/release_notes/3.23.0.txt +172 -0
  15. data/doc/release_notes/3.24.0.txt +420 -0
  16. data/doc/release_notes/3.25.0.txt +88 -0
  17. data/doc/release_notes/3.26.0.txt +88 -0
  18. data/doc/release_notes/3.27.0.txt +82 -0
  19. data/doc/release_notes/3.28.0.txt +304 -0
  20. data/doc/release_notes/3.29.0.txt +459 -0
  21. data/doc/schema_modification.rdoc +547 -0
  22. data/doc/sharding.rdoc +7 -1
  23. data/doc/testing.rdoc +115 -0
  24. data/doc/transactions.rdoc +137 -0
  25. data/doc/virtual_rows.rdoc +2 -2
  26. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  27. data/lib/sequel/adapters/ado.rb +40 -18
  28. data/lib/sequel/adapters/amalgalite.rb +15 -7
  29. data/lib/sequel/adapters/db2.rb +175 -86
  30. data/lib/sequel/adapters/dbi.rb +15 -15
  31. data/lib/sequel/adapters/do/mysql.rb +0 -5
  32. data/lib/sequel/adapters/do/postgres.rb +0 -5
  33. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  34. data/lib/sequel/adapters/do.rb +17 -36
  35. data/lib/sequel/adapters/firebird.rb +27 -208
  36. data/lib/sequel/adapters/ibmdb.rb +448 -0
  37. data/lib/sequel/adapters/informix.rb +6 -23
  38. data/lib/sequel/adapters/jdbc/as400.rb +5 -31
  39. data/lib/sequel/adapters/jdbc/db2.rb +44 -0
  40. data/lib/sequel/adapters/jdbc/derby.rb +217 -0
  41. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  42. data/lib/sequel/adapters/jdbc/h2.rb +26 -17
  43. data/lib/sequel/adapters/jdbc/hsqldb.rb +166 -0
  44. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  45. data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
  46. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  47. data/lib/sequel/adapters/jdbc/mysql.rb +9 -10
  48. data/lib/sequel/adapters/jdbc/oracle.rb +67 -25
  49. data/lib/sequel/adapters/jdbc/postgresql.rb +6 -11
  50. data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
  51. data/lib/sequel/adapters/jdbc/sqlserver.rb +41 -0
  52. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  53. data/lib/sequel/adapters/jdbc.rb +107 -38
  54. data/lib/sequel/adapters/mock.rb +315 -0
  55. data/lib/sequel/adapters/mysql.rb +78 -182
  56. data/lib/sequel/adapters/mysql2.rb +24 -23
  57. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  58. data/lib/sequel/adapters/odbc/mssql.rb +0 -5
  59. data/lib/sequel/adapters/odbc.rb +28 -9
  60. data/lib/sequel/adapters/openbase.rb +2 -4
  61. data/lib/sequel/adapters/oracle.rb +349 -51
  62. data/lib/sequel/adapters/postgres.rb +169 -21
  63. data/lib/sequel/adapters/shared/access.rb +21 -6
  64. data/lib/sequel/adapters/shared/db2.rb +288 -0
  65. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  66. data/lib/sequel/adapters/shared/informix.rb +45 -0
  67. data/lib/sequel/adapters/shared/mssql.rb +108 -65
  68. data/lib/sequel/adapters/shared/mysql.rb +56 -13
  69. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  70. data/lib/sequel/adapters/shared/oracle.rb +185 -34
  71. data/lib/sequel/adapters/shared/postgres.rb +107 -58
  72. data/lib/sequel/adapters/shared/progress.rb +0 -6
  73. data/lib/sequel/adapters/shared/sqlite.rb +168 -41
  74. data/lib/sequel/adapters/sqlite.rb +27 -18
  75. data/lib/sequel/adapters/swift/mysql.rb +9 -5
  76. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  77. data/lib/sequel/adapters/swift/sqlite.rb +6 -4
  78. data/lib/sequel/adapters/swift.rb +5 -5
  79. data/lib/sequel/adapters/tinytds.rb +152 -17
  80. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +63 -0
  81. data/lib/sequel/ast_transformer.rb +190 -0
  82. data/lib/sequel/connection_pool/threaded.rb +3 -2
  83. data/lib/sequel/connection_pool.rb +1 -1
  84. data/lib/sequel/core.rb +46 -5
  85. data/lib/sequel/database/connecting.rb +5 -6
  86. data/lib/sequel/database/dataset.rb +4 -4
  87. data/lib/sequel/database/dataset_defaults.rb +59 -1
  88. data/lib/sequel/database/logging.rb +1 -1
  89. data/lib/sequel/database/misc.rb +100 -19
  90. data/lib/sequel/database/query.rb +153 -58
  91. data/lib/sequel/database/schema_generator.rb +8 -5
  92. data/lib/sequel/database/schema_methods.rb +59 -29
  93. data/lib/sequel/dataset/actions.rb +194 -57
  94. data/lib/sequel/dataset/features.rb +81 -8
  95. data/lib/sequel/dataset/graph.rb +8 -7
  96. data/lib/sequel/dataset/misc.rb +50 -23
  97. data/lib/sequel/dataset/mutation.rb +5 -6
  98. data/lib/sequel/dataset/prepared_statements.rb +32 -15
  99. data/lib/sequel/dataset/query.rb +223 -42
  100. data/lib/sequel/dataset/sql.rb +58 -61
  101. data/lib/sequel/dataset.rb +8 -0
  102. data/lib/sequel/exceptions.rb +4 -0
  103. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  104. data/lib/sequel/extensions/migration.rb +6 -4
  105. data/lib/sequel/extensions/named_timezones.rb +5 -0
  106. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  107. data/lib/sequel/extensions/to_dot.rb +95 -83
  108. data/lib/sequel/model/associations.rb +997 -331
  109. data/lib/sequel/model/base.rb +386 -129
  110. data/lib/sequel/model/errors.rb +1 -1
  111. data/lib/sequel/model/exceptions.rb +5 -1
  112. data/lib/sequel/model.rb +15 -8
  113. data/lib/sequel/plugins/association_pks.rb +22 -4
  114. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  115. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  116. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  117. data/lib/sequel/plugins/force_encoding.rb +6 -6
  118. data/lib/sequel/plugins/identity_map.rb +114 -7
  119. data/lib/sequel/plugins/many_through_many.rb +65 -7
  120. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  121. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  122. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  123. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  124. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  125. data/lib/sequel/plugins/serialization.rb +6 -1
  126. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  127. data/lib/sequel/plugins/sharding.rb +12 -25
  128. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  129. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  130. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  131. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  132. data/lib/sequel/sql.rb +107 -51
  133. data/lib/sequel/timezones.rb +44 -35
  134. data/lib/sequel/version.rb +1 -1
  135. data/spec/adapters/db2_spec.rb +146 -0
  136. data/spec/adapters/mssql_spec.rb +65 -29
  137. data/spec/adapters/mysql_spec.rb +122 -123
  138. data/spec/adapters/oracle_spec.rb +48 -76
  139. data/spec/adapters/postgres_spec.rb +213 -61
  140. data/spec/adapters/spec_helper.rb +6 -5
  141. data/spec/adapters/sqlite_spec.rb +35 -21
  142. data/spec/core/connection_pool_spec.rb +71 -92
  143. data/spec/core/core_sql_spec.rb +20 -31
  144. data/spec/core/database_spec.rb +729 -508
  145. data/spec/core/dataset_spec.rb +980 -1044
  146. data/spec/core/expression_filters_spec.rb +159 -42
  147. data/spec/core/mock_adapter_spec.rb +378 -0
  148. data/spec/core/object_graph_spec.rb +48 -114
  149. data/spec/core/schema_generator_spec.rb +3 -3
  150. data/spec/core/schema_spec.rb +298 -38
  151. data/spec/core/spec_helper.rb +6 -48
  152. data/spec/extensions/association_pks_spec.rb +38 -0
  153. data/spec/extensions/class_table_inheritance_spec.rb +1 -1
  154. data/spec/extensions/columns_introspection_spec.rb +91 -0
  155. data/spec/extensions/dataset_associations_spec.rb +199 -0
  156. data/spec/extensions/defaults_setter_spec.rb +64 -0
  157. data/spec/extensions/identity_map_spec.rb +162 -0
  158. data/spec/extensions/instance_hooks_spec.rb +71 -0
  159. data/spec/extensions/many_through_many_spec.rb +195 -20
  160. data/spec/extensions/migration_spec.rb +17 -17
  161. data/spec/extensions/named_timezones_spec.rb +22 -2
  162. data/spec/extensions/nested_attributes_spec.rb +4 -0
  163. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  164. data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
  165. data/spec/extensions/prepared_statements_spec.rb +72 -0
  166. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  167. data/spec/extensions/schema_dumper_spec.rb +2 -2
  168. data/spec/extensions/schema_spec.rb +13 -21
  169. data/spec/extensions/serialization_modification_detection_spec.rb +37 -0
  170. data/spec/extensions/serialization_spec.rb +5 -8
  171. data/spec/extensions/single_table_inheritance_spec.rb +11 -0
  172. data/spec/extensions/spec_helper.rb +7 -1
  173. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  174. data/spec/extensions/to_dot_spec.rb +3 -5
  175. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  176. data/spec/extensions/xml_serializer_spec.rb +16 -4
  177. data/spec/integration/associations_test.rb +522 -21
  178. data/spec/integration/database_test.rb +4 -2
  179. data/spec/integration/dataset_test.rb +375 -62
  180. data/spec/integration/eager_loader_test.rb +19 -21
  181. data/spec/integration/model_test.rb +80 -1
  182. data/spec/integration/plugin_test.rb +304 -116
  183. data/spec/integration/prepared_statement_test.rb +200 -120
  184. data/spec/integration/schema_test.rb +161 -30
  185. data/spec/integration/spec_helper.rb +39 -30
  186. data/spec/integration/timezone_test.rb +38 -12
  187. data/spec/integration/transaction_test.rb +172 -5
  188. data/spec/integration/type_test.rb +17 -3
  189. data/spec/model/association_reflection_spec.rb +174 -7
  190. data/spec/model/associations_spec.rb +849 -661
  191. data/spec/model/base_spec.rb +255 -95
  192. data/spec/model/dataset_methods_spec.rb +7 -27
  193. data/spec/model/eager_loading_spec.rb +640 -676
  194. data/spec/model/hooks_spec.rb +309 -67
  195. data/spec/model/model_spec.rb +207 -167
  196. data/spec/model/plugins_spec.rb +24 -13
  197. data/spec/model/record_spec.rb +321 -218
  198. data/spec/model/spec_helper.rb +13 -71
  199. data/spec/model/validations_spec.rb +11 -0
  200. metadata +95 -38
@@ -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,168 @@ 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(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(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(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)}
208
+ each_opts[:timezone] = :utc if db.timezone == :utc
209
+ rn = row_number_column if @opts[:offset]
210
+ columns = cols = result.fields.map{|c| output_identifier(c)}
211
+ if opts[:offset]
212
+ rn = row_number_column
213
+ columns = columns.dup
214
+ columns.delete(rn)
215
+ end
216
+ @columns = columns
94
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
102
225
  else
103
226
  each_opts[:symbolize_keys] = true
104
227
  if offset
105
228
  result.each(each_opts) do |r|
106
- r.delete(row_number_column) if offset
229
+ r.delete(rn) if rn
107
230
  yield r
108
231
  end
109
232
  else
110
- result.each(each_opts, &block)
233
+ result.each(each_opts, &Proc.new)
111
234
  end
112
235
  end
113
236
  end
114
237
  self
115
238
  end
116
239
 
240
+ # Create a named prepared statement that is stored in the
241
+ # database (and connection) for reuse.
242
+ def prepare(type, name=nil, *values)
243
+ ps = to_prepared_statement(type, values)
244
+ ps.extend(PreparedStatementMethods)
245
+ if name
246
+ ps.prepared_statement_name = name
247
+ db.prepared_statements[name] = ps
248
+ end
249
+ ps
250
+ end
251
+
117
252
  private
118
253
 
119
254
  # Properly escape the given string +v+.
120
255
  def literal_string(v)
121
- db.synchronize{|c| "N'#{c.escape(v)}'"}
256
+ s = db.synchronize{|c| "#{'N' if mssql_unicode_strings}'#{c.escape(v)}'"}
122
257
  end
123
258
  end
124
259
  end
@@ -0,0 +1,63 @@
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(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(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
+ subselect_sql(unlimited.
48
+ unordered.
49
+ select_append{ROW_NUMBER(:over, :order=>order){}.as(rn)}.
50
+ from_self(:alias=>dsa1).
51
+ limit(@opts[:limit]).
52
+ where(SQL::Identifier.new(rn) > o))
53
+ end
54
+
55
+ private
56
+
57
+ # This emulation adds an extra row number column that should be
58
+ # eliminated.
59
+ def offset_returns_row_number_column?
60
+ true
61
+ end
62
+ end
63
+ end
@@ -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
@@ -188,7 +188,7 @@ module Sequel
188
188
  # This is used to ensure that the files loaded are from the same version of
189
189
  # Sequel as this file.
190
190
  def self.require(files, subdir=nil)
191
- Array(files).each{|f| super("#{File.dirname(__FILE__)}/#{"#{subdir}/" if subdir}#{f}")}
191
+ Array(files).each{|f| super("#{File.dirname(__FILE__).untaint}/#{"#{subdir}/" if subdir}#{f}")}
192
192
  end
193
193
 
194
194
  # Set whether to set the single threaded mode for all databases by default. By default,
@@ -228,17 +228,58 @@ module Sequel
228
228
  end
229
229
  end
230
230
 
231
- # Converts the given +string+ into a +Time+ object.
231
+ # Converts the given +string+ into a <tt>Sequel::SQLTime</tt> object.
232
232
  #
233
- # Sequel.string_to_datetime('10:20:30') # Time.parse('10:20:30')
233
+ # v = Sequel.string_to_time('10:20:30') # Sequel::SQLTime.parse('10:20:30')
234
+ # DB.literal(v) # => '10:20:30'
234
235
  def self.string_to_time(string)
235
236
  begin
236
- Time.parse(string)
237
+ SQLTime.parse(string)
237
238
  rescue => e
238
239
  raise convert_exception_class(e, InvalidValue)
239
240
  end
240
241
  end
241
242
 
243
+ # Uses a transaction on all given databases with the given options. This:
244
+ #
245
+ # Sequel.transaction([DB1, DB2, DB3]){...}
246
+ #
247
+ # is equivalent to:
248
+ #
249
+ # DB1.transaction do
250
+ # DB2.transaction do
251
+ # DB3.transaction do
252
+ # ...
253
+ # end
254
+ # end
255
+ # end
256
+ #
257
+ # except that if Sequel::Rollback is raised by the block, the transaction is
258
+ # rolled back on all databases instead of just the last one.
259
+ #
260
+ # Note that this method cannot guarantee that all databases will commit or
261
+ # rollback. For example, if DB3 commits but attempting to commit on DB2
262
+ # fails (maybe because foreign key checks are deferred), there is no way
263
+ # to uncommit the changes on DB3. For that kind of support, you need to
264
+ # have two-phase commit/prepared transactions (which Sequel supports on
265
+ # some databases).
266
+ def self.transaction(dbs, opts={}, &block)
267
+ unless opts[:rollback]
268
+ rescue_rollback = true
269
+ opts = opts.merge(:rollback=>:reraise)
270
+ end
271
+ pr = dbs.reverse.inject(block){|bl, db| proc{db.transaction(opts, &bl)}}
272
+ if rescue_rollback
273
+ begin
274
+ pr.call
275
+ rescue Sequel::Rollback => e
276
+ nil
277
+ end
278
+ else
279
+ pr.call
280
+ end
281
+ end
282
+
242
283
  # Same as Sequel.require, but wrapped in a mutex in order to be thread safe.
243
284
  def self.ts_require(*args)
244
285
  check_requiring_thread{require(*args)}
@@ -292,7 +333,7 @@ module Sequel
292
333
 
293
334
  private_class_method :adapter_method, :def_adapter_method
294
335
 
295
- require(%w"metaprogramming sql connection_pool exceptions dataset database timezones version")
336
+ require(%w"metaprogramming sql connection_pool exceptions dataset database timezones ast_transformer version")
296
337
  require('core_sql') if !defined?(::SEQUEL_NO_CORE_EXTENSIONS) && !ENV.has_key?('SEQUEL_NO_CORE_EXTENSIONS')
297
338
 
298
339
  # Add the database adapter class methods to Sequel via metaprogramming
@@ -1,12 +1,12 @@
1
1
  module Sequel
2
2
  class Database
3
3
  # ---------------------
4
- # :section: Methods relating to adapters, connecting, disconnecting, and sharding
4
+ # :section: 4 - Methods relating to adapters, connecting, disconnecting, and sharding
5
5
  # This methods involve the Database's connection pool.
6
6
  # ---------------------
7
7
 
8
8
  # Array of supported database adapters
9
- ADAPTERS = %w'ado amalgalite db2 dbi do firebird informix jdbc mysql mysql2 odbc openbase oracle postgres sqlite swift tinytds'.collect{|x| x.to_sym}
9
+ ADAPTERS = %w'ado amalgalite db2 dbi do firebird ibmdb informix jdbc mock mysql mysql2 odbc openbase oracle postgres sqlite swift tinytds'.collect{|x| x.to_sym}
10
10
 
11
11
  # Whether to use the single threaded connection pool by default
12
12
  @@single_threaded = false
@@ -54,7 +54,7 @@ module Sequel
54
54
  c = adapter_class(scheme)
55
55
  uri_options = c.send(:uri_to_options, uri)
56
56
  uri.query.split('&').collect{|s| s.split('=')}.each{|k,v| uri_options[k.to_sym] = v if k && !k.empty?} unless uri.query.to_s.strip.empty?
57
- uri_options.entries.each{|k,v| uri_options[k] = URI.unescape(v) if v.is_a?(String)}
57
+ uri_options.to_a.each{|k,v| uri_options[k] = URI.unescape(v) if v.is_a?(String)}
58
58
  opts = uri_options.merge(opts)
59
59
  opts[:adapter] = scheme
60
60
  end
@@ -155,8 +155,8 @@ module Sequel
155
155
 
156
156
  # Disconnects all available connections from the connection pool. Any
157
157
  # connections currently in use will not be disconnected. Options:
158
- # * :servers - Should be a symbol specifing the server to disconnect from,
159
- # or an array of symbols to specify multiple servers.
158
+ # :servers :: Should be a symbol specifing the server to disconnect from,
159
+ # or an array of symbols to specify multiple servers.
160
160
  #
161
161
  # Example:
162
162
  #
@@ -218,7 +218,6 @@ module Sequel
218
218
  # If a server option is given, acquires a connection for that specific
219
219
  # server, instead of the :default server.
220
220
  #
221
- #
222
221
  # DB.synchronize do |conn|
223
222
  # ...
224
223
  # end
@@ -1,7 +1,7 @@
1
1
  module Sequel
2
2
  class Database
3
3
  # ---------------------
4
- # :section: Methods that create datasets
4
+ # :section: 3 - Methods that create datasets
5
5
  # These methods all return instances of this database's dataset class.
6
6
  # ---------------------
7
7
 
@@ -24,10 +24,10 @@ module Sequel
24
24
  #
25
25
  # DB.dataset # SELECT *
26
26
  # DB.dataset.from(:items) # SELECT * FROM items
27
- def dataset
28
- ds = Sequel::Dataset.new(self)
27
+ def dataset(opts=nil)
28
+ @dataset_class.new(self, opts)
29
29
  end
30
-
30
+
31
31
  # Fetches records for an arbitrary SQL statement. If a block is given,
32
32
  # it is used to iterate over the records:
33
33
  #