sequel 3.12.1 → 3.13.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 (150) hide show
  1. data/CHANGELOG +42 -0
  2. data/README.rdoc +137 -118
  3. data/Rakefile +21 -66
  4. data/doc/active_record.rdoc +9 -9
  5. data/doc/advanced_associations.rdoc +59 -188
  6. data/doc/association_basics.rdoc +15 -2
  7. data/doc/cheat_sheet.rdoc +38 -33
  8. data/doc/dataset_filtering.rdoc +16 -7
  9. data/doc/prepared_statements.rdoc +7 -7
  10. data/doc/querying.rdoc +5 -4
  11. data/doc/release_notes/3.13.0.txt +210 -0
  12. data/doc/sharding.rdoc +1 -1
  13. data/doc/sql.rdoc +5 -5
  14. data/doc/validations.rdoc +11 -11
  15. data/lib/sequel/adapters/ado.rb +1 -1
  16. data/lib/sequel/adapters/do.rb +3 -3
  17. data/lib/sequel/adapters/firebird.rb +3 -3
  18. data/lib/sequel/adapters/jdbc/h2.rb +39 -0
  19. data/lib/sequel/adapters/jdbc/mysql.rb +5 -0
  20. data/lib/sequel/adapters/jdbc/oracle.rb +3 -3
  21. data/lib/sequel/adapters/mysql.rb +7 -4
  22. data/lib/sequel/adapters/oracle.rb +3 -3
  23. data/lib/sequel/adapters/shared/mssql.rb +10 -1
  24. data/lib/sequel/adapters/shared/mysql.rb +63 -0
  25. data/lib/sequel/adapters/shared/postgres.rb +61 -3
  26. data/lib/sequel/adapters/sqlite.rb +105 -18
  27. data/lib/sequel/connection_pool.rb +31 -30
  28. data/lib/sequel/core.rb +58 -58
  29. data/lib/sequel/core_sql.rb +52 -43
  30. data/lib/sequel/database/misc.rb +11 -0
  31. data/lib/sequel/database/query.rb +55 -17
  32. data/lib/sequel/dataset/actions.rb +2 -1
  33. data/lib/sequel/dataset/query.rb +2 -3
  34. data/lib/sequel/dataset/sql.rb +24 -11
  35. data/lib/sequel/extensions/schema_dumper.rb +1 -1
  36. data/lib/sequel/metaprogramming.rb +4 -0
  37. data/lib/sequel/model.rb +37 -19
  38. data/lib/sequel/model/associations.rb +33 -25
  39. data/lib/sequel/model/base.rb +2 -2
  40. data/lib/sequel/model/plugins.rb +7 -2
  41. data/lib/sequel/plugins/active_model.rb +1 -1
  42. data/lib/sequel/plugins/association_pks.rb +2 -2
  43. data/lib/sequel/plugins/association_proxies.rb +1 -1
  44. data/lib/sequel/plugins/boolean_readers.rb +2 -2
  45. data/lib/sequel/plugins/class_table_inheritance.rb +10 -2
  46. data/lib/sequel/plugins/identity_map.rb +3 -3
  47. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  48. data/lib/sequel/plugins/json_serializer.rb +212 -0
  49. data/lib/sequel/plugins/lazy_attributes.rb +1 -1
  50. data/lib/sequel/plugins/list.rb +174 -0
  51. data/lib/sequel/plugins/many_through_many.rb +2 -2
  52. data/lib/sequel/plugins/rcte_tree.rb +6 -7
  53. data/lib/sequel/plugins/tree.rb +118 -0
  54. data/lib/sequel/plugins/xml_serializer.rb +321 -0
  55. data/lib/sequel/sql.rb +315 -206
  56. data/lib/sequel/timezones.rb +40 -17
  57. data/lib/sequel/version.rb +8 -2
  58. data/spec/adapters/firebird_spec.rb +2 -2
  59. data/spec/adapters/informix_spec.rb +1 -1
  60. data/spec/adapters/mssql_spec.rb +2 -2
  61. data/spec/adapters/mysql_spec.rb +2 -2
  62. data/spec/adapters/oracle_spec.rb +1 -1
  63. data/spec/adapters/postgres_spec.rb +36 -6
  64. data/spec/adapters/spec_helper.rb +2 -2
  65. data/spec/adapters/sqlite_spec.rb +1 -1
  66. data/spec/core/connection_pool_spec.rb +3 -3
  67. data/spec/core/core_sql_spec.rb +31 -13
  68. data/spec/core/database_spec.rb +39 -2
  69. data/spec/core/dataset_spec.rb +24 -12
  70. data/spec/core/expression_filters_spec.rb +5 -1
  71. data/spec/core/object_graph_spec.rb +1 -1
  72. data/spec/core/schema_generator_spec.rb +1 -1
  73. data/spec/core/schema_spec.rb +1 -1
  74. data/spec/core/spec_helper.rb +1 -1
  75. data/spec/core/version_spec.rb +1 -1
  76. data/spec/extensions/active_model_spec.rb +82 -67
  77. data/spec/extensions/association_dependencies_spec.rb +1 -1
  78. data/spec/extensions/association_pks_spec.rb +1 -1
  79. data/spec/extensions/association_proxies_spec.rb +1 -1
  80. data/spec/extensions/blank_spec.rb +1 -1
  81. data/spec/extensions/boolean_readers_spec.rb +1 -1
  82. data/spec/extensions/caching_spec.rb +1 -1
  83. data/spec/extensions/class_table_inheritance_spec.rb +3 -2
  84. data/spec/extensions/composition_spec.rb +2 -5
  85. data/spec/extensions/force_encoding_spec.rb +3 -1
  86. data/spec/extensions/hook_class_methods_spec.rb +1 -1
  87. data/spec/extensions/identity_map_spec.rb +1 -1
  88. data/spec/extensions/inflector_spec.rb +1 -1
  89. data/spec/extensions/instance_filters_spec.rb +1 -1
  90. data/spec/extensions/instance_hooks_spec.rb +1 -1
  91. data/spec/extensions/json_serializer_spec.rb +154 -0
  92. data/spec/extensions/lazy_attributes_spec.rb +1 -2
  93. data/spec/extensions/list_spec.rb +251 -0
  94. data/spec/extensions/looser_typecasting_spec.rb +1 -1
  95. data/spec/extensions/many_through_many_spec.rb +3 -3
  96. data/spec/extensions/migration_spec.rb +1 -1
  97. data/spec/extensions/named_timezones_spec.rb +5 -6
  98. data/spec/extensions/nested_attributes_spec.rb +1 -1
  99. data/spec/extensions/optimistic_locking_spec.rb +1 -1
  100. data/spec/extensions/pagination_spec.rb +1 -1
  101. data/spec/extensions/pretty_table_spec.rb +1 -1
  102. data/spec/extensions/query_spec.rb +1 -1
  103. data/spec/extensions/rcte_tree_spec.rb +1 -1
  104. data/spec/extensions/schema_dumper_spec.rb +3 -2
  105. data/spec/extensions/schema_spec.rb +1 -1
  106. data/spec/extensions/serialization_spec.rb +6 -2
  107. data/spec/extensions/sharding_spec.rb +1 -1
  108. data/spec/extensions/single_table_inheritance_spec.rb +1 -1
  109. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  110. data/spec/extensions/spec_helper.rb +7 -3
  111. data/spec/extensions/sql_expr_spec.rb +1 -1
  112. data/spec/extensions/string_date_time_spec.rb +1 -1
  113. data/spec/extensions/string_stripper_spec.rb +1 -1
  114. data/spec/extensions/subclasses_spec.rb +1 -1
  115. data/spec/extensions/tactical_eager_loading_spec.rb +1 -1
  116. data/spec/extensions/thread_local_timezones_spec.rb +1 -1
  117. data/spec/extensions/timestamps_spec.rb +1 -1
  118. data/spec/extensions/touch_spec.rb +1 -1
  119. data/spec/extensions/tree_spec.rb +119 -0
  120. data/spec/extensions/typecast_on_load_spec.rb +1 -1
  121. data/spec/extensions/update_primary_key_spec.rb +1 -1
  122. data/spec/extensions/validation_class_methods_spec.rb +1 -1
  123. data/spec/extensions/validation_helpers_spec.rb +1 -1
  124. data/spec/extensions/xml_serializer_spec.rb +142 -0
  125. data/spec/integration/associations_test.rb +1 -1
  126. data/spec/integration/database_test.rb +1 -1
  127. data/spec/integration/dataset_test.rb +29 -14
  128. data/spec/integration/eager_loader_test.rb +1 -1
  129. data/spec/integration/migrator_test.rb +1 -1
  130. data/spec/integration/model_test.rb +1 -1
  131. data/spec/integration/plugin_test.rb +316 -1
  132. data/spec/integration/prepared_statement_test.rb +1 -1
  133. data/spec/integration/schema_test.rb +8 -8
  134. data/spec/integration/spec_helper.rb +1 -1
  135. data/spec/integration/timezone_test.rb +1 -1
  136. data/spec/integration/transaction_test.rb +35 -20
  137. data/spec/integration/type_test.rb +1 -1
  138. data/spec/model/association_reflection_spec.rb +1 -1
  139. data/spec/model/associations_spec.rb +49 -34
  140. data/spec/model/base_spec.rb +1 -1
  141. data/spec/model/dataset_methods_spec.rb +4 -4
  142. data/spec/model/eager_loading_spec.rb +1 -1
  143. data/spec/model/hooks_spec.rb +1 -1
  144. data/spec/model/inflector_spec.rb +1 -1
  145. data/spec/model/model_spec.rb +7 -1
  146. data/spec/model/plugins_spec.rb +1 -1
  147. data/spec/model/record_spec.rb +1 -3
  148. data/spec/model/spec_helper.rb +2 -2
  149. data/spec/model/validations_spec.rb +1 -1
  150. metadata +29 -5
