sequel 3.21.0 → 3.28.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 (158) hide show
  1. data/CHANGELOG +294 -0
  2. data/README.rdoc +20 -6
  3. data/Rakefile +20 -15
  4. data/doc/association_basics.rdoc +210 -43
  5. data/doc/dataset_basics.rdoc +4 -4
  6. data/doc/mass_assignment.rdoc +54 -0
  7. data/doc/migration.rdoc +15 -538
  8. data/doc/model_hooks.rdoc +64 -27
  9. data/doc/opening_databases.rdoc +37 -10
  10. data/doc/prepared_statements.rdoc +16 -10
  11. data/doc/reflection.rdoc +8 -2
  12. data/doc/release_notes/3.22.0.txt +39 -0
  13. data/doc/release_notes/3.23.0.txt +172 -0
  14. data/doc/release_notes/3.24.0.txt +420 -0
  15. data/doc/release_notes/3.25.0.txt +88 -0
  16. data/doc/release_notes/3.26.0.txt +88 -0
  17. data/doc/release_notes/3.27.0.txt +82 -0
  18. data/doc/release_notes/3.28.0.txt +304 -0
  19. data/doc/schema_modification.rdoc +547 -0
  20. data/doc/testing.rdoc +106 -0
  21. data/doc/transactions.rdoc +97 -0
  22. data/doc/virtual_rows.rdoc +2 -2
  23. data/lib/sequel/adapters/ado.rb +12 -1
  24. data/lib/sequel/adapters/amalgalite.rb +4 -0
  25. data/lib/sequel/adapters/db2.rb +95 -58
  26. data/lib/sequel/adapters/do.rb +12 -0
  27. data/lib/sequel/adapters/firebird.rb +25 -203
  28. data/lib/sequel/adapters/ibmdb.rb +440 -0
  29. data/lib/sequel/adapters/informix.rb +4 -19
  30. data/lib/sequel/adapters/jdbc/as400.rb +0 -7
  31. data/lib/sequel/adapters/jdbc/db2.rb +49 -0
  32. data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
  33. data/lib/sequel/adapters/jdbc/h2.rb +16 -5
  34. data/lib/sequel/adapters/jdbc/informix.rb +31 -0
  35. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  36. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  37. data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
  38. data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
  39. data/lib/sequel/adapters/jdbc/postgresql.rb +6 -0
  40. data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
  41. data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
  42. data/lib/sequel/adapters/jdbc.rb +62 -29
  43. data/lib/sequel/adapters/mysql.rb +22 -139
  44. data/lib/sequel/adapters/mysql2.rb +9 -14
  45. data/lib/sequel/adapters/odbc/db2.rb +21 -0
  46. data/lib/sequel/adapters/odbc.rb +15 -3
  47. data/lib/sequel/adapters/oracle.rb +17 -1
  48. data/lib/sequel/adapters/postgres.rb +111 -16
  49. data/lib/sequel/adapters/shared/access.rb +21 -0
  50. data/lib/sequel/adapters/shared/db2.rb +290 -0
  51. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  52. data/lib/sequel/adapters/shared/informix.rb +45 -0
  53. data/lib/sequel/adapters/shared/mssql.rb +85 -47
  54. data/lib/sequel/adapters/shared/mysql.rb +50 -7
  55. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +143 -0
  56. data/lib/sequel/adapters/shared/oracle.rb +0 -4
  57. data/lib/sequel/adapters/shared/postgres.rb +75 -43
  58. data/lib/sequel/adapters/shared/sqlite.rb +56 -8
  59. data/lib/sequel/adapters/sqlite.rb +12 -11
  60. data/lib/sequel/adapters/swift/mysql.rb +9 -0
  61. data/lib/sequel/adapters/tinytds.rb +139 -7
  62. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
  63. data/lib/sequel/ast_transformer.rb +190 -0
  64. data/lib/sequel/connection_pool/threaded.rb +3 -2
  65. data/lib/sequel/connection_pool.rb +1 -1
  66. data/lib/sequel/core.rb +6 -5
  67. data/lib/sequel/database/connecting.rb +5 -5
  68. data/lib/sequel/database/dataset.rb +1 -1
  69. data/lib/sequel/database/dataset_defaults.rb +1 -1
  70. data/lib/sequel/database/logging.rb +1 -1
  71. data/lib/sequel/database/misc.rb +38 -17
  72. data/lib/sequel/database/query.rb +50 -19
  73. data/lib/sequel/database/schema_generator.rb +8 -5
  74. data/lib/sequel/database/schema_methods.rb +52 -27
  75. data/lib/sequel/dataset/actions.rb +167 -48
  76. data/lib/sequel/dataset/features.rb +57 -8
  77. data/lib/sequel/dataset/graph.rb +1 -1
  78. data/lib/sequel/dataset/misc.rb +39 -20
  79. data/lib/sequel/dataset/mutation.rb +3 -3
  80. data/lib/sequel/dataset/prepared_statements.rb +29 -14
  81. data/lib/sequel/dataset/query.rb +182 -32
  82. data/lib/sequel/dataset/sql.rb +31 -58
  83. data/lib/sequel/dataset.rb +8 -0
  84. data/lib/sequel/exceptions.rb +4 -0
  85. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  86. data/lib/sequel/extensions/migration.rb +6 -4
  87. data/lib/sequel/extensions/to_dot.rb +95 -83
  88. data/lib/sequel/model/associations.rb +893 -309
  89. data/lib/sequel/model/base.rb +302 -105
  90. data/lib/sequel/model/errors.rb +1 -1
  91. data/lib/sequel/model/exceptions.rb +5 -1
  92. data/lib/sequel/model.rb +13 -7
  93. data/lib/sequel/plugins/association_pks.rb +22 -4
  94. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  95. data/lib/sequel/plugins/identity_map.rb +113 -6
  96. data/lib/sequel/plugins/many_through_many.rb +67 -5
  97. data/lib/sequel/plugins/prepared_statements.rb +140 -0
  98. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  99. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  100. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  101. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  102. data/lib/sequel/plugins/sharding.rb +12 -20
  103. data/lib/sequel/plugins/single_table_inheritance.rb +2 -0
  104. data/lib/sequel/plugins/update_primary_key.rb +1 -1
  105. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  106. data/lib/sequel/sql.rb +107 -51
  107. data/lib/sequel/timezones.rb +12 -3
  108. data/lib/sequel/version.rb +1 -1
  109. data/spec/adapters/db2_spec.rb +146 -0
  110. data/spec/adapters/mssql_spec.rb +36 -0
  111. data/spec/adapters/mysql_spec.rb +36 -19
  112. data/spec/adapters/postgres_spec.rb +115 -28
  113. data/spec/adapters/spec_helper.rb +6 -0
  114. data/spec/adapters/sqlite_spec.rb +11 -0
  115. data/spec/core/connection_pool_spec.rb +62 -77
  116. data/spec/core/database_spec.rb +244 -287
  117. data/spec/core/dataset_spec.rb +383 -34
  118. data/spec/core/expression_filters_spec.rb +159 -41
  119. data/spec/core/schema_spec.rb +326 -3
  120. data/spec/core/spec_helper.rb +45 -0
  121. data/spec/extensions/association_pks_spec.rb +38 -0
  122. data/spec/extensions/columns_introspection_spec.rb +91 -0
  123. data/spec/extensions/defaults_setter_spec.rb +64 -0
  124. data/spec/extensions/identity_map_spec.rb +162 -0
  125. data/spec/extensions/many_through_many_spec.rb +195 -20
  126. data/spec/extensions/migration_spec.rb +17 -17
  127. data/spec/extensions/nested_attributes_spec.rb +1 -0
  128. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  129. data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
  130. data/spec/extensions/prepared_statements_spec.rb +72 -0
  131. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  132. data/spec/extensions/schema_dumper_spec.rb +2 -2
  133. data/spec/extensions/schema_spec.rb +12 -20
  134. data/spec/extensions/serialization_modification_detection_spec.rb +36 -0
  135. data/spec/extensions/single_table_inheritance_spec.rb +11 -0
  136. data/spec/extensions/spec_helper.rb +3 -1
  137. data/spec/extensions/to_dot_spec.rb +3 -5
  138. data/spec/extensions/xml_serializer_spec.rb +16 -4
  139. data/spec/integration/associations_test.rb +405 -15
  140. data/spec/integration/database_test.rb +4 -2
  141. data/spec/integration/dataset_test.rb +240 -20
  142. data/spec/integration/plugin_test.rb +142 -5
  143. data/spec/integration/prepared_statement_test.rb +174 -95
  144. data/spec/integration/schema_test.rb +128 -16
  145. data/spec/integration/spec_helper.rb +15 -0
  146. data/spec/integration/transaction_test.rb +40 -0
  147. data/spec/integration/type_test.rb +16 -2
  148. data/spec/model/association_reflection_spec.rb +91 -0
  149. data/spec/model/associations_spec.rb +476 -5
  150. data/spec/model/base_spec.rb +91 -1
  151. data/spec/model/eager_loading_spec.rb +519 -31
  152. data/spec/model/hooks_spec.rb +161 -0
  153. data/spec/model/model_spec.rb +89 -2
  154. data/spec/model/plugins_spec.rb +17 -0
  155. data/spec/model/record_spec.rb +184 -12
  156. data/spec/model/spec_helper.rb +5 -0
  157. data/spec/model/validations_spec.rb +11 -0
  158. metadata +85 -34
