sequel 4.10.0 → 4.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +58 -0
  3. data/doc/association_basics.rdoc +1 -1
  4. data/doc/cheat_sheet.rdoc +0 -1
  5. data/doc/core_extensions.rdoc +2 -2
  6. data/doc/dataset_filtering.rdoc +5 -5
  7. data/doc/model_hooks.rdoc +9 -0
  8. data/doc/object_model.rdoc +7 -13
  9. data/doc/opening_databases.rdoc +3 -1
  10. data/doc/querying.rdoc +8 -8
  11. data/doc/release_notes/4.11.0.txt +147 -0
  12. data/doc/sql.rdoc +11 -7
  13. data/doc/virtual_rows.rdoc +4 -5
  14. data/lib/sequel/adapters/ibmdb.rb +24 -16
  15. data/lib/sequel/adapters/jdbc/h2.rb +5 -0
  16. data/lib/sequel/adapters/jdbc/hsqldb.rb +5 -0
  17. data/lib/sequel/adapters/mock.rb +14 -2
  18. data/lib/sequel/adapters/shared/access.rb +6 -9
  19. data/lib/sequel/adapters/shared/cubrid.rb +5 -0
  20. data/lib/sequel/adapters/shared/db2.rb +5 -0
  21. data/lib/sequel/adapters/shared/firebird.rb +5 -0
  22. data/lib/sequel/adapters/shared/mssql.rb +23 -16
  23. data/lib/sequel/adapters/shared/mysql.rb +12 -2
  24. data/lib/sequel/adapters/shared/oracle.rb +31 -15
  25. data/lib/sequel/adapters/shared/postgres.rb +28 -4
  26. data/lib/sequel/adapters/shared/sqlanywhere.rb +5 -0
  27. data/lib/sequel/adapters/shared/sqlite.rb +12 -1
  28. data/lib/sequel/ast_transformer.rb +9 -7
  29. data/lib/sequel/connection_pool.rb +10 -4
  30. data/lib/sequel/database/features.rb +15 -0
  31. data/lib/sequel/database/schema_generator.rb +2 -2
  32. data/lib/sequel/database/schema_methods.rb +21 -3
  33. data/lib/sequel/database/transactions.rb +8 -4
  34. data/lib/sequel/dataset/actions.rb +13 -7
  35. data/lib/sequel/dataset/features.rb +7 -0
  36. data/lib/sequel/dataset/query.rb +28 -11
  37. data/lib/sequel/dataset/sql.rb +90 -14
  38. data/lib/sequel/extensions/constraint_validations.rb +2 -2
  39. data/lib/sequel/extensions/date_arithmetic.rb +1 -1
  40. data/lib/sequel/extensions/eval_inspect.rb +12 -6
  41. data/lib/sequel/extensions/pg_array_ops.rb +11 -2
  42. data/lib/sequel/extensions/pg_json.rb +130 -23
  43. data/lib/sequel/extensions/pg_json_ops.rb +196 -28
  44. data/lib/sequel/extensions/to_dot.rb +5 -7
  45. data/lib/sequel/model/associations.rb +0 -50
  46. data/lib/sequel/plugins/class_table_inheritance.rb +49 -21
  47. data/lib/sequel/plugins/many_through_many.rb +10 -11
  48. data/lib/sequel/plugins/serialization.rb +4 -1
  49. data/lib/sequel/plugins/sharding.rb +0 -9
  50. data/lib/sequel/plugins/single_table_inheritance.rb +4 -2
  51. data/lib/sequel/plugins/timestamps.rb +2 -2
  52. data/lib/sequel/sql.rb +166 -44
  53. data/lib/sequel/version.rb +1 -1
  54. data/spec/adapters/postgres_spec.rb +199 -133
  55. data/spec/core/connection_pool_spec.rb +6 -0
  56. data/spec/core/database_spec.rb +12 -0
  57. data/spec/core/dataset_spec.rb +58 -3
  58. data/spec/core/expression_filters_spec.rb +67 -5
  59. data/spec/core/mock_adapter_spec.rb +8 -4
  60. data/spec/core/schema_spec.rb +7 -0
  61. data/spec/core_extensions_spec.rb +14 -0
  62. data/spec/extensions/class_table_inheritance_spec.rb +23 -3
  63. data/spec/extensions/core_refinements_spec.rb +14 -0
  64. data/spec/extensions/eval_inspect_spec.rb +8 -4
  65. data/spec/extensions/pg_array_ops_spec.rb +6 -0
  66. data/spec/extensions/pg_json_ops_spec.rb +99 -0
  67. data/spec/extensions/pg_json_spec.rb +104 -4
  68. data/spec/extensions/serialization_spec.rb +19 -0
  69. data/spec/extensions/single_table_inheritance_spec.rb +11 -3
  70. data/spec/extensions/timestamps_spec.rb +10 -0
  71. data/spec/extensions/to_dot_spec.rb +8 -4
  72. data/spec/integration/database_test.rb +1 -1
  73. data/spec/integration/dataset_test.rb +9 -0
  74. data/spec/integration/schema_test.rb +27 -0
  75. metadata +4 -2
@@ -92,6 +92,7 @@ module Sequel
92
92
  dot "AliasedExpression"