@@ -1,8 +1,8 @@
1
- # Sequel extends the Array class to add methods to implement the SQL DSL.
1
+ # Sequel extends +Array+ to add methods to implement the SQL DSL.
2
2
  # Most of these methods require that the array not be empty and that it
3
3
  # must consist solely of other arrays that have exactly two elements.
4
4
  class Array
5
- # Return a Sequel::SQL::BooleanExpression created from this array, not matching all of the
5
+ # Return a <tt>Sequel::SQL::BooleanExpression</tt> created from this array, not matching all of the
6
6
  # conditions.
7
7
  #
8
8
  # ~[[:a, true]] # SQL: a IS NOT TRUE
@@ -11,8 +11,8 @@ class Array
11
11
  sql_expr_if_all_two_pairs(:OR, true)
12
12
  end
13
13
 
14
- # True if the array is not empty and all of its elements are
15
- # arrays of size 2, false otherwise. This is used to determine if the array
14
+ # +true+ if the array is not empty and all of its elements are
15
+ # arrays of size 2, +false+ otherwise. This is used to determine if the array
16
16
  # could be a specifier of conditions, used similarly to a hash
17
17
  # but allowing for duplicate keys and a specific order.
18
18
  #
@@ -24,28 +24,37 @@ class Array
24
24
  !empty? && all?{|i| (Array === i) && (i.length == 2)}