@@ -0,0 +1,82 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The prepared_statements_safe plugin modifies the model to reduce the number of
4
+ # prepared statements that can be created, by setting as many columns as possible
5
+ # before creating, and by changing +save_changes+ to save all columns instead of
6
+ # just the changed ones.
7
+ #
8
+ # This plugin depends on the +prepared_statements+ plugin.
9
+ #
10
+ # Usage:
11
+ #
12
+ # # Make all model subclasses more safe when using prepared statements (called before loading subclasses)
13
+ # Sequel::Model.plugin :prepared_statements_safe
14
+ #
15
+ # # Make the Album class more safe when using prepared statements
16
+ # Album.plugin :prepared_statements_safe
17
+ module PreparedStatementsSafe
18
+ # Depend on the prepared_statements plugin
19
+ def self.apply(model)
20
+ model.plugin(:prepared_statements)
21
+ end
22
+
23
+ # Set the column defaults to use when creating on the model.
24
+ def self.configure(model)
25
+ model.send(:set_prepared_statements_column_defaults)
26
+ end
27
+
28
+ module ClassMethods
29
+ # A hash with column symbol keys and default values. Instance's
30
+ # values are merged into this hash before creating to reduce the
31
+ # number of free columns (columns that may or may not be present
32
+ # in the INSERT statement), as the number of prepared statements
33
+ # that can be created is 2^N (where N is the number of free columns).
34
+ attr_reader :prepared_statements_column_defaults
35
+
36
+ def inherited(subclass)
37
+ super
38
+ subclass.instance_variable_set(:@prepared_statements_column_defaults, @prepared_statements_column_defaults) if @prepared_statements_column_defaults && !subclass.prepared_statements_column_defaults
39
+ end
40
+
41
+ # Set the column defaults to use when creating on the subclass.
42
+ def set_dataset(*)
43
+ x = super
44
+ set_prepared_statements_column_defaults
45
+ x
46
+ end
47
+
48
+ private
49
+
50
+ # Set the column defaults based on the database schema. All columns
51
+ # are set to a default value unless they are a primary key column or
52
+ # they don't have a parseable default.
53
+ def set_prepared_statements_column_defaults
54
+ if db_schema
55
+ h = {}
56
+ db_schema.each do |k, v|
57
+ h[k] = v[:ruby_default] if (v[:ruby_default] || !v[:default]) && !v[:primary_key]
58
+ end
59
+ @prepared_statements_column_defaults = h
60
+ end
61
+ end
62
+ end
63
+
64
+ module InstanceMethods
65
+ # Merge the current values into the default values to reduce the number
66
+ # of free columns.
67
+ def before_create
68
+ if v = model.prepared_statements_column_defaults
69
+ set_values(v.merge(values))
70
+ end
71
+ super
72
+ end
73
+
74
+ # Always do a full save of all columns to reduce the number of prepared
75
+ # statements that can be used.
76
+ def save_changes(opts={})
77
+ save(opts) || false if modified?
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,59 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The prepared_statements_with_pk plugin allows Dataset#with_pk for model datasets
4
+ # to use prepared statements by extract the values of previously bound variables
5
+ # using <tt>Dataset#unbind</tt>, and attempting to use a prepared statement if the
6
+ # variables can be unbound correctly. See +Unbinder+ for details about what types of
7
+ # dataset filters can be unbound correctly.
8
+ #
9
+ # This plugin depends on the +prepared_statements+ plugin and should be considered unsafe.
10
+ # Unbinding dataset values cannot be done correctly in all cases, and use of this plugin
11
+ # in cases where not there are variables that are not unbound can lead to an denial of
12
+ # service attack by allocating an arbitrary number of prepared statements. You have been
13
+ # warned.
14
+ #
15
+ # Usage:
16
+ #
17
+ # # Make all model subclasses use prepared statements for Dataset#with_pk (called before loading subclasses)
18
+ # Sequel::Model.plugin :prepared_statements_with_pk
19
+ #
20
+ # # Make the Album class use prepared statements for Dataset#with_pk
21
+ # Album.plugin :prepared_statements_with_pk
22
+ module PreparedStatementsWithPk
23
+ # Depend on the prepared_statements plugin
24
+ def self.apply(model)
25
+ model.plugin(:prepared_statements)
26
+ end
27
+
28
+ module ClassMethods
29
+ private
30
+
31
+ # Return a prepared statement that can be used to lookup a row given a dataset for the row matching
32
+ # the primary key.
33
+ def prepared_lookup_dataset(ds)
34
+ @prepared_statements[:lookup_sql][ds.sql] ||= prepare_statement(ds.filter(prepared_statement_key_array(primary_key).map{|k, v| [SQL::QualifiedIdentifier.new(ds.model.table_name, k), v]}), :first)
35
+ end
36
+ end
37
+
38
+ module DatasetMethods
39
+ # Use a prepared statement to find a row with the matching primary key
40
+ # inside this dataset.
41
+ def with_pk(pk)
42
+ begin
43
+ ds, bv = unbind
44
+ rescue UnbindDuplicate
45
+ super
46
+ else
47
+ begin
48
+ bv = bv.merge!(model.primary_key_hash(pk)){|k, v1, v2| ((v1 == v2) ? v1 : raise(UnbindDuplicate))}
49
+ rescue UnbindDuplicate
50
+ super
51
+ else
52
+ model.send(:prepared_lookup_dataset, ds).call(bv)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,51 @@
1
+ module Sequel
2
+ module Plugins
3
+ # Sequel's built in Serialization plugin doesn't check for modification
4
+ # of the serialized objects, because it requires an extra deserialization of a potentially
5
+ # very large object. This plugin can detect changes in serialized values by
6
+ # checking whether the current deserialized value is the same as the original
7
+ # deserialized value. This does require deserializing the value twice, but the
8
+ # original deserialized value is cached.
9
+ #
10
+ # == Example
11
+ #
12
+ # require 'sequel'
13
+ # require 'json'
14
+ # class User < Sequel::Model
15
+ # plugin :serialization, :json, :permissions
16
+ # plugin :serialization_modification_detection
17
+ # end
18
+ # user = User.create(:permissions => {})
19
+ # user.permissions[:global] = 'read-only'
20
+ # user.save_changes
21
+ module SerializationModificationDetection
22
+ # Load the serialization plugin automatically.
23
+ def self.apply(model)
24
+ model.plugin :serialization
25
+ end
26
+
27
+ module InstanceMethods
28
+ # Detect which serialized columns have changed.
29
+ def changed_columns
30
+ cc = super
31
+ deserialized_values.each{|c, v| cc << c if !cc.include?(c) && original_deserialized_value(c) != v}
32
+ cc
33
+ end
34
+
35
+ private
36
+
37
+ # Clear the cache of original deserialized values after saving so that it doesn't
38
+ # show the column is modified after saving.
39
+ def after_save
40
+ super
41
+ @original_deserialized_values.clear if @original_deserialized_values
42
+ end
43
+
44
+ # Return the original deserialized value of the column, caching it to improve performance.
45
+ def original_deserialized_value(column)
46
+ (@original_deserialized_values ||= {})[column] ||= deserialize_value(column, self[column])
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -40,6 +40,18 @@ module Sequel
40
40
  def new_using_server(s, values={}, &block)