93
93
  v(e.expression, :expression)
94
94
  v(e.alias, :alias)
95
+ v(e.columns, :columns) if e.columns
95
96
  when SQL::CaseExpression
96
97
  dot "CaseExpression"
97
98
  v(e.expression, :expression) if e.expression
@@ -102,18 +103,16 @@ module Sequel
102
103
  v(e.expr, :expr)
103
104
  v(e.type, :type)
104
105
  when SQL::Function
105
- dot "Function: #{e.f}"
106
+ dot "Function: #{e.name}"
106
107
  e.args.each_with_index do |val, j|
107
108
  v(val, j)
108
109
  end
110
+ v(e.args, :args)
111
+ v(e.opts, :opts)
109
112
  when SQL::Subscript
110
113
  dot "Subscript"
111
114
  v(e.f, :f)
112
115
  v(e.sub, :sub)
113
- when SQL::WindowFunction
114
- dot "WindowFunction"
115
- v(e.function, :function)
116
- v(e.window, :window)
117
116
  when SQL::Window
118
117
  dot "Window"
119
118
  v(e.opts, :opts)
@@ -130,8 +129,7 @@ module Sequel
130
129
  str << " USING"
131
130
  end
132
131
  dot str
133
- v(e.table, :table)
134
- v(e.table_alias, :alias) if e.table_alias
132
+ v(e.table_expr, :table)
135
133
  if e.is_a?(SQL::JoinOnClause)
136
134
  v(e.on, :on)
137
135
  elsif e.is_a?(SQL::JoinUsingClause)
@@ -1350,20 +1350,6 @@ module Sequel
1350
1350
  association_reflections.values
1351
1351
  end
1352
1352
 
1353
- # REMOVE410
1354
- def apply_association_dataset_opts(opts, ds)
1355
- Deprecation.deprecate("Model.apply_association_dataset_opts/Model.eager_loading_dataset", "Use AssociationReflection#apply_dataset_changes/Association#reflection#apply_eager_dataset_changes instead.")
1356
- ds = ds.select(*opts.select) if opts.select
1357
- if c = opts[:conditions]
1358
- ds = (c.is_a?(Array) && !Sequel.condition_specifier?(c)) ? ds.where(*c) : ds.where(c)
1359
- end
1360
- ds = ds.order(*opts[:order]) if opts[:order]
1361
- ds = ds.eager(opts[:eager]) if opts[:eager]
1362
- ds = ds.distinct if opts[:distinct]
1363
- ds = opts[:eager_block].call(ds) if opts[:eager_block]
1364
- ds
1365
- end
1366
-
1367
1353
  # Associates a related model with the current model. The following types are
1368
1354
  # supported:
1369
1355
  #
@@ -1646,22 +1632,6 @@ module Sequel
1646
1632
  opts.eager_load_results(eo, &block)
1647
1633
  end
1648
1634
 
1649
- # REMOVE410
1650
- def eager_loading_dataset(opts, ds, select, associations, eager_options=OPTS)
1651
- ds = apply_association_dataset_opts(opts, ds)
1652
- ds = ds.select(*select) if select
1653
- if opts[:eager_graph]
1654
- raise(Error, "cannot eagerly load a #{opts[:type]} association that uses :eager_graph") if opts.eager_loading_use_associated_key?
1655
- ds = ds.eager_graph(opts[:eager_graph])
1656
- end
1657
- ds = ds.eager(associations) unless Array(associations).empty?
1658
- ds = eager_options[:eager_block].call(ds) if eager_options[:eager_block]
1659
- if opts.eager_loading_use_associated_key?
1660
- ds = ds.select_append(*opts.associated_key_array)
1661
- end
1662
- ds
1663
- end
1664
-
1665
1635
  # Shortcut for adding a many_to_many association, see #associate
1666
1636
  def many_to_many(name, opts=OPTS, &block)
1667
1637
  associate(:many_to_many, name, opts, &block)
@@ -1711,19 +1681,6 @@ module Sequel
1711
1681
  association_module(opts).send(:private, name)
1712
1682
  end
1713
1683
 
1714
- # REMOVE410
1715
- def def_add_method(opts)
1716
- Deprecation.deprecate("Model.def_add_method", "The Model.associate method now sets up the add method you if an :adder association reflection entry is present.")
1717
- association_module_def(opts.add_method, opts){|o,*args| add_associated_object(opts, o, *args)}
1718
- end
1719
-
1720
- # REMOVE410
1721
- def def_association_dataset_methods(opts)
1722
- Deprecation.deprecate("Model.def_association_dataset_methods", "The Model.associate method now sets up the association dataset methods.")
1723
- association_module_def(opts.dataset_method, opts){_dataset(opts)}
1724
- def_association_method(opts)
1725
- end
1726
-
1727
1684
  # Adds the association method to the association methods module.
1728
1685
  def def_association_method(opts)
1729
1686
  association_module_def(opts.association_method, opts){|*dynamic_opts, &block| load_associated_objects(opts, dynamic_opts[0], &block)}
@@ -1996,13 +1953,6 @@ module Sequel
1996
1953
  def_one_to_many(opts)
1997
1954
  end
1998
1955
 