25
25
  end
26
26
 
27
- # Return a Sequel::SQL::CaseExpression with this array as the conditions and the given
27
+ # Return a <tt>Sequel::SQL::CaseExpression</tt> with this array as the conditions and the given
28
28
  # default value and expression.
29
29
  #
30
30
  # [[{:a=>[2,3]}, 1]].case(0) # SQL: CASE WHEN a IN (2, 3) THEN 1 ELSE 0 END
31
31
  # [[:a, 1], [:b, 2]].case(:d, :c) # SQL: CASE c WHEN a THEN 1 WHEN b THEN 2 ELSE d END
32
- def case(default, expression = nil)
33
- ::Sequel::SQL::CaseExpression.new(self, default, expression)
32
+ def case(*args)
33
+ ::Sequel::SQL::CaseExpression.new(self, *args)
34
34
  end
35
35
 
36
- # Return a Sequel::SQL::Array created from this array. Used if this array contains
37
- # all two pairs and you want it treated as an SQL array instead of a ordered hash-like
38
- # conditions.
36
+ # Return a <tt>Sequel::SQL::ValueList</tt> created from this array. Used if this array contains
37
+ # all two element arrays and you want it treated as an SQL value list (IN predicate)
38
+ # instead of as a conditions specifier (similar to a hash). This is not necessary if you are using
39
+ # this array as a value in a filter, but may be necessary if you are using it as a
40
+ # value with placeholder SQL:
39
41
  #
40
- # [[1, 2], [3, 4]] # SQL: 1 = 2 AND 3 = 4
41
- # [[1, 2], [3, 4]].sql_array # SQL: ((1, 2), (3, 4))
42
- def sql_array
43
- ::Sequel::SQL::SQLArray.new(self)
42
+ # DB[:a].filter([:a, :b]=>[[1, 2], [3, 4]]) # SQL: (a, b) IN ((1, 2), (3, 4))
43
+ # DB[:a].filter('(a, b) IN ?', [[1, 2], [3, 4]]) # SQL: (a, b) IN ((1 = 2) AND (3 = 4))
44
+ # DB[:a].filter('(a, b) IN ?', [[1, 2], [3, 4]].sql_value_list) # SQL: (a, b) IN ((1, 2), (3, 4))
45
+ def sql_value_list
46
+ ::Sequel::SQL::ValueList.new(self)
44
47
  end
48
+
49
+ # Deprecated alias for sql_value_list
50
+ alias sql_array sql_value_list
45
51
 
46
- # Return a Sequel::SQL::BooleanExpression created from this array, matching all of the
52
+ # Return a <tt>Sequel::SQL::BooleanExpression</tt> created from this array, matching all of the
47
53
  # conditions. Rarely do you need to call this explicitly, as Sequel generally
48
- # assumes that arrays of all two pairs specify this type of condition.
54
+ # assumes that arrays of two element arrays specify this type of condition. One case where
55
+ # it can be necessary to use this is if you are using the object as a value in a filter hash
56
+ # and want to use the = operator instead of the IN operator (which is used by default for
57
+ # arrays of two element arrays).
49
58
  #
50
59
  # [[:a, true]].sql_expr # SQL: a IS TRUE