41
41
  new(values, &block).set_server(s)
42
42
  end
43
+
44
+ private
45
+
46
+ # Set the server for each graphed dataset to the current server
47
+ # unless the graphed dataset already has a server set.
48
+ def eager_graph_dataset(opts, eager_options)
49
+ ds = super
50
+ if s = eager_options[:self].opts[:server]
51
+ ds = ds.server(s) unless ds.opts[:server]
52
+ end
53
+ ds
54
+ end
43
55
  end
44
56
 
45
57
  module InstanceMethods
@@ -109,26 +121,6 @@ module Sequel
109
121
  end
110
122
  ds
111
123
  end
112
-
113
- private
114
-
115
- # Set the shard of all retrieved objects to the shard of
116
- # the table's dataset, or the current shard if the table's
117
- # dataset does not have a shard.
118
- def graph_each
119
- ta = @opts[:graph][:table_aliases]
120
- s = @opts[:server]
121
- super do |r|
122
- r.each do |k, v|
123
- if ds = ta[k]
124
- dss = ds.opts[:server]
125
- end
126
- vs = dss || s
127
- v.set_server(vs) if vs && v.respond_to?(:set_server)
128
- end
129
- yield r
130
- end
131
- end
132
124
  end
133
125
  end
134
126
  end
@@ -108,8 +108,10 @@ module Sequel
108
108
  key = skm[subclass]