1999
- # REMOVE410
2000
- def def_remove_methods(opts)
2001
- Deprecation.deprecate("Model.def_remove_methods", "The Model.associate method now sets up the remove/remove_all methods for you if a :remover or :clearer association reflection entry is present.")
2002
- association_module_def(opts.remove_method, opts){|o,*args| remove_associated_object(opts, o, *args)}
2003
- association_module_def(opts.remove_all_method, opts){|*args| remove_all_associated_objects(opts, *args)}
2004
- end
2005
-
2006
1956
  # Return dataset to graph into given the association reflection, applying the :callback option if set.
2007
1957
  def eager_graph_dataset(opts, eager_options)
2008
1958
  ds = opts.associated_class.dataset
@@ -71,7 +71,9 @@ module Sequel
71
71
  # # You can also set options when loading the plugin:
72
72
  # # :kind :: column to hold the class name
73
73
  # # :table_map :: map of class name symbols to table name symbols
74
- # Employee.plugin :class_table_inheritance, :key=>:kind, :table_map=>{:Staff=>:staff}
74
+ # # :model_map :: map of column values to class name symbols
75
+ # Employee.plugin :class_table_inheritance, :key=>:kind, :table_map=>{:Staff=>:staff},
76
+ # :model_map=>{1=>:Employee, 2=>:Manager, 3=>:Executive, 4=>:Staff}
75
77
  module ClassTableInheritance
76
78
  # The class_table_inheritance plugin requires the lazy_attributes plugin
77
79
  # to handle lazily-loaded attributes for subclass instances returned
@@ -83,25 +85,26 @@ module Sequel
83
85
  # Initialize the per-model data structures and set the dataset's row_proc
84
86
  # to check for the :key option column for the type of class when loading objects.
85
87
  # Options:
86
- # * :key - The column symbol holding the name of the model class this
87
- # is an instance of. Necessary if you want to call model methods
88
- # using the superclass, but have them return subclass instances.
89
- # * :table_map - Hash with class name symbol keys and table name symbol
90
- # values. Necessary if the implicit table name for the model class
91
- # does not match the database table name
88
+ # :key :: The column symbol holding the name of the model class this
89
+ # is an instance of. Necessary if you want to call model methods
90
+ # using the superclass, but have them return subclass instances.
91
+ # :table_map :: Hash with class name symbol keys and table name symbol
92
+ # values. Necessary if the implicit table name for the model class
93
+ # does not match the database table name
94
+ # :model_map :: Hash with keys being values of the cti_key column, and values
95
+ # being class name strings or symbols. Used if you don't want to
96
+ # store class names in the database. If you use this option, you
97
+ # are responsible for setting the values of the cti_key column
98
+ # manually (usually in a before_create hook).
92
99
  def self.configure(model, opts=OPTS)
93
100
  model.instance_eval do
94
- m = method(:constantize)
95
101
  @cti_base_model = self
96
102
  @cti_key = key = opts[:key]
97
103
  @cti_tables = [table_name]
98
104
  @cti_columns = {table_name=>columns}
99
105
  @cti_table_map = opts[:table_map] || {}
100
- dataset.row_proc = if key
101
- lambda{|r| (m.call(r[key]) rescue model).call(r)}
102
- else
103
- model
104
- end
106
+ @cti_model_map = opts[:model_map]
107
+ set_dataset_cti_row_proc
105
108
  end
106
109
  end
107
110
 
@@ -120,6 +123,11 @@ module Sequel
120
123
  # load method.
121
124
  attr_reader :cti_key
122
125
 
126
+ # A hash with keys being values of the cti_key column, and values
127
+ # being class name strings or symbols. Used if you don't want to
128
+ # store class names in the database.
129
+ attr_reader :cti_model_map
130
+
123
131
  # An array of table symbols that back this model. The first is
124
132
  # cti_base_model table symbol, and the last is the current model
125
133
  # table symbol.
@@ -139,6 +147,7 @@ module Sequel
139
147
  ct = cti_tables.dup
140
148
  ctm = cti_table_map.dup
141
149
  cbm = cti_base_model
150
+ cmm = cti_model_map
142
151
  pk = primary_key
143
152
  ds = dataset
144
153
  subclass.instance_eval do
@@ -150,6 +159,7 @@ module Sequel
150
159
  @cti_columns = cc.merge(table=>columns)
151
160
  @cti_table_map = ctm
152
161
  @cti_base_model = cbm
162
+ @cti_model_map = cmm
153
163
  # Need to set dataset and columns before calling super so that
154
164
  # the main column accessor module is included in the class before any
155
165
  # plugin accessor modules (such as the lazy attributes accessor module).
@@ -158,12 +168,7 @@ module Sequel
158
168
  end
159
169
  super
160
170
  subclass.instance_eval do
161
- m = method(:constantize)
162
- dataset.row_proc = if cti_key
163
- lambda{|r| (m.call(r[ck]) rescue subclass).call(r)}
164
- else
165
- subclass
166
- end
171
+ set_dataset_cti_row_proc
167
172
  (columns - [cbm.primary_key]).each{|a| define_lazy_attribute_getter(a)}
168
173
  cti_tables.reverse.each do |table|
169
174
  db.schema(table).each{|k,v| db_schema[k] = v}