51
60
  # [[:a, 1], [:b, [2, 3]]].sql_expr # SQL: a = 1 AND b IN (2, 3)
@@ -53,7 +62,7 @@ class Array
53
62
  sql_expr_if_all_two_pairs
54
63
  end
55
64
 
56
- # Return a Sequel::SQL::BooleanExpression created from this array, matching none
65
+ # Return a <tt>Sequel::SQL::BooleanExpression</tt> created from this array, matching none
57
66
  # of the conditions.
58
67
  #
59
68
  # [[:a, true]].sql_negate # SQL: a IS NOT TRUE
@@ -62,7 +71,7 @@ class Array
62
71
  sql_expr_if_all_two_pairs(:AND, true)
63
72
  end
64
73
 
65
- # Return a Sequel::SQL::BooleanExpression created from this array, matching any of the
74
+ # Return a <tt>Sequel::SQL::BooleanExpression</tt> created from this array, matching any of the
66
75
  # conditions.
67
76
  #
68
77
  # [[:a, true]].sql_or # SQL: a IS TRUE
@@ -71,10 +80,10 @@ class Array
71
80
  sql_expr_if_all_two_pairs(:OR)
72
81
  end
73
82
 
74
- # Return a Sequel::SQL::BooleanExpression representing an SQL string made up of the
83
+ # Return a <tt>Sequel::SQL::BooleanExpression</tt> representing an SQL string made up of the
75
84
  # concatenation of this array's elements. If an argument is passed
76
85
  # it is used in between each element of the array in the SQL
77
- # concatenation. This does not require that the array be made up of all two pairs.
86
+ # concatenation.
78
87
  #
79
88
  # [:a].sql_string_join # SQL: a
80
89
  # [:a, :b].sql_string_join # SQL: a || b
@@ -93,16 +102,16 @@ class Array
93
102
 
94
103
  private
95
104
 
96
- # Raise an error if this array is not made up of all two pairs, otherwise create a Sequel::SQL::BooleanExpression from this array.
105
+ # Raise an error if this array is not made up all two element arrays, otherwise create a <tt>Sequel::SQL::BooleanExpression</tt> from this array.
97
106
  def sql_expr_if_all_two_pairs(*args)
98
107
  raise(Sequel::Error, 'Not all elements of the array are arrays of size 2, so it cannot be converted to an SQL expression') unless all_two_pairs?
99
108
  ::Sequel::SQL::BooleanExpression.from_value_pairs(self, *args)
100
109
  end
101
110
  end
102
111
 
103
- # Sequel extends the Hash class to add methods to implement the SQL DSL.
112
+ # Sequel extends +Hash+ to add methods to implement the SQL DSL.
104
113
  class Hash
105
- # Return a Sequel::SQL::BooleanExpression created from this hash, matching
114
+ # Return a <tt>Sequel::SQL::BooleanExpression</tt> created from this hash, matching
106
115
  # all of the conditions in this hash and the condition specified by
107
116
  # the given argument.
108
117
  #
@@ -112,7 +121,7 @@ class Hash
112
121
  ::Sequel::SQL::BooleanExpression.new(:AND, self, ce)
113
122
  end
114
123
 
115
- # Return a Sequel::SQL::BooleanExpression created from this hash, matching
124
+ # Return a <tt>Sequel::SQL::BooleanExpression</tt> created from this hash, matching
116
125
  # all of the conditions in this hash or the condition specified by
117
126
  # the given argument.
118
127
  #
@@ -122,7 +131,7 @@ class Hash
122
131
  ::Sequel::SQL::BooleanExpression.new(:OR, self, ce)
123
132
  end
124
133
 
125
- # Return a Sequel::SQL::BooleanExpression created from this hash, not matching all of the
134
+ # Return a <tt>Sequel::SQL::BooleanExpression</tt> created from this hash, not matching all of the
126
135
  # conditions.
127
136
  #
128
137
  # ~{:a=>true} # SQL: a IS NOT TRUE
@@ -131,18 +140,18 @@ class Hash
131
140
  ::Sequel::SQL::BooleanExpression.from_value_pairs(self, :OR, true)
132
141
  end
133
142
 
134
- # Return a Sequel::SQL::CaseExpression with this hash as the conditions and the given
135
- # default value. Note that the order of the conditions will be arbitrary, so all
143
+ # Return a <tt>Sequel::SQL::CaseExpression</tt> with this hash as the conditions and the given
144
+ # default value. Note that the order of the conditions will be arbitrary on ruby 1.8, so all
136
145
  # conditions should be orthogonal.
137
146
  #
138
147
  # {{:a=>[2,3]}=>1}.case(0) # SQL: CASE WHEN a IN (2, 3) THEN 1 ELSE 0 END