109
109
  sti_subclass_added(key)
110
110
  ska = [key]
111
+ rp = dataset.row_proc
111
112
  subclass.set_dataset(sd.filter(SQL::QualifiedIdentifier.new(table_name, sk)=>ska), :inherited=>true)
112
113
  subclass.instance_eval do
114
+ dataset.row_proc = rp
113
115
  @sti_key = sk
114
116
  @sti_key_array = ska
115
117
  @sti_dataset = sd
@@ -1,6 +1,6 @@
1
1
  module Sequel
2
2
  module Plugins
3
- # The update_primary_key plugin allows you to modify an objects
3
+ # The update_primary_key plugin allows you to modify an object's
4
4
  # primary key and then save the record. Sequel does not work
5
5
  # correctly with primary key modifications by default. Sequel
6
6
  # is designed to work with surrogate primary keys that never need to be
@@ -205,9 +205,9 @@ module Sequel
205
205
  klass.from_xml_node(node)
206
206
  end
207
207
  elsif cols.include?(k)
208
- self[k.to_sym] = node[:nil] ? nil : node.children.first.to_s
208
+ self[k.to_sym] = node.key?('nil') ? nil : node.children.first.to_s
209
209
  elsif meths.include?("#{k}=")
210
- send("#{k}=", node[:nil] ? nil : node.children.first.to_s)
210
+ send("#{k}=", node.key?('nil') ? nil : node.children.first.to_s)
211
211
  else