@@ -192,12 +197,35 @@ module Sequel
192
197
  ds.row_proc = @dataset.row_proc if @dataset
193
198
  end
194
199
 
200
+ # Set the row_proc for the model's dataset appropriately
201
+ # based on the cti key and model map.
202
+ def set_dataset_cti_row_proc
203
+ m = method(:constantize)
204
+ dataset.row_proc = if ck = cti_key
205
+ if model_map = cti_model_map
206
+ lambda do |r|
207
+ mod = if name = model_map[r[ck]]
208
+ m.call(name)
209
+ else
210
+ self
211
+ end
212
+ mod.call(r)
213
+ end
214
+ else
215
+ lambda{|r| (m.call(r[ck]) rescue self).call(r)}
216
+ end
217
+ else
218
+ self
219
+ end
220
+ end
195
221
  end
196
222
 
197
223
  module InstanceMethods
198
224
  # Set the cti_key column to the name of the model.
199
- def before_create
200
- send("#{model.cti_key}=", model.name.to_s) if model.cti_key
225
+ def before_validation
226
+ if new? && model.cti_key && !model.cti_model_map
227
+ send("#{model.cti_key}=", model.name.to_s)
228
+ end
201
229
  super
202
230
  end
203
231
 
@@ -9,14 +9,13 @@ module Sequel
9
9
  # The many_through_many plugin would allow this:
10
10
  #
11
11
  # Artist.plugin :many_through_many
12
- # Artist.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
12
+ # Artist.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums_tags, :album_id, :tag_id]]
13
13
  #
14
14
  # Which will give you the tags for all of the artist's albums.
15
15
  #
16
16
  # Let's break down the 2nd argument of the many_through_many call:
17
17
  #
18
18
  # [[:albums_artists, :artist_id, :album_id],
19
- # [:albums, :id, :id],
20
19
  # [:albums_tags, :album_id, :tag_id]]
21
20
  #
22
21
  # This argument is an array of arrays with three elements. Each entry in the main array represents a JOIN in SQL:
@@ -29,12 +28,12 @@ module Sequel
29
28
  #
30
29
  # FROM artists
31
30
  # JOIN albums_artists ON (artists.id = albums_artists.artist_id)
32
- # JOIN albums ON (albums_artists.album_id = albums.id)
33
- # JOIN albums_tags ON (albums.id = albums_tag.album_id)
31
+ # JOIN albums_tags ON (albums_artists.album_id = albums_tag.album_id)
34
32
  # JOIN tags ON (albums_tags.tag_id = tags.id)
35
33
  #
36
34
  # The "artists.id" and "tags.id" criteria come from other association options (defaulting to the primary keys of the current and
37
- # associated tables), but hopefully you can see how each argument in the array is used in the JOIN clauses.
35
+ # associated tables), but hopefully you can see how each argument in the array is used in the JOIN clauses. Note that you do
36
+ # not need to add an entry for the final table (tags in this example), as that comes from the associated class.
38
37
  #
39
38
  # Here are some more examples:
40
39
  #
@@ -42,20 +41,20 @@ module Sequel
42
41
  # Artist.many_through_many :albums, [[:albums_artists, :artist_id, :album_id]]
43
42
  #
44
43
  # # All artists that are associated to any album that this artist is associated to
45
- # Artist.many_through_many :artists, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_artists, :album_id, :artist_id]]
44
+ # Artist.many_through_many :artists, [[:albums_artists, :artist_id, :album_id], [:albums_artists, :album_id, :artist_id]]
46
45
  #
47
46
  # # All albums by artists that are associated to any album that this artist is associated to
48
- # Artist.many_through_many :artist_albums, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], \
49
- # [:albums_artists, :album_id, :artist_id], [:artists, :id, :id], [:albums_artists, :artist_id, :album_id]], \
47
+ # Artist.many_through_many :artist_albums, [[:albums_artists, :artist_id, :album_id], \
48
+ # [:albums_artists, :album_id, :artist_id], [:albums_artists, :artist_id, :album_id]], \
50
49
  # :class=>:Album
51
50
  #
52
- # # All tracks on albums by this artist
53
- # Artist.many_through_many :tracks, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id]], \
51
+ # # All tracks on albums by this artist (also could be a many_to_many)
52
+ # Artist.many_through_many :tracks, [[:albums_artists, :artist_id, :album_id]], \
54
53
  # :right_primary_key=>:album_id
55
54
  #
56
55
  # Often you don't want the current object to appear in the array of associated objects. This is easiest to handle via an :after_load hook:
57
56
  #
58
- # Artist.many_through_many :artists, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_artists, :album_id, :artist_id]],
57
+ # Artist.many_through_many :artists, [[:albums_artists, :artist_id, :album_id], [:albums_artists, :album_id, :artist_id]],
59
58
  # :after_load=>proc{|artist, associated_artists| associated_artists.delete(artist)}
60
59
  #
61
60
  # You can also handle it by adding a dataset block that excludes the current record (so it won't be retrieved at all), but
@@ -163,7 +163,10 @@ module Sequel
163
163
  end
164
164
  end
165
165
  define_method("#{column}=") do |v|