139
- # {:a=>1, {:b=>2}].case(:d, :c) # SQL: CASE c WHEN a THEN 1 WHEN b THEN 2 ELSE d END
148
+ # {:a=>1, :b=>2}.case(:d, :c) # SQL: CASE c WHEN a THEN 1 WHEN b THEN 2 ELSE d END
140
149
  # # or: CASE c WHEN b THEN 2 WHEN a THEN 1 ELSE d END
141
- def case(default, expression = nil)
142
- ::Sequel::SQL::CaseExpression.new(to_a, default, expression)
150
+ def case(*args)
151
+ ::Sequel::SQL::CaseExpression.new(to_a, *args)
143
152
  end
144
153
 
145
- # Return a Sequel::SQL::BooleanExpression created from this hash, matching all of the
154
+ # Return a <tt>Sequel::SQL::BooleanExpression</tt> created from this hash, matching all of the
146
155
  # conditions. Rarely do you need to call this explicitly, as Sequel generally
147
156
  # assumes that hashes specify this type of condition.
148
157
  #
@@ -152,7 +161,7 @@ class Hash
152
161
  ::Sequel::SQL::BooleanExpression.from_value_pairs(self)
153
162
  end
154
163
 
155
- # Return a Sequel::SQL::BooleanExpression created from this hash, matching none
164
+ # Return a <tt>Sequel::SQL::BooleanExpression</tt> created from this hash, matching none
156
165
  # of the conditions.
157
166
  #
158
167
  # {:a=>true}.sql_negate # SQL: a IS NOT TRUE
@@ -161,7 +170,7 @@ class Hash
161
170
  ::Sequel::SQL::BooleanExpression.from_value_pairs(self, :AND, true)
162
171
  end
163
172
 
164
- # Return a Sequel::SQL::BooleanExpression created from this hash, matching any of the
173
+ # Return a <tt>Sequel::SQL::BooleanExpression</tt> created from this hash, matching any of the
165
174
  # conditions.
166
175
  #
167
176
  # {:a=>true}.sql_or # SQL: a IS TRUE
@@ -171,12 +180,12 @@ class Hash
171
180
  end
172
181
  end
173
182
 
174
- # Sequel extends the String class to add methods to implement the SQL DSL.
183
+ # Sequel extends +String+ to add methods to implement the SQL DSL.
175
184
  class String
176
185
  include Sequel::SQL::AliasMethods
177
186
  include Sequel::SQL::CastMethods
178
187
 
179
- # Converts a string into a Sequel::LiteralString, in order to override string
188
+ # Converts a string into a <tt>Sequel::LiteralString</tt>, in order to override string
180
189
  # literalization, e.g.:
181
190
  #
182
191
  # DB[:items].filter(:abc => 'def').sql #=>
@@ -185,7 +194,7 @@ class String
185
194
  # DB[:items].filter(:abc => 'def'.lit).sql #=>
186
195
  # "SELECT * FROM items WHERE (abc = def)"
187
196
  #
188
- # You can also provide arguments, to create a Sequel::SQL::PlaceholderLiteralString:
197
+ # You can also provide arguments, to create a <tt>Sequel::SQL::PlaceholderLiteralString</tt>:
189
198
  #
190
199
  # DB[:items].select{|o| o.count('DISTINCT ?'.lit(:a))}.sql #=>
191
200
  # "SELECT count(DISTINCT a) FROM items"
@@ -193,14 +202,14 @@ class String
193
202
  args.empty? ? Sequel::LiteralString.new(self) : Sequel::SQL::PlaceholderLiteralString.new(self, args)
194
203
  end
195
204
 
196
- # Returns a Sequel::SQL::Blob that holds the same data as this string. Blobs provide proper
205
+ # Returns a <tt>Sequel::SQL::Blob</tt> that holds the same data as this string. Blobs provide proper
197
206
  # escaping of binary data.
198
207
  def to_sequel_blob
199
208
  ::Sequel::SQL::Blob.new(self)
200
209
  end
201
210
  end
202
211
 
203
- # Sequel extends the Symbol class to add methods to implement the SQL DSL.
212
+ # Sequel extends +Symbol+ to add methods to implement the SQL DSL.
204
213
  class Symbol
205
214
  include Sequel::SQL::QualifyingMethods
206
215
  include Sequel::SQL::IdentifierMethods
@@ -214,9 +223,9 @@ class Symbol
214
223
  include Sequel::SQL::ComplexExpressionMethods
215
224
  include Sequel::SQL::InequalityMethods if RUBY_VERSION < '1.9.0'
216
225
 
217
- # If no argument is given, returns a Sequel::SQL::ColumnAll object specifying all
226
+ # If no argument is given, returns a <tt>Sequel::SQL::ColumnAll</tt> object specifying all
218
227
  # columns for this table.
219
- # If an argument is given, returns a Sequel::SQL::NumericExpression using the *
228
+ # If an argument is given, returns a <tt>Sequel::SQL::NumericExpression</tt> using the *
220
229
  # (multiplication) operator with this and the given argument.