212
212
  raise Error, "Entry in XML not an association or column and no setter method exists: #{k}"
213
213
  end
@@ -266,7 +266,7 @@ module Sequel
266
266
 
267
267
  name_proc = model.xml_serialize_name_proc(opts)
268
268
  x = model.xml_builder(opts)
269
- x.send(name_proc[opts.fetch(:root_name, model.send(:underscore, model.name)).to_s]) do |x1|
269
+ x.send(name_proc[opts.fetch(:root_name, model.send(:underscore, model.name).gsub('/', '__')).to_s]) do |x1|
270
270
  cols.each do |c|
271
271
  attrs = {}
272
272
  if types
data/lib/sequel/sql.rb CHANGED
@@ -38,6 +38,16 @@ module Sequel
38
38
  class LiteralString < ::String
39
39
  end
40
40
 
41
+ # Time subclass that gets literalized with only the time value, so it operates
42
+ # like a standard SQL time type.
43
+ class SQLTime < ::Time
44
+ # Create a new SQLTime instance given an hour, minute, and second.
45
+ def self.create(hour, minute, second, usec = 0)
46
+ t = now
47
+ local(t.year, t.month, t.day, hour, minute, second, usec)
48
+ end
49
+ end
50
+
41
51
  # The SQL module holds classes whose instances represent SQL fragments.
42
52
  # It also holds modules that are included in core ruby classes that
43
53
  # make Sequel a friendly DSL.
@@ -144,14 +154,17 @@ module Sequel
144
154
  # Operator symbols that take exactly two arguments
145
155
  TWO_ARITY_OPERATORS = [:'=', :'!=', :LIKE, :'NOT LIKE', \
146
156
  :~, :'!~', :'~*', :'!~*', :ILIKE, :'NOT ILIKE'] + \
147
- INEQUALITY_OPERATORS + BITWISE_OPERATORS + IS_OPERATORS + IN_OPERATORS
157
+ INEQUALITY_OPERATORS + IS_OPERATORS + IN_OPERATORS
148
158
 
149
159
  # Operator symbols that take one or more arguments