166
- changed_columns << column unless changed_columns.include?(column)
166
+ if !changed_columns.include?(column) && (new? || send(column) != v)
167
+ changed_columns << column
168
+ end
169
+
167
170
  deserialized_values[column] = v
168
171
  end
169
172
  end
@@ -39,15 +39,6 @@ module Sequel
39
39
  super
40
40
  end
41
41
 
42
- # REMOVE410
43
- def eager_loading_dataset(opts, ds, select, associations, eager_options=OPTS)
44
- ds = super(opts, ds, select, associations, eager_options)
45
- if !ds.opts[:server] and s = eager_options[:self] and server = s.opts[:server]
46
- ds = ds.server(server)
47
- end
48
- ds
49
- end
50
-
51
42
  # Return a newly instantiated object that is tied to the given
52
43
  # shard s. When the object is saved, a record will be inserted
53
44
  # on shard s.
@@ -216,8 +216,10 @@ module Sequel
216
216
 
217
217
  module InstanceMethods
218
218
  # Set the sti_key column based on the sti_key_map.
219
- def before_create
220
- send("#{model.sti_key}=", model.sti_key_chooser.call(self)) unless self[model.sti_key]
219
+ def before_validation
220
+ if new? && !self[model.sti_key]
221
+ send("#{model.sti_key}=", model.sti_key_chooser.call(self))
222
+ end
221
223
  super
222
224
  end
223
225
  end
@@ -58,8 +58,8 @@ module Sequel
58
58
 
59
59
  module InstanceMethods
60
60
  # Set the create timestamp when creating
61
- def before_create
62
- set_create_timestamp
61
+ def before_validation
62
+ set_create_timestamp if new?
63
63
  super
64
64
  end
65
65
 
@@ -251,8 +251,9 @@ module Sequel
251
251
  # Create an SQL alias (+AliasedExpression+) of the receiving column or expression to the given alias.
252
252
  #
253
253
  # Sequel.function(:func).as(:alias) # func() AS "alias"
254
- def as(aliaz)
255
- AliasedExpression.new(self, aliaz)
254
+ # Sequel.function(:func).as(:alias, [:col_alias1, :col_alias2]) # func() AS "alias"("col_alias1", "col_alias2")
255
+ def as(aliaz, columns=nil)
256
+ AliasedExpression.new(self, aliaz, columns)
256
257
  end
257
258
  end
258
259
 
@@ -320,8 +321,9 @@ module Sequel
320
321
  # Create an SQL::AliasedExpression for the given expression and alias.
321
322
  #
322
323
  # Sequel.as(:column, :alias) # "column" AS "alias"
323
- def as(exp, aliaz)
324
- SQL::AliasedExpression.new(exp, aliaz)
324
+ # Sequel.as(:column, :alias, [:col_alias1, :col_alias2]) # "column" AS "alias"("col_alias1", "col_alias2")
325
+ def as(exp, aliaz, columns=nil)
326
+ SQL::AliasedExpression.new(exp, aliaz, columns)
325
327
  end
326
328
 
327
329
  # Order the given argument ascending.
@@ -391,7 +393,7 @@ module Sequel
391
393
  # Sequel.char_length(:a) # char_length(a) -- Most databases
392
394
  # Sequel.char_length(:a) # length(a) -- SQLite
393
395
  def char_length(arg)
394
- SQL::EmulatedFunction.new(:char_length, arg)
396
+ SQL::Function.new!(:char_length, [arg], :emulate=>true)
395
397
  end
396
398
 
397
399
  # Do a deep qualification of the argument using the qualifier. This recurses into
@@ -555,7 +557,7 @@ module Sequel
555
557
  # Create a <tt>BooleanExpression</tt> case insensitive (if the database supports it) pattern match of the receiver with
556
558
  # the given patterns. See <tt>SQL::StringExpression.like</tt>.
557
559
  #
558
- # Sequel.ilike(:a, 'A%') # "a" ILIKE 'A%'
560
+ # Sequel.ilike(:a, 'A%') # "a" ILIKE 'A%' ESCAPE '\'
559
561
  def ilike(*args)
560
562
  SQL::StringExpression.like(*(args << {:case_insensitive=>true}))
561
563
  end
@@ -563,7 +565,7 @@ module Sequel
563
565
  # Create a <tt>SQL::BooleanExpression</tt> case sensitive (if the database supports it) pattern match of the receiver with
564
566
  # the given patterns. See <tt>SQL::StringExpression.like</tt>.
565
567
  #
566
- # Sequel.like(:a, 'A%') # "a" LIKE 'A%'
568
+ # Sequel.like(:a, 'A%') # "a" LIKE 'A%' ESCAPE '\'
567
569
  def like(*args)
568
570
  SQL::StringExpression.like(*args)
569
571
  end
@@ -648,7 +650,7 @@ module Sequel
648
650
  # Sequel.trim(:a) # trim(a) -- Most databases
649
651
  # Sequel.trim(:a) # ltrim(rtrim(a)) -- Microsoft SQL Server
650
652
  def trim(arg)
651
- SQL::EmulatedFunction.new(:trim, arg)
653
+ SQL::Function.new!(:trim, [arg], :emulate=>true)
652
654
  end
653
655
 
