sequel 4.10.0 → 4.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +58 -0
- data/doc/association_basics.rdoc +1 -1
- data/doc/cheat_sheet.rdoc +0 -1
- data/doc/core_extensions.rdoc +2 -2
- data/doc/dataset_filtering.rdoc +5 -5
- data/doc/model_hooks.rdoc +9 -0
- data/doc/object_model.rdoc +7 -13
- data/doc/opening_databases.rdoc +3 -1
- data/doc/querying.rdoc +8 -8
- data/doc/release_notes/4.11.0.txt +147 -0
- data/doc/sql.rdoc +11 -7
- data/doc/virtual_rows.rdoc +4 -5
- data/lib/sequel/adapters/ibmdb.rb +24 -16
- data/lib/sequel/adapters/jdbc/h2.rb +5 -0
- data/lib/sequel/adapters/jdbc/hsqldb.rb +5 -0
- data/lib/sequel/adapters/mock.rb +14 -2
- data/lib/sequel/adapters/shared/access.rb +6 -9
- data/lib/sequel/adapters/shared/cubrid.rb +5 -0
- data/lib/sequel/adapters/shared/db2.rb +5 -0
- data/lib/sequel/adapters/shared/firebird.rb +5 -0
- data/lib/sequel/adapters/shared/mssql.rb +23 -16
- data/lib/sequel/adapters/shared/mysql.rb +12 -2
- data/lib/sequel/adapters/shared/oracle.rb +31 -15
- data/lib/sequel/adapters/shared/postgres.rb +28 -4
- data/lib/sequel/adapters/shared/sqlanywhere.rb +5 -0
- data/lib/sequel/adapters/shared/sqlite.rb +12 -1
- data/lib/sequel/ast_transformer.rb +9 -7
- data/lib/sequel/connection_pool.rb +10 -4
- data/lib/sequel/database/features.rb +15 -0
- data/lib/sequel/database/schema_generator.rb +2 -2
- data/lib/sequel/database/schema_methods.rb +21 -3
- data/lib/sequel/database/transactions.rb +8 -4
- data/lib/sequel/dataset/actions.rb +13 -7
- data/lib/sequel/dataset/features.rb +7 -0
- data/lib/sequel/dataset/query.rb +28 -11
- data/lib/sequel/dataset/sql.rb +90 -14
- data/lib/sequel/extensions/constraint_validations.rb +2 -2
- data/lib/sequel/extensions/date_arithmetic.rb +1 -1
- data/lib/sequel/extensions/eval_inspect.rb +12 -6
- data/lib/sequel/extensions/pg_array_ops.rb +11 -2
- data/lib/sequel/extensions/pg_json.rb +130 -23
- data/lib/sequel/extensions/pg_json_ops.rb +196 -28
- data/lib/sequel/extensions/to_dot.rb +5 -7
- data/lib/sequel/model/associations.rb +0 -50
- data/lib/sequel/plugins/class_table_inheritance.rb +49 -21
- data/lib/sequel/plugins/many_through_many.rb +10 -11
- data/lib/sequel/plugins/serialization.rb +4 -1
- data/lib/sequel/plugins/sharding.rb +0 -9
- data/lib/sequel/plugins/single_table_inheritance.rb +4 -2
- data/lib/sequel/plugins/timestamps.rb +2 -2
- data/lib/sequel/sql.rb +166 -44
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +199 -133
- data/spec/core/connection_pool_spec.rb +6 -0
- data/spec/core/database_spec.rb +12 -0
- data/spec/core/dataset_spec.rb +58 -3
- data/spec/core/expression_filters_spec.rb +67 -5
- data/spec/core/mock_adapter_spec.rb +8 -4
- data/spec/core/schema_spec.rb +7 -0
- data/spec/core_extensions_spec.rb +14 -0
- data/spec/extensions/class_table_inheritance_spec.rb +23 -3
- data/spec/extensions/core_refinements_spec.rb +14 -0
- data/spec/extensions/eval_inspect_spec.rb +8 -4
- data/spec/extensions/pg_array_ops_spec.rb +6 -0
- data/spec/extensions/pg_json_ops_spec.rb +99 -0
- data/spec/extensions/pg_json_spec.rb +104 -4
- data/spec/extensions/serialization_spec.rb +19 -0
- data/spec/extensions/single_table_inheritance_spec.rb +11 -3
- data/spec/extensions/timestamps_spec.rb +10 -0
- data/spec/extensions/to_dot_spec.rb +8 -4
- data/spec/integration/database_test.rb +1 -1
- data/spec/integration/dataset_test.rb +9 -0
- data/spec/integration/schema_test.rb +27 -0
- 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.
|
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.
|
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
|
-
#
|
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
|
-
#
|
87
|
-
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
#
|
91
|
-
#
|
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
|
-
|
101
|
-
|
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
|
-
|
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
|
200
|
-
|
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], [:
|
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
|
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], [:
|
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],
|
49
|
-
# [:albums_artists, :album_id, :artist_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]
|
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], [:
|
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
|
-
|
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
|
220
|
-
|
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
|
data/lib/sequel/sql.rb
CHANGED
@@ -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
|
-
|
255
|
-
|
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
|
-
|
324
|
-
|
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::
|
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::
|
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
|
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 :
|
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
|
-
#
|
1231
|
-
|
1232
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
1253
|
-
|
1254
|
-
|
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
|
-
#
|
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
|
1307
|
-
|
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
|
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
|
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+,
|
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
|
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 +
|
1591
|
-
# of options which are
|
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+,
|
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
|
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
|
-
#
|
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
|
|