sequel 4.10.0 → 4.11.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 (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