654
656
  # Return a <tt>SQL::ValueList</tt> created from the given array. Used if the array contains
@@ -887,7 +889,7 @@ module Sequel
887
889
  # Create a +BooleanExpression+ case insensitive pattern match of the receiver
888
890
  # with the given patterns. See <tt>StringExpression.like</tt>.
889
891
  #
890
- # :a.ilike('A%') # "a" ILIKE 'A%'
892
+ # :a.ilike('A%') # "a" ILIKE 'A%' ESCAPE '\'
891
893
  def ilike(*ces)
892
894
  StringExpression.like(self, *(ces << {:case_insensitive=>true}))
893
895
  end
@@ -895,7 +897,7 @@ module Sequel
895
897
  # Create a +BooleanExpression+ case sensitive (if the database supports it) pattern match of the receiver with
896
898
  # the given patterns. See <tt>StringExpression.like</tt>.
897
899
  #
898
- # :a.like('A%') # "a" LIKE 'A%'
900
+ # :a.like('A%') # "a" LIKE 'A%' ESCAPE '\'
899
901
  def like(*ces)
900
902
  StringExpression.like(self, *ces)
901
903
  end
@@ -940,9 +942,15 @@ module Sequel
940
942
  attr_reader :aliaz
941
943
  alias_method :alias, :aliaz
942
944
 
945
+ # The columns aliases to use, for when the aliased expression is
946
+ # a record or set of records (such as a dataset).
947
+ attr_reader :columns
948
+
943
949
  # Create an object with the given expression and alias.
944
- def initialize(expression, aliaz)
945
- @expression, @aliaz = expression, aliaz
950
+ def initialize(expression, aliaz, columns=nil)
951
+ @expression = expression
952
+ @aliaz = aliaz
953
+ @columns = columns
946
954
  end
947
955
 
948
956
  to_s_method :aliased_expression_sql
@@ -1222,44 +1230,122 @@ module Sequel
1222
1230
  COMMA_ARRAY = [LiteralString.new(', ').freeze].freeze
1223
1231
 
1224
1232
  # The SQL function to call
1225
- attr_reader :f
1233
+ attr_reader :name
1234
+ alias f name
1226
1235
 
1227
1236
  # The array of arguments to pass to the function (may be blank)
1228
1237
  attr_reader :args
1229
1238
 
1230
- # Set the functions and args to the given arguments
1231
- def initialize(f, *args)
1232
- @f, @args = f, args
1239
+ # Options for this function
1240
+ attr_reader :opts
1241
+
1242
+ # Set the name and args for the function
1243
+ def initialize(name, *args)
1244
+ @name = name
1245
+ @args = args
1246
+ @opts = OPTS
1247
+ end
1248
+
1249
+ def self.new!(name, args, opts)
1250
+ f = new(name, *args)
1251
+ f.instance_variable_set(:@opts, opts)
1252
+ f
1233
1253
  end
1234
1254
 
1235
1255
  # If no arguments are given, return a new function with the wildcard prepended to the arguments.
1236
1256
  #
1237
1257
  # Sequel.function(:count).* # count(*)
1238
- # Sequel.function(:count, 1).* # count(*, 1)
1239
1258
  def *(ce=(arg=false;nil))
1240
1259
  if arg == false
1241
- Function.new(f, WILDCARD, *args)
1260
+ raise Error, "Cannot apply * to functions with arguments" unless args.empty?
1261
+ with_opts(:"*"=>true)
1242
1262
  else
1243
1263
  super(ce)
1244
1264
  end
1245
1265
  end
1246
1266
 
1247
1267
  # Return a new function with DISTINCT before the method arguments.
1268
+ #
1269
+ # Sequel.function(:count, :col).distinct # count(DISTINCT col)
1248
1270
  def distinct
1249
- Function.new(f, PlaceholderLiteralString.new(DISTINCT + COMMA_ARRAY * (args.length-1), args))
1271
+ with_opts(:distinct=>true)
1272
+ end
1273
+
1274
+ # Return a new function with FILTER added to it, for filtered
1275
+ # aggregate functions:
1276
+ #
1277
+ # Sequel.function(:foo, :col).filter(:a=>1) # foo(col) FILTER (WHERE a = 1)
1278
+ def filter(*args, &block)
1279
+ args = args.first if args.length == 1
1280
+ with_opts(:filter=>args, :filter_block=>block)
1281
+ end
1282
+
1283
+ # Return a function which will use LATERAL when literalized:
1284
+ #
1285
+ # Sequel.function(:foo, :col).lateral # LATERAL foo(col)
1286
+ def lateral
1287
+ with_opts(:lateral=>true)
1288
+ end
1289
+
1290
+ # Return a new function with an OVER clause (making it a window function).
1291
+ #
1292
+ # Sequel.function(:row_number).over(:partition=>:col) # row_number() OVER (PARTITION BY col)
1293
+ def over(window=OPTS)
1294
+ raise Error, "function already has a window applied to it" if opts[:over]
1295
+ window = Window.new(window) unless window.is_a?(Window)
1296
+ with_opts(:over=>window)
1297
+ end
1298
+
1299
+ # Return a new function where the function name will be quoted if the database supports
1300
+ # quoted functions:
1301
+ #
1302
+ # Sequel.function(:foo).quoted # "foo"()
1303
+ def quoted
1304
+ with_opts(:quoted=>true)
1305
+ end
1306
+
1307
+ # Return a new function where the function name will not be quoted even
1308
+ # if the database supports quoted functions:
1309
+ #
1310
+ # Sequel.expr(:foo).function.unquoted # foo()
1311
+ def unquoted
1312
+ with_opts(:quoted=>false)
1313
+ end
1314
+
1315
+ # Return a new function that will use WITH ORDINALITY to also return
1316
+ # a row number for every row the function returns:
1317
+ #
1318
+ # Sequel.function(:foo).with_ordinality # foo() WITH ORDINALITY
1319
+ def with_ordinality
1320
+ with_opts(:with_ordinality=>true)
1250
1321
  end