150
- N_ARITY_OPERATORS = [:AND, :OR, :'||'] + MATHEMATICAL_OPERATORS
160
+ N_ARITY_OPERATORS = [:AND, :OR, :'||'] + MATHEMATICAL_OPERATORS + BITWISE_OPERATORS
151
161
 
152
162
  # Operator symbols that take only a single argument
153
163
  ONE_ARITY_OPERATORS = [:NOT, :NOOP, :'B~']
154
164
 
165
+ # Custom expressions that may have different syntax on different databases
166
+ CUSTOM_EXPRESSIONS = [:extract]
167
+
155
168
  # An array of args for this object
156
169
  attr_reader :args
157
170
 
@@ -180,6 +193,8 @@ module Sequel
180
193
  args[1] = orig_args[1] if IN_OPERATORS.include?(op)
181
194
  when *ONE_ARITY_OPERATORS
182
195
  raise(Error, "The #{op} operator requires a single argument") unless args.length == 1
196
+ when *CUSTOM_EXPRESSIONS
197
+ # nothing
183
198
  else
184
199
  raise(Error, "Invalid operator #{op}")
185
200
  end
@@ -219,14 +234,7 @@ module Sequel
219
234
  # ~:a.sql_number # ~"a"
220
235
  module BitwiseMethods
221
236
  ComplexExpression::BITWISE_OPERATORS.each do |o|
222
- define_method(o) do |ce|
223
- case ce
224
- when BooleanExpression, StringExpression
225
- raise(Sequel::Error, "cannot apply #{o} to a non-numeric expression")
226
- else
227
- NumericExpression.new(o, self, ce)
228
- end
229
- end
237
+ module_eval("def #{o}(o) NumericExpression.new(#{o.inspect}, self, o) end", __FILE__, __LINE__)
230
238
  end
231
239
 
232
240
  # Do the bitwise compliment of the self
@@ -244,16 +252,24 @@ module Sequel
244
252
  # :a & :b # "a" AND "b"
245
253
  # :a | :b # "a" OR "b"
246
254
  # ~:a # NOT "a"
255
+ #
256
+ # One exception to this is when a NumericExpression or Integer is the argument
257
+ # to & or |, in which case a bitwise method will be used:
258
+ #
259
+ # :a & 1 # "a" & 1
260
+ # :a | (:b + 1) # "a" | ("b" + 1)
247
261
  module BooleanMethods
248
262
  ComplexExpression::BOOLEAN_OPERATOR_METHODS.each do |m, o|