221
230
  #
222
231
  # :table.* # SQL: table.*
@@ -226,9 +235,9 @@ class Symbol
226
235
  Sequel::SQL::ColumnAll.new(self);
227
236
  end
228
237
 
229
- # Returns a Sequel::SQL::Function with this as the function name,
230
- # and the given arguments. This is aliased as Symbol#[] if the RUBY_VERSION
231
- # is less than 1.9.0. Ruby 1.9 defines Symbol#[], and Sequel
238
+ # Returns a <tt>Sequel::SQL::Function</tt> with this as the function name,
239
+ # and the given arguments. This is aliased as <tt>Symbol#[]</tt> if the RUBY_VERSION
240
+ # is less than 1.9.0. Ruby 1.9 defines <tt>Symbol#[]</tt>, and Sequel
232
241
  # doesn't override methods defined by ruby itself.
233
242
  #
234
243
  # :now.sql_function # SQL: now()
@@ -79,11 +79,22 @@ module Sequel
79
79
  {:primary_key => true, :type => Integer, :auto_increment => true}
80
80
  end
81
81
 
82
+ # Whether the database and adapter support prepared transactions
83
+ # (two-phase commit), false by default
84
+ def supports_prepared_transactions?
85
+ false
86
+ end
87
+
82
88
  # Whether the database and adapter support savepoints, false by default
83
89
  def supports_savepoints?
84
90
  false
85
91
  end
86
92
 
93
+ # Whether the database and adapter support transaction isolation levels, false by default
94
+ def supports_transaction_isolation_levels?
95
+ false
96
+ end
97
+
87
98
  # Typecast the value to the given column_type. Calls
88
99
  # typecast_value_#{column_type} if the method exists,
89
100
  # otherwise returns the value.
@@ -15,6 +15,11 @@ module Sequel
15
15
  TRANSACTION_BEGIN = 'Transaction.begin'.freeze
16
16
  TRANSACTION_COMMIT = 'Transaction.commit'.freeze
17
17
  TRANSACTION_ROLLBACK = 'Transaction.rollback'.freeze
18
+
19
+ TRANSACTION_ISOLATION_LEVELS = {:uncommitted=>'READ UNCOMMITTED'.freeze,
20
+ :committed=>'READ COMMITTED'.freeze,
21
+ :repeatable=>'REPEATABLE READ'.freeze,
22
+ :serializable=>'SERIALIZABLE'.freeze}
18
23
 