1251
1322
 
1252
- # Create a WindowFunction using the receiver and the appropriate options for the window.
1253
- def over(opts=OPTS)
1254
- WindowFunction.new(self, Window.new(opts))
1323
+ # Return a new function that uses WITHIN GROUP ordered by the given expression,
1324
+ # useful for ordered-set and hypothetical-set aggregate functions:
1325
+ #
1326
+ # Sequel.function(:rank, :a).within_group(:b, :c)
1327
+ # # rank(a) WITHIN GROUP (ORDER BY b, c)
1328
+ def within_group(*expressions)
1329
+ with_opts(:within_group=>expressions)
1255
1330
  end
1256
1331
 
1257
1332
  to_s_method :function_sql
1333
+
1334
+ private
1335
+
1336
+ # Return a new function call with the given opts merged into the current opts.
1337
+ def with_opts(opts)
1338
+ self.class.new!(name, args, @opts.merge(opts))
1339
+ end
1258
1340
  end
1259
1341
 
1260
- # Represents an SQL function call that is translated/emulated
1261
- # on databases that lack support for such a function.
1342
+ # REMOVE411
1262
1343
  class EmulatedFunction < Function
1344
+ def self.new(name, *args)
1345
+ Deprecation.deprecate("Sequel::SQL::EmulatedFunction", "Please use Sequel::SQL::Function.new!(name, args, :emulate=>true) to create an emulated SQL function")
1346
+ Function.new!(name, args, :emulate=>true)
1347
+ end
1348
+
1263
1349
  to_s_method :emulated_function_sql
1264
1350
  end
1265
1351
 
@@ -1303,15 +1389,48 @@ module Sequel
1303
1389
  # The type of join to do
1304
1390
  attr_reader :join_type
1305
1391
 
1306
- # The actual table to join
1307
- attr_reader :table
1308
-
1309
- # The table alias to use for the join, if any
1310
- attr_reader :table_alias
1392
+ # The expression representing the table/set related to the JOIN.
1393
+ # Is an AliasedExpression if the JOIN uses an alias.
1394
+ attr_reader :table_expr
1311
1395
 
1312
- # Create an object with the given join_type, table, and table alias
1396
+ # Create an object with the given join_type and table expression.
1313
1397
  def initialize(join_type, table, table_alias = nil)
1314
- @join_type, @table, @table_alias = join_type, table, table_alias
1398
+ @join_type = join_type
1399
+
1400
+ @table_expr = if table.is_a?(AliasedExpression)
1401
+ table
1402
+ # REMOVE411
1403
+ elsif table_alias
1404
+ Deprecation.deprecate("The table_alias argument to Sequel::SQL::JoinClause#initialize", "Please use a Sequel::SQL::AliasedExpression as the table argument instead.")
1405
+ AliasedExpression.new(table, table_alias)
1406
+ else
1407
+ table
1408
+ end
1409
+ end
1410
+
1411
+ # The table/set related to the JOIN, without any alias.
1412
+ def table
1413
+ if @table_expr.is_a?(AliasedExpression)
1414
+ @table_expr.expression
1415
+ else
1416
+ @table_expr
1417
+ end
1418
+ end
1419
+
1420
+ # The table alias to use for the JOIN , or nil if the
1421
+ # JOIN does not alias the table.
1422
+ def table_alias
1423
+ if @table_expr.is_a?(AliasedExpression)
1424
+ @table_expr.alias
1425
+ end
1426
+ end
1427
+
1428
+ # The column aliases to use for the JOIN , or nil if the
1429
+ # JOIN does not use a derived column list.
1430
+ def column_aliases
1431
+ if @table_expr.is_a?(AliasedExpression)
1432
+ @table_expr.columns
1433
+ end
1315
1434
  end
1316
1435
 
1317
1436
  to_s_method :join_clause_sql
@@ -1486,9 +1605,9 @@ module Sequel
1486
1605
  # if a case insensitive regular expression is used (//i), that particular
1487
1606
  # pattern which will always be case insensitive.
1488
1607
  #
1489
- # StringExpression.like(:a, 'a%') # "a" LIKE 'a%'
1490
- # StringExpression.like(:a, 'a%', :case_insensitive=>true) # "a" ILIKE 'a%'
1491
- # StringExpression.like(:a, 'a%', /^a/i) # "a" LIKE 'a%' OR "a" ~* '^a'
1608
+ # StringExpression.like(:a, 'a%') # "a" LIKE 'a%' ESCAPE '\'
1609
+ # StringExpression.like(:a, 'a%', :case_insensitive=>true) # "a" ILIKE 'a%' ESCAPE '\'
1610
+ # StringExpression.like(:a, 'a%', /^a/i) # "a" LIKE 'a%' ESCAPE '\' OR "a" ~* '^a'
1492
1611
  def self.like(l, *ces)
1493
1612
  l, lre, lci = like_element(l)
1494
1613
  lci = (ces.last.is_a?(Hash) ? ces.pop : {})[:case_insensitive] ? true : lci
@@ -1568,7 +1687,7 @@ module Sequel
1568
1687
  # If the block doesn't take an argument, the block is instance_execed in the context of
1569
1688
  # an instance of this class.
1570
1689
  #
1571
- # +VirtualRow+ uses +method_missing+ to return either an +Identifier+, +QualifiedIdentifier+, +Function+, or +WindowFunction+,
1690
+ # +VirtualRow+ uses +method_missing+ to return either an +Identifier+, +QualifiedIdentifier+, or +Function+
1572
1691
  # depending on how it is called.
1573
1692
  #
1574
1693
  # If a block is _not_ given, creates one of the following objects:
@@ -1579,16 +1698,15 @@ module Sequel
1579
1698
  # table being the part before __, and the column being the part after.
1580
1699
  # +Identifier+ :: Returned otherwise, using the method name.
1581
1700
  #
1582
- # If a block is given, it returns either a +Function+ or +WindowFunction+, depending on the first
1583
- # argument to the method. Note that the block is currently not called by the code, though
1701
+ # If a block is given, it returns a +Function+. Note that the block is currently not called by the code, though
1584
1702
  # this may change in a future version. If the first argument is:
1585
1703
  #
1586
1704
  # no arguments given :: creates a +Function+ with no arguments.
1587
1705
  # :* :: creates a +Function+ with a literal wildcard argument (*), mostly useful for COUNT.
1588
1706
  # :distinct :: creates a +Function+ that prepends DISTINCT to the rest of the arguments, mostly
1589
1707
  # useful for aggregate functions.
1590
- # :over :: creates a +WindowFunction+. If a second argument is provided, it should be a hash
1591
- # of options which are passed to Window (with possible keys :window, :partition, :order, and :frame). The
1708
+ # :over :: creates a +Function+ with a window. If a second argument is provided, it should be a hash
1709
+ # of options which are used to create the +Window+ (with possible keys :window, :partition, :order, and :frame). The
1592
1710
  # arguments to the function itself should be specified as <tt>:*=>true</tt> for a wildcard, or via
1593
1711
  # the <tt>:args</tt> option.
1594
1712
  #
@@ -1657,7 +1775,7 @@ module Sequel
1657
1775
  Sequel::LiteralString.new(s)
1658
1776
  end
1659
1777
 
1660
- # Return an +Identifier+, +QualifiedIdentifier+, +Function+, or +WindowFunction+, depending
1778
+ # Return an +Identifier+, +QualifiedIdentifier+, or +Function+, depending
1661
1779
  # on arguments and whether a block is provided. Does not currently call the block.
1662
1780
  # See the class level documentation.
1663
1781
  def method_missing(m, *args, &block)
@@ -1690,9 +1808,7 @@ module Sequel
1690
1808
  Sequel::VIRTUAL_ROW = new
1691
1809
  end
1692
1810
 
1693
- # A +Window+ is part of a window function specifying the window over which the function operates.
1694
- # It is separated from the +WindowFunction+ class because it also can be used separately on
1695
- # some databases.
1811
+ # A +Window+ is part of a window function specifying the window over which a window function operates.
1696
1812
  class Window < Expression
1697
1813
  # The options for this window. Options currently supported:
1698
1814
  # :frame :: if specified, should be :all, :rows, or a String that is used literally. :all always operates over all rows in the
@@ -1711,7 +1827,7 @@ module Sequel
1711
1827
  to_s_method :window_sql, '@opts'
1712
1828
  end
1713
1829
 
1714
- # A +WindowFunction+ is a grouping of a +Function+ with a +Window+ over which it operates.
1830
+ # REMOVE411
1715
1831
  class WindowFunction < GenericExpression
1716
1832
  # The function to use, should be an <tt>SQL::Function</tt>.
1717
1833
  attr_reader :function
@@ -1719,8 +1835,14 @@ module Sequel
1719
1835
  # The window to use, should be an <tt>SQL::Window</tt>.
1720
1836
  attr_reader :window
1721
1837
 
1838
+ def self.new(function, window)
1839
+ Deprecation.deprecate("Sequel::SQL::WindowFunction", "Please use Sequel::SQL::Function.new(name, *args).over(...) to create an SQL window function")
1840
+ function.over(window)
1841
+ end
1842
+
1722
1843
  # Set the function and window.
1723
1844
  def initialize(function, window)
1845
+ Deprecation.deprecate("Sequel::SQL::WindowFunction", "Please use Sequel::SQL::Function.new(name, *args).over(...) to create an SQL window function")
1724
1846
  @function, @window = function, window
1725
1847
  end
1726
1848