249
- define_method(m) do |ce|
250
- case ce
251
- when NumericExpression, StringExpression
252
- raise(Sequel::Error, "cannot apply #{o} to a non-boolean expression")
253
- else
254
- BooleanExpression.new(o, self, ce)
263
+ module_eval(<<-END, __FILE__, __LINE__+1)
264
+ def #{m}(o)
265
+ case o
266
+ when NumericExpression, Integer
267
+ NumericExpression.new(#{m.inspect}, self, o)
268
+ else
269
+ BooleanExpression.new(#{o.inspect}, self, o)
270
+ end
255
271
  end
256
- end
272
+ END
257
273
  end
258
274
 
259
275
  # Create a new BooleanExpression with NOT, representing the inversion of whatever self represents.
@@ -321,7 +337,7 @@ module Sequel
321
337
  # doesn't use the standard function calling convention, and it
322
338
  # doesn't work on all databases.
323
339
  def extract(datetime_part)
324
- Function.new(:extract, PlaceholderLiteralString.new("#{datetime_part} FROM ?", [self])).sql_number
340
+ NumericExpression.new(:extract, datetime_part, self)
325
341
  end
326
342
 
327
343
  # Return a BooleanExpression representation of +self+.
@@ -367,34 +383,12 @@ module Sequel
367
383
  # 'a'.lit <= :b # a <= "b"
368
384
  module InequalityMethods
369
385
  ComplexExpression::INEQUALITY_OPERATORS.each do |o|
370
- define_method(o) do |ce|
371
- case ce
372
- when BooleanExpression, TrueClass, FalseClass, NilClass, Hash, ::Array
373
- raise(Error, "cannot apply #{o} to a boolean expression")
374
- else
375
- BooleanExpression.new(o, self, ce)
376
- end
377
- end
386
+ module_eval("def #{o}(o) BooleanExpression.new(#{o.inspect}, self, o) end", __FILE__, __LINE__)
378
387
  end
379
388
  end
380
389
 
381
- # This module augments the default initalize method for the
382
- # +ComplexExpression+ subclass it is included in, so that
383
- # attempting to use boolean input when initializing a +NumericExpression+
384
- # or +StringExpression+ results in an error. It is not expected to be
385
- # used directly.
390
+ # Only exists for backwards compatibility, ignore it.
386
391
  module NoBooleanInputMethods
387
- # Raise an +Error+ if one of the args would be boolean in an SQL
388
- # context, otherwise call super.
389
- def initialize(op, *args)
390
- args.each do |a|
391
- case a
392
- when BooleanExpression, TrueClass, FalseClass, NilClass, Hash, ::Array
393
- raise(Error, "cannot apply #{op} to a boolean expression")
394
- end
395
- end
396
- super
397
- end
398
392
  end
399
393
 
400
394
  # This module includes the standard mathematical methods (+, -, *, and /)
@@ -405,15 +399,26 @@ module Sequel
405
399
  # :a - :b # "a" - "b"
406
400
  # :a * :b # "a" * "b"
407
401
  # :a / :b # "a" / "b"
402
+ #
403
+ # One exception to this is if + is called with a +String+ or +StringExpression+,
404
+ # in which case the || operator is used instead of the + operator:
405
+ #
406
+ # :a + 'b' # "a" || 'b'
408
407
  module NumericMethods
409
408
  ComplexExpression::MATHEMATICAL_OPERATORS.each do |o|
410
- define_method(o) do |ce|
411
- case ce
412
- when BooleanExpression, StringExpression
413
- raise(Sequel::Error, "cannot apply #{o} to a non-numeric expression")
414
- else
415
- NumericExpression.new(o, self, ce)
416
- end
409
+ module_eval("def #{o}(o) NumericExpression.new(#{o.inspect}, self, o) end", __FILE__, __LINE__) unless o == :+
410
+ end
411
+
412
+ # Use || as the operator when called with StringExpression and String instances,
413
+ # and the + operator for LiteralStrings and all other types.
414
+ def +(ce)
415
+ case ce
416
+ when LiteralString
417
+ NumericExpression.new(:+, self, ce)
418
+ when StringExpression, String
419
+ StringExpression.new(:'||', self, ce)
420
+ else
421
+ NumericExpression.new(:+, self, ce)
417
422
  end
418
423
  end
419
424
  end
@@ -595,10 +600,27 @@ module Sequel
595
600
  end
596
601
  when StringExpression, NumericExpression
597
602
  raise(Sequel::Error, "cannot invert #{ce.inspect}")
603
+ when Constant
604
+ CONSTANT_INVERSIONS[ce] || raise(Sequel::Error, "cannot invert #{ce.inspect}")
598
605
  else
599
606
  BooleanExpression.new(:NOT, ce)
600
607
  end
601
608
  end
609
+
610
+ # Always use an AND operator for & on BooleanExpressions
611
+ def &(ce)
612
+ BooleanExpression.new(:AND, self, ce)
613
+ end
614
+
615
+ # Always use an OR operator for | on BooleanExpressions
616
+ def |(ce)
617
+ BooleanExpression.new(:OR, self, ce)
618
+ end
619
+
620
+ # Return self instead of creating a new object to save on memory.
621
+ def sql_boolean
622
+ self
623
+ end
602
624
  end
603
625
 
604
626
  # Represents an SQL CASE expression, used for conditional branching in SQL.
@@ -666,6 +688,21 @@ module Sequel
666
688
  include CastMethods
667
689
  include OrderMethods
668
690
  include SubscriptMethods
691
+
692
+ # Return a BooleanExpression with the same op and args.
693
+ def sql_boolean
694
+ BooleanExpression.new(self.op, *self.args)
695
+ end
696
+
697
+ # Return a NumericExpression with the same op and args.
698
+ def sql_number
699
+ NumericExpression.new(self.op, *self.args)
700
+ end
701
+
702
+ # Return a StringExpression with the same op and args.
703
+ def sql_string
704
+ StringExpression.new(self.op, *self.args)
705
+ end
669
706
  end
670
707
 
671
708
  # Represents constants or psuedo-constants (e.g. +CURRENT_DATE+) in SQL.
@@ -706,6 +743,12 @@ module Sequel
706
743
  NOTNULL = NegativeBooleanConstant.new(nil)
707
744
  end
708
745
 
746
+ class ComplexExpression
747
+ # A hash of the opposite for each constant, used for inverting constants.
748
+ CONSTANT_INVERSIONS = {Constants::TRUE=>Constants::FALSE, Constants::FALSE=>Constants::TRUE,
749
+ Constants::NULL=>Constants::NOTNULL, Constants::NOTNULL=>Constants::NULL}
750
+ end
751
+
709
752
  # Represents an SQL function call.
710
753
  class Function < GenericExpression
711
754
  # The array of arguments to pass to the function (may be blank)
@@ -834,7 +877,16 @@ module Sequel
834
877
  include BitwiseMethods
835
878
  include NumericMethods
836
879
  include InequalityMethods
837
- include NoBooleanInputMethods
880
+
881
+ # Always use + for + operator for NumericExpressions.
882
+ def +(ce)
883
+ NumericExpression.new(:+, self, ce)
884
+ end
885
+
886
+ # Return self instead of creating a new object to save on memory.
887
+ def sql_number
888
+ self
889
+ end
838
890
  end
839
891
 
840
892
  # Represents a column/expression to order the result set by.
@@ -900,7 +952,6 @@ module Sequel
900
952
  include StringMethods
901
953
  include StringConcatenationMethods
902
954
  include InequalityMethods
903
- include NoBooleanInputMethods
904
955
 
905
956
  # Map of [regexp, case_insenstive] to +ComplexExpression+ operator symbol
906
957
  LIKE_MAP = {[true, true]=>:'~*', [true, false]=>:~, [false, true]=>:ILIKE, [false, false]=>:LIKE}
@@ -949,6 +1000,11 @@ module Sequel
949
1000
  end
950
1001
  end
951
1002
  private_class_method :like_element
1003
+
1004
+ # Return self instead of creating a new object to save on memory.
1005
+ def sql_string
1006
+ self
1007
+ end
952
1008
  end
953
1009
 
954
1010
  # Represents an SQL array access, with multiple possible arguments.
@@ -109,6 +109,8 @@ module Sequel
109
109
  else
110
110
  Time.send(input_timezone == :utc ? :utc : :local, y, mo, d, h, mi, s)
111
111
  end
112
+ when Hash
113
+ convert_input_timestamp([:year, :month, :day, :hour, :minute, :second].map{|x| (v[x] || v[x.to_s]).to_i}, input_timezone)
112
114
  when Time
113
115
  if datetime_class == DateTime
114
116
  v.respond_to?(:to_datetime) ? v.to_datetime : string_to_datetime(v.iso8601)
@@ -121,8 +123,6 @@ module Sequel
121
123
  else
122
124
  v.respond_to?(:to_time) ? v.to_time : string_to_datetime(v.to_s)
123
125
  end
124
- when Date
125
- convert_input_timestamp(v.to_s, input_timezone)
126
126
  else
127
127
  raise InvalidValue, "Invalid convert_input_timestamp type: #{v.inspect}"
128
128
  end
@@ -160,7 +160,16 @@ module Sequel
160
160
  # +convert_output_timestamp+.
161
161
  def convert_timestamp(v, input_timezone)
162
162
  begin
163
- convert_output_timestamp(convert_input_timestamp(v, input_timezone), Sequel.application_timezone)
163
+ if v.is_a?(Date) && !v.is_a?(DateTime)
164
+ # Dates handled specially as they are assumed to already be in the application_timezone
165
+ if datetime_class == DateTime
166
+ DateTime.civil(v.year, v.month, v.day, 0, 0, 0, application_timezone == :local ? (defined?(Rational) ? Rational(Time.local(v.year, v.month, v.day).utc_offset, 86400) : Time.local(v.year, v.month, v.day).utc_offset/86400.0) : 0)
167
+ else
168
+ Time.send(application_timezone == :utc ? :utc : :local, v.year, v.month, v.day)
169
+ end
170
+ else
171
+ convert_output_timestamp(convert_input_timestamp(v, input_timezone), application_timezone)
172
+ end
164
173
  rescue InvalidValue
165
174
  raise
166
175
  rescue => e
@@ -3,7 +3,7 @@ module Sequel
3
3
  MAJOR = 3
4
4
  # The minor version of Sequel. Bumped for every non-patch level
5
5
  # release, generally around once a month.
6
- MINOR = 21
6
+ MINOR = 28
7
7
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
8
8
  # releases that fix regressions from previous versions.
9
9
  TINY = 0