19
24
  POSTGRES_DEFAULT_RE = /\A(?:B?('.*')::[^']+|\((-?\d+(?:\.\d+)?)\))\z/
20
25
  MSSQL_DEFAULT_RE = /\A(?:\(N?('.*')\)|\(\((-?\d+(?:\.\d+)?)\)\))\z/
@@ -24,6 +29,13 @@ module Sequel
24
29
  # The prepared statement objects for this database, keyed by name
25
30
  attr_reader :prepared_statements
26
31
 
32
+ # The default transaction isolation level for this database,
33
+ # used for all future transactions. For MSSQL, this should be set
34
+ # to something if you ever plan to use the :isolation option to
35
+ # Database#transaction, as on MSSQL if affects all future transactions
36
+ # on the same connection.
37
+ attr_accessor :transaction_isolation_level
38
+
27
39
  # Runs the supplied SQL statement string on the database server.
28
40
  # Alias for run.
29
41
  def <<(sql)
@@ -142,15 +154,20 @@ module Sequel
142
154
  #
143
155
  # The following options are respected:
144
156
  #
145
- # * :server - The server to use for the transaction
146
- # * :savepoint - Whether to create a new savepoint for this transaction,
147
- # only respected if the database adapter supports savepoints. By
148
- # default Sequel will reuse an existing transaction, so if you want to
149
- # use a savepoint you must use this option.
157
+ # :isolation :: The transaction isolation level to use for this transaction,
158
+ # should be :uncommitted, :committed, :repeatable, or :serializable.
159
+ # :prepare :: A string to use as the transaction identifier for a
160
+ # prepared transaction (two-phase commit), if the database/adapter
161
+ # supports prepared transactions
162
+ # :server :: The server to use for the transaction
163
+ # :savepoint :: Whether to create a new savepoint for this transaction,
164
+ # only respected if the database adapter supports savepoints. By
165
+ # default Sequel will reuse an existing transaction, so if you want to
166
+ # use a savepoint you must use this option.
150
167
  def transaction(opts={}, &block)
151
168
  synchronize(opts[:server]) do |conn|
152
169
  return yield(conn) if already_in_transaction?(conn, opts)
153
- _transaction(conn, &block)
170
+ _transaction(conn, opts, &block)
154
171
  end
155
172
  end
156
173
 
@@ -160,17 +177,17 @@ module Sequel
160
177
  # block will cause the transaction to be rolled back. If the exception is
161
178
  # not Sequel::Rollback, the error will be reraised. If no exception occurs
162
179
  # inside the block, the transaction is commited.
163
- def _transaction(conn)
180
+ def _transaction(conn, opts={})
164
181
  begin
165
182
  add_transaction
166
- t = begin_transaction(conn)
183
+ t = begin_transaction(conn, opts)
167
184
  yield(conn)
168
185
  rescue Exception => e
169
- rollback_transaction(t) if t
186
+ rollback_transaction(t, opts) if t
170
187
  transaction_error(e)
171
188
  ensure
172
189
  begin
173
- commit_transaction(t) unless e
190
+ commit_transaction(t, opts) unless e
174
191
  rescue Exception => e
175
192
  raise_error(e, :classes=>database_error_classes)
176
193
  ensure
@@ -202,15 +219,24 @@ module Sequel
202
219
  SQL_SAVEPOINT % depth
203
220
  end
204
221
 
205
- # Start a new database transaction on the given connection.
206
- def begin_transaction(conn)
222
+ # Start a new database connection on the given connection
223
+ def begin_new_transaction(conn, opts)
224
+ log_connection_execute(conn, begin_transaction_sql)
225
+ set_transaction_isolation(conn, opts)
226
+ end
227
+
228
+ # Start a new database transaction or a new savepoint on the given connection.
229
+ def begin_transaction(conn, opts={})
207
230
  if supports_savepoints?
208
231
  th = Thread.current
209
- depth = th[:sequel_transaction_depth]
210
- log_connection_execute(conn, depth > 0 ? begin_savepoint_sql(depth) : begin_transaction_sql)
232
+ if (depth = th[:sequel_transaction_depth]) > 0
233
+ log_connection_execute(conn, begin_savepoint_sql(depth))
234
+ else
235
+ begin_new_transaction(conn, opts)
236
+ end
211
237
  th[:sequel_transaction_depth] += 1
212
238
  else
213
- log_connection_execute(conn, begin_transaction_sql)
239
+ begin_new_transaction(conn, opts)
214
240
  end
215
241
  conn
216
242
  end
@@ -276,7 +302,7 @@ module Sequel
276
302
  end
277
303
 
278
304
  # Commit the active transaction on the connection
279
- def commit_transaction(conn)
305
+ def commit_transaction(conn, opts={})
280
306
  if supports_savepoints?
281
307
  depth = Thread.current[:sequel_transaction_depth]
282
308
  log_connection_execute(conn, depth > 1 ? commit_savepoint_sql(depth-1) : commit_transaction_sql)
@@ -343,7 +369,7 @@ module Sequel
343
369
  end
344
370
 
345
371
  # Rollback the active transaction on the connection
346
- def rollback_transaction(conn)
372
+ def rollback_transaction(conn, opts={})
347
373
  if supports_savepoints?
348
374
  depth = Thread.current[:sequel_transaction_depth]
349
375
  log_connection_execute(conn, depth > 1 ? rollback_savepoint_sql(depth-1) : rollback_transaction_sql)
@@ -382,6 +408,18 @@ module Sequel
382
408
  end
383
409
  end
384
410
 
411
+ # Set the transaction isolation level on the given connection
412
+ def set_transaction_isolation(conn, opts)
413
+ if supports_transaction_isolation_levels? and level = opts.fetch(:isolation, transaction_isolation_level)
414
+ log_connection_execute(conn, set_transaction_isolation_sql(level))
415
+ end
416
+ end
417
+
418
+ # SQL to set the transaction isolation level
419
+ def set_transaction_isolation_sql(level)
420
+ "SET TRANSACTION ISOLATION LEVEL #{TRANSACTION_ISOLATION_LEVELS[level]}"
421
+ end
422
+
385
423
  # Raise a database error unless the exception is an Rollback.
386
424
  def transaction_error(e)
387
425
  raise_error(e, :classes=>database_error_classes) unless e.is_a?(Rollback)
@@ -90,7 +90,8 @@ module Sequel
90
90
  #
91
91
  # Note that this method is not safe to use on many adapters if you are
92
92
  # running additional queries inside the provided block. If you are
93
- # running queries inside the block, you use should all instead of each.
93
+ # running queries inside the block, you should use +all+ instead of +each+
94
+ # for the outer queries, or use a separate thread or shard inside +each+.
94
95
  def each(&block)
95
96
  if @opts[:graph]
96
97
  graph_each(&block)
@@ -226,9 +226,8 @@ module Sequel
226
226
  group(*columns)
227
227
  end
228
228
 
229
- # Returns a dataset grouped by the given column with count by group,
230
- # order by the count of records. Column aliases may be supplied, and will
231
- # be included in the select clause.
229
+ # Returns a dataset grouped by the given column with count by group.
230
+ # Column aliases may be supplied, and will be included in the select clause.
232
231
  #
233
232
  # Examples:
234
233
  #
@@ -228,7 +228,7 @@ module Sequel
228
228
  # SQL fragment for specifying given CaseExpression.
229
229
  def case_expression_sql(ce)
230
230
  sql = '(CASE '
231
- sql << "#{literal(ce.expression)} " if ce.expression
231
+ sql << "#{literal(ce.expression)} " if ce.expression?
232
232
  ce.conditions.collect{ |c,r|
233
233
  sql << "WHEN #{literal(c)} THEN #{literal(r)} "
234
234
  }
@@ -261,10 +261,10 @@ module Sequel
261
261
  when :IN, :"NOT IN"
262
262
  cols = args.at(0)
263
263
  vals = args.at(1)
264
- col_array = true if cols.is_a?(Array) || cols.is_a?(SQL::SQLArray)
265
- if vals.is_a?(Array) || vals.is_a?(SQL::SQLArray)
264
+ col_array = true if cols.is_a?(Array)
265
+ if vals.is_a?(Array)
266
266
  val_array = true
267
- empty_val_array = vals.to_a == []
267
+ empty_val_array = vals == []
268
268
  end
269
269
  if col_array
270
270
  if empty_val_array
@@ -284,7 +284,10 @@ module Sequel
284
284
  complex_expression_sql(op, [cols, vals.map!{|x| x.values_at(*val_cols)}])
285
285
  end
286
286
  else
287
- "(#{literal(cols)} #{op} #{literal(vals)})"
287
+ # If the columns and values are both arrays, use array_sql instead of
288
+ # literal so that if values is an array of two element arrays, it
289
+ # will be treated as a value list instead of a condition specifier.
290
+ "(#{literal(cols)} #{op} #{val_array ? array_sql(vals) : literal(vals)})"
288
291
  end
289
292
  else
290
293
  if empty_val_array
@@ -350,7 +353,15 @@ module Sequel
350
353
  # SQL fragment for the ordered expression, used in the ORDER BY
351
354
  # clause.
352
355
  def ordered_expression_sql(oe)
353
- "#{literal(oe.expression)} #{oe.descending ? 'DESC' : 'ASC'}"
356
+ s = "#{literal(oe.expression)} #{oe.descending ? 'DESC' : 'ASC'}"
357
+ case oe.nulls
358
+ when :first
359
+ "#{s} NULLS FIRST"
360
+ when :last
361
+ "#{s} NULLS LAST"
362
+ else
363
+ s
364
+ end
354
365
  end
355
366
 
356
367
  # SQL fragment for a literal string with placeholders
@@ -434,8 +445,10 @@ module Sequel
434
445
  "ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING"
435
446
  when :rows
436
447
  "ROWS UNBOUNDED PRECEDING"
448
+ when String
449
+ opts[:frame]
437
450
  else
438
- raise Error, "invalid window frame clause, should be :all, :rows, or nil"
451
+ raise Error, "invalid window frame clause, should be :all, :rows, a string, or nil"
439
452
  end
440
453
  "(#{[window, partition, order, frame].compact.join(' ')})"
441
454
  end
@@ -796,19 +809,19 @@ module Sequel
796
809
  when SQL::Identifier
797
810
  SQL::QualifiedIdentifier.new(table, e)
798
811
  when SQL::OrderedExpression
799
- SQL::OrderedExpression.new(qualified_expression(e.expression, table), e.descending)
812
+ SQL::OrderedExpression.new(qualified_expression(e.expression, table), e.descending, :nulls=>e.nulls)
800
813
  when SQL::AliasedExpression
801
814
  SQL::AliasedExpression.new(qualified_expression(e.expression, table), e.aliaz)
802
815
  when SQL::CaseExpression
803
- SQL::CaseExpression.new(qualified_expression(e.conditions, table), qualified_expression(e.default, table), qualified_expression(e.expression, table))
816
+ args = [qualified_expression(e.conditions, table), qualified_expression(e.default, table)]
817
+ args << qualified_expression(e.expression, table) if e.expression?
818
+ SQL::CaseExpression.new(*args)
804
819
  when SQL::Cast
805
820
  SQL::Cast.new(qualified_expression(e.expr, table), e.type)
806
821
  when SQL::Function
807
822
  SQL::Function.new(e.f, *qualified_expression(e.args, table))
808
823
  when SQL::ComplexExpression
809
824
  SQL::ComplexExpression.new(e.op, *qualified_expression(e.args, table))
810
- when SQL::SQLArray
811
- SQL::SQLArray.new(qualified_expression(e.array, table))
812
825
  when SQL::Subscript
813
826
  SQL::Subscript.new(qualified_expression(e.f, table), qualified_expression(e.sub, table))
814
827
  when SQL::WindowFunction