sequel 3.13.0 → 3.14.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.
- data/CHANGELOG +36 -0
- data/doc/release_notes/3.14.0.txt +118 -0
- data/lib/sequel/adapters/oracle.rb +7 -2
- data/lib/sequel/adapters/shared/mssql.rb +9 -3
- data/lib/sequel/connection_pool/sharded_threaded.rb +1 -1
- data/lib/sequel/connection_pool/threaded.rb +3 -3
- data/lib/sequel/database/connecting.rb +47 -11
- data/lib/sequel/database/dataset.rb +17 -6
- data/lib/sequel/database/dataset_defaults.rb +15 -3
- data/lib/sequel/database/logging.rb +4 -3
- data/lib/sequel/database/misc.rb +33 -21
- data/lib/sequel/database/query.rb +61 -22
- data/lib/sequel/database/schema_generator.rb +108 -45
- data/lib/sequel/database/schema_methods.rb +8 -5
- data/lib/sequel/dataset/actions.rb +194 -45
- data/lib/sequel/dataset/features.rb +1 -1
- data/lib/sequel/dataset/graph.rb +51 -43
- data/lib/sequel/dataset/misc.rb +29 -5
- data/lib/sequel/dataset/mutation.rb +0 -1
- data/lib/sequel/dataset/prepared_statements.rb +14 -2
- data/lib/sequel/dataset/query.rb +268 -125
- data/lib/sequel/dataset/sql.rb +33 -44
- data/lib/sequel/extensions/migration.rb +3 -2
- data/lib/sequel/extensions/pagination.rb +1 -1
- data/lib/sequel/model/associations.rb +89 -87
- data/lib/sequel/model/base.rb +386 -109
- data/lib/sequel/model/errors.rb +15 -1
- data/lib/sequel/model/exceptions.rb +3 -3
- data/lib/sequel/model/inflections.rb +2 -2
- data/lib/sequel/model/plugins.rb +9 -5
- data/lib/sequel/plugins/rcte_tree.rb +43 -15
- data/lib/sequel/plugins/schema.rb +6 -5
- data/lib/sequel/plugins/serialization.rb +1 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
- data/lib/sequel/plugins/tree.rb +33 -1
- data/lib/sequel/timezones.rb +16 -10
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +36 -2
- data/spec/adapters/mysql_spec.rb +4 -4
- data/spec/adapters/postgres_spec.rb +1 -1
- data/spec/adapters/spec_helper.rb +2 -2
- data/spec/core/database_spec.rb +8 -1
- data/spec/core/dataset_spec.rb +36 -1
- data/spec/extensions/pagination_spec.rb +1 -1
- data/spec/extensions/rcte_tree_spec.rb +40 -8
- data/spec/extensions/schema_spec.rb +5 -0
- data/spec/extensions/serialization_spec.rb +4 -4
- data/spec/extensions/single_table_inheritance_spec.rb +7 -0
- data/spec/extensions/tree_spec.rb +36 -0
- data/spec/integration/dataset_test.rb +19 -0
- data/spec/integration/prepared_statement_test.rb +2 -2
- data/spec/integration/schema_test.rb +1 -1
- data/spec/integration/spec_helper.rb +4 -4
- data/spec/integration/timezone_test.rb +27 -21
- data/spec/model/associations_spec.rb +5 -5
- data/spec/model/dataset_methods_spec.rb +13 -0
- data/spec/model/hooks_spec.rb +31 -0
- data/spec/model/record_spec.rb +24 -7
- data/spec/model/validations_spec.rb +9 -4
- metadata +6 -4
data/lib/sequel/dataset/graph.rb
CHANGED
@@ -3,13 +3,17 @@ module Sequel
|
|
3
3
|
# ---------------------
|
4
4
|
# :section: Methods related to dataset graphing
|
5
5
|
# Dataset graphing changes the dataset to yield hashes where keys are table
|
6
|
-
# name symbols and
|
6
|
+
# name symbols and values are hashes representing the columns related to
|
7
7
|
# that table. All of these methods return modified copies of the receiver.
|
8
8
|
# ---------------------
|
9
9
|
|
10
10
|
# Adds the given graph aliases to the list of graph aliases to use,
|
11
|
-
# unlike
|
12
|
-
#
|
11
|
+
# unlike +set_graph_aliases+, which replaces the list (the equivalent
|
12
|
+
# of +select_more+ when graphing). See +set_graph_aliases+.
|
13
|
+
#
|
14
|
+
# DB[:table].add_graph_aliases(:some_alias=>[:table, :column])
|
15
|
+
# # SELECT ..., table.column AS some_alias
|
16
|
+
# # => {:table=>{:column=>some_alias_value, ...}, ...}
|
13
17
|
def add_graph_aliases(graph_aliases)
|
14
18
|
ds = select_more(*graph_alias_columns(graph_aliases))
|
15
19
|
ds.opts[:graph_aliases] = (ds.opts[:graph_aliases] || (ds.opts[:graph][:column_aliases] rescue {}) || {}).merge(graph_aliases)
|
@@ -24,16 +28,18 @@ module Sequel
|
|
24
28
|
#
|
25
29
|
# # CREATE TABLE artists (id INTEGER, name TEXT);
|
26
30
|
# # CREATE TABLE albums (id INTEGER, name TEXT, artist_id INTEGER);
|
31
|
+
#
|
27
32
|
# DB[:artists].left_outer_join(:albums, :artist_id=>:id).first
|
28
|
-
#
|
33
|
+
# #=> {:id=>albums.id, :name=>albums.name, :artist_id=>albums.artist_id}
|
34
|
+
#
|
29
35
|
# DB[:artists].graph(:albums, :artist_id=>:id).first
|
30
|
-
#
|
36
|
+
# #=> {:artists=>{:id=>artists.id, :name=>artists.name}, :albums=>{:id=>albums.id, :name=>albums.name, :artist_id=>albums.artist_id}}
|
31
37
|
#
|
32
38
|
# Using a join such as left_outer_join, the attribute names that are shared between
|
33
39
|
# the tables are combined in the single return hash. You can get around that by
|
34
|
-
# using
|
35
|
-
# use graph and have the result set split for you. In addition, graph respects
|
36
|
-
# any row_proc of the current dataset and the datasets you use with graph
|
40
|
+
# using +select+ with correct aliases for all of the columns, but it is simpler to
|
41
|
+
# use +graph+ and have the result set split for you. In addition, +graph+ respects
|
42
|
+
# any +row_proc+ of the current dataset and the datasets you use with +graph+.
|
37
43
|
#
|
38
44
|
# If you are graphing a table and all columns for that table are nil, this
|
39
45
|
# indicates that no matching rows existed in the table, so graph will return nil
|
@@ -44,26 +50,26 @@ module Sequel
|
|
44
50
|
# => {:artists=>{:id=>artists.id, :name=>artists.name}, :albums=>nil}
|
45
51
|
#
|
46
52
|
# Arguments:
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
53
|
+
# dataset :: Can be a symbol (specifying a table), another dataset,
|
54
|
+
# or an object that responds to +dataset+ and returns a symbol or a dataset
|
55
|
+
# join_conditions :: Any condition(s) allowed by +join_table+.
|
56
|
+
# block :: A block that is passed to +join_table+.
|
57
|
+
#
|
58
|
+
# Options:
|
59
|
+
# :from_self_alias :: The alias to use when the receiver is not a graphed
|
60
|
+
# dataset but it contains multiple FROM tables or a JOIN. In this case,
|
61
|
+
# the receiver is wrapped in a from_self before graphing, and this option
|
62
|
+
# determines the alias to use.
|
63
|
+
# :implicit_qualifier :: The qualifier of implicit conditions, see #join_table.
|
64
|
+
# :join_type :: The type of join to use (passed to +join_table+). Defaults to :left_outer.
|
65
|
+
# :select :: An array of columns to select. When not used, selects
|
66
|
+
# all columns in the given dataset. When set to false, selects no
|
67
|
+
# columns and is like simply joining the tables, though graph keeps
|
68
|
+
# some metadata about the join that makes it important to use +graph+ instead
|
69
|
+
# of +join_table+.
|
70
|
+
# :table_alias :: The alias to use for the table. If not specified, doesn't
|
71
|
+
# alias the table. You will get an error if the the alias (or table) name is
|
72
|
+
# used more than once.
|
67
73
|
def graph(dataset, join_conditions = nil, options = {}, &block)
|
68
74
|
# Allow the use of a model, dataset, or symbol as the first argument
|
69
75
|
# Find the table name/dataset based on the argument
|
@@ -172,29 +178,31 @@ module Sequel
|
|
172
178
|
# This allows you to manually specify the graph aliases to use
|
173
179
|
# when using graph. You can use it to only select certain
|
174
180
|
# columns, and have those columns mapped to specific aliases
|
175
|
-
# in the result set. This is the equivalent of
|
176
|
-
# graphed dataset, and must be used instead of
|
177
|
-
# graphing is used.
|
181
|
+
# in the result set. This is the equivalent of +select+ for a
|
182
|
+
# graphed dataset, and must be used instead of +select+ whenever
|
183
|
+
# graphing is used.
|
178
184
|
#
|
179
|
-
#
|
180
|
-
#
|
185
|
+
# graph_aliases :: Should be a hash with keys being symbols of
|
186
|
+
# column aliases, and values being arrays with two or three elements.
|
187
|
+
# The first element of the array should be the table alias symbol,
|
188
|
+
# and the second should be the actual column name symbol. If the array
|
189
|
+
# has a third element, it is used as the value returned, instead of
|
190
|
+
# table_alias.column_name.
|
181
191
|
#
|
182
|
-
#
|
183
|
-
#
|
184
|
-
#
|
185
|
-
#
|
186
|
-
#
|
187
|
-
#
|
188
|
-
# table_alias.column_name.
|
192
|
+
# DB[:artists].graph(:albums, :artist_id=>:id).
|
193
|
+
# set_graph_aliases(:artist_name=>[:artists, :name],
|
194
|
+
# :album_name=>[:albums, :name],
|
195
|
+
# :forty_two=>[:albums, :fourtwo, 42]).first
|
196
|
+
# # SELECT artists.name AS artist_name, albums.name AS album_name, 42 AS forty_two FROM table
|
197
|
+
# # => {:artists=>{:name=>artists.name}, :albums=>{:name=>albums.name, :fourtwo=>42}}
|
189
198
|
def set_graph_aliases(graph_aliases)
|
190
199
|
ds = select(*graph_alias_columns(graph_aliases))
|
191
200
|
ds.opts[:graph_aliases] = graph_aliases
|
192
201
|
ds
|
193
202
|
end
|
194
203
|
|
195
|
-
# Remove the splitting of results into subhashes
|
196
|
-
#
|
197
|
-
# any tables to this dataset after calling this method.
|
204
|
+
# Remove the splitting of results into subhashes, and all metadata
|
205
|
+
# related to the current graph (if any).
|
198
206
|
def ungraphed
|
199
207
|
clone(:graph=>nil)
|
200
208
|
end
|
data/lib/sequel/dataset/misc.rb
CHANGED
@@ -10,7 +10,8 @@ module Sequel
|
|
10
10
|
ARG_BLOCK_ERROR_MSG = 'Must use either an argument or a block, not both'.freeze
|
11
11
|
IMPORT_ERROR_MSG = 'Using Sequel::Dataset#import an empty column array is not allowed'.freeze
|
12
12
|
|
13
|
-
# The database
|
13
|
+
# The database related to this dataset. This is the Database instance that
|
14
|
+
# will execute all of this dataset's queries.
|
14
15
|
attr_accessor :db
|
15
16
|
|
16
17
|
# The hash of options for this dataset, keys are symbols.
|
@@ -22,8 +23,8 @@ module Sequel
|
|
22
23
|
# DB[:posts]
|
23
24
|
#
|
24
25
|
# Sequel::Dataset is an abstract class that is not useful by itself. Each
|
25
|
-
# database adaptor
|
26
|
-
# the Database#dataset method return an instance of that
|
26
|
+
# database adaptor provides a subclass of Sequel::Dataset, and has
|
27
|
+
# the Database#dataset method return an instance of that subclass.
|
27
28
|
def initialize(db, opts = nil)
|
28
29
|
@db = db
|
29
30
|
@quote_identifiers = db.quote_identifiers? if db.respond_to?(:quote_identifiers?)
|
@@ -36,6 +37,8 @@ module Sequel
|
|
36
37
|
# Return the dataset as an aliased expression with the given alias. You can
|
37
38
|
# use this as a FROM or JOIN dataset, or as a column if this dataset
|
38
39
|
# returns a single row and column.
|
40
|
+
#
|
41
|
+
# DB.from(DB[:table].as(:b)) # SELECT * FROM (SELECT * FROM table) AS b
|
39
42
|
def as(aliaz)
|
40
43
|
::Sequel::SQL::AliasedExpression.new(self, aliaz)
|
41
44
|
end
|
@@ -49,13 +52,19 @@ module Sequel
|
|
49
52
|
db.servers.each{|s| yield server(s)}
|
50
53
|
end
|
51
54
|
|
52
|
-
# Alias of first_source_alias
|
55
|
+
# Alias of +first_source_alias+
|
53
56
|
def first_source
|
54
57
|
first_source_alias
|
55
58
|
end
|
56
59
|
|
57
60
|
# The first source (primary table) for this dataset. If the dataset doesn't
|
58
|
-
# have a table, raises an
|
61
|
+
# have a table, raises an +Error+. If the table is aliased, returns the aliased name.
|
62
|
+
#
|
63
|
+
# DB[:table].first_source_alias
|
64
|
+
# # => :table
|
65
|
+
#
|
66
|
+
# DB[:table___t].first_source_alias
|
67
|
+
# # => :t
|
59
68
|
def first_source_alias
|
60
69
|
source = @opts[:from]
|
61
70
|
if source.nil? || source.empty?
|
@@ -75,6 +84,12 @@ module Sequel
|
|
75
84
|
# The first source (primary table) for this dataset. If the dataset doesn't
|
76
85
|
# have a table, raises an error. If the table is aliased, returns the original
|
77
86
|
# table, not the alias
|
87
|
+
#
|
88
|
+
# DB[:table].first_source_alias
|
89
|
+
# # => :table
|
90
|
+
#
|
91
|
+
# DB[:table___t].first_source_alias
|
92
|
+
# # => :table
|
78
93
|
def first_source_table
|
79
94
|
source = @opts[:from]
|
80
95
|
if source.nil? || source.empty?
|
@@ -103,6 +118,15 @@ module Sequel
|
|
103
118
|
# possibly appended with "_N" if the implicit alias has already been
|
104
119
|
# used, where N is an integer starting at 0 and increasing until an
|
105
120
|
# unused one is found.
|
121
|
+
#
|
122
|
+
# DB[:table].unused_table_alias(:t)
|
123
|
+
# # => :t
|
124
|
+
#
|
125
|
+
# DB[:table].unused_table_alias(:table)
|
126
|
+
# # => :table_0
|
127
|
+
#
|
128
|
+
# DB[:table, :table_0].unused_table_alias(:table)
|
129
|
+
# # => :table_1
|
106
130
|
def unused_table_alias(table_alias)
|
107
131
|
table_alias = alias_symbol(table_alias)
|
108
132
|
used_aliases = []
|
@@ -176,6 +176,10 @@ module Sequel
|
|
176
176
|
# Set the bind variables to use for the call. If bind variables have
|
177
177
|
# already been set for this dataset, they are updated with the contents
|
178
178
|
# of bind_vars.
|
179
|
+
#
|
180
|
+
# DB[:table].filter(:id=>:$id).bind(:id=>1).call(:first)
|
181
|
+
# # SELECT * FROM table WHERE id = ? LIMIT 1 -- (1)
|
182
|
+
# # => {:id=>1}
|
179
183
|
def bind(bind_vars={})
|
180
184
|
clone(:bind_vars=>@opts[:bind_vars] ? @opts[:bind_vars].merge(bind_vars) : bind_vars)
|
181
185
|
end
|
@@ -185,6 +189,10 @@ module Sequel
|
|
185
189
|
# specified in the hash. values is a hash of passed to
|
186
190
|
# insert or update (if one of those types is used),
|
187
191
|
# which may contain placeholders.
|
192
|
+
#
|
193
|
+
# DB[:table].filter(:id=>:$id).call(:first, :id=>1)
|
194
|
+
# # SELECT * FROM table WHERE id = ? LIMIT 1 -- (1)
|
195
|
+
# # => {:id=>1}
|
188
196
|
def call(type, bind_variables={}, *values, &block)
|
189
197
|
prepare(type, nil, *values).call(bind_variables, &block)
|
190
198
|
end
|
@@ -195,9 +203,13 @@ module Sequel
|
|
195
203
|
# do substitution. The prepared statement is also stored in
|
196
204
|
# the associated database. The following usage is identical:
|
197
205
|
#
|
198
|
-
# ps = prepare(:
|
206
|
+
# ps = DB[:table].filter(:name=>:$name).prepare(:first, :select_by_name)
|
207
|
+
#
|
199
208
|
# ps.call(:name=>'Blah')
|
200
|
-
#
|
209
|
+
# # SELECT * FROM table WHERE name = ? -- ('Blah')
|
210
|
+
# # => {:id=>1, :name=>'Blah'}
|
211
|
+
#
|
212
|
+
# DB.call(:select_by_name, :name=>'Blah') # Same thing
|
201
213
|
def prepare(type, name=nil, *values)
|
202
214
|
ps = to_prepared_statement(type, values)
|
203
215
|
db.prepared_statements[name] = ps if name
|
data/lib/sequel/dataset/query.rb
CHANGED
@@ -39,7 +39,7 @@ module Sequel
|
|
39
39
|
# exists an error is raised. This method is identical to #filter except
|
40
40
|
# it expects an existing filter.
|
41
41
|
#
|
42
|
-
#
|
42
|
+
# DB[:table].filter(:a).and(:b) # SELECT * FROM table WHERE a AND b
|
43
43
|
def and(*cond, &block)
|
44
44
|
raise(InvalidOperation, "No existing filter found.") unless @opts[:having] || @opts[:where]
|
45
45
|
filter(*cond, &block)
|
@@ -47,7 +47,8 @@ module Sequel
|
|
47
47
|
|
48
48
|
# Returns a new clone of the dataset with with the given options merged.
|
49
49
|
# If the options changed include options in COLUMN_CHANGE_OPTS, the cached
|
50
|
-
# columns are deleted.
|
50
|
+
# columns are deleted. This method should generally not be called
|
51
|
+
# directly by user code.
|
51
52
|
def clone(opts = {})
|
52
53
|
c = super()
|
53
54
|
c.opts = @opts.merge(opts)
|
@@ -62,8 +63,8 @@ module Sequel
|
|
62
63
|
# of all returned columns. Raises an error if arguments
|
63
64
|
# are given and DISTINCT ON is not supported.
|
64
65
|
#
|
65
|
-
#
|
66
|
-
#
|
66
|
+
# DB[:items].distinct # SQL: SELECT DISTINCT * FROM items
|
67
|
+
# DB[:items].order(:id).distinct(:id) # SQL: SELECT DISTINCT ON (id) * FROM items ORDER BY id
|
67
68
|
def distinct(*args)
|
68
69
|
raise(InvalidOperation, "DISTINCT ON not supported") if !args.empty? && !supports_distinct_on?
|
69
70
|
clone(:distinct => args)
|
@@ -72,13 +73,20 @@ module Sequel
|
|
72
73
|
# Adds an EXCEPT clause using a second dataset object.
|
73
74
|
# An EXCEPT compound dataset returns all rows in the current dataset
|
74
75
|
# that are not in the given dataset.
|
75
|
-
# Raises an InvalidOperation if the operation is not supported.
|
76
|
+
# Raises an +InvalidOperation+ if the operation is not supported.
|
76
77
|
# Options:
|
77
|
-
#
|
78
|
-
#
|
78
|
+
# :alias :: Use the given value as the from_self alias
|
79
|
+
# :all :: Set to true to use EXCEPT ALL instead of EXCEPT, so duplicate rows can occur
|
80
|
+
# :from_self :: Set to false to not wrap the returned dataset in a from_self, use with care.
|
79
81
|
#
|
80
|
-
# DB[:items].except(DB[:other_items])
|
81
|
-
#
|
82
|
+
# DB[:items].except(DB[:other_items])
|
83
|
+
# # SELECT * FROM items EXCEPT SELECT * FROM other_items
|
84
|
+
#
|
85
|
+
# DB[:items].except(DB[:other_items], :all=>true, :from_self=>false)
|
86
|
+
# # SELECT * FROM items EXCEPT ALL SELECT * FROM other_items
|
87
|
+
#
|
88
|
+
# DB[:items].except(DB[:other_items], :alias=>:i)
|
89
|
+
# # SELECT * FROM (SELECT * FROM items EXCEPT SELECT * FROM other_items) AS i
|
82
90
|
def except(dataset, opts={})
|
83
91
|
opts = {:all=>opts} unless opts.is_a?(Hash)
|
84
92
|
raise(InvalidOperation, "EXCEPT not supported") unless supports_intersect_except?
|
@@ -86,10 +94,14 @@ module Sequel
|
|
86
94
|
compound_clone(:except, dataset, opts)
|
87
95
|
end
|
88
96
|
|
89
|
-
# Performs the inverse of Dataset#filter.
|
97
|
+
# Performs the inverse of Dataset#filter. Note that if you have multiple filter
|
98
|
+
# conditions, this is not the same as a negation of all conditions.
|
90
99
|
#
|
91
|
-
#
|
92
|
-
#
|
100
|
+
# DB[:items].exclude(:category => 'software')
|
101
|
+
# # SELECT * FROM items WHERE (category != 'software')
|
102
|
+
#
|
103
|
+
# DB[:items].exclude(:category => 'software', :id=>3)
|
104
|
+
# # SELECT * FROM items WHERE ((category != 'software') OR (id != 3))
|
93
105
|
def exclude(*cond, &block)
|
94
106
|
clause = (@opts[:having] ? :having : :where)
|
95
107
|
cond = cond.first if cond.size == 1
|
@@ -112,6 +124,7 @@ module Sequel
|
|
112
124
|
# * If all members are arrays of length two, treats the same way
|
113
125
|
# as a hash, except it allows for duplicate keys to be
|
114
126
|
# specified.
|
127
|
+
# * Otherwise, treats each argument as a separate condition.
|
115
128
|
# * String - taken literally
|
116
129
|
# * Symbol - taken as a boolean column argument (e.g. WHERE active)
|
117
130
|
# * Sequel::SQL::BooleanExpression - an existing condition expression,
|
@@ -122,29 +135,32 @@ module Sequel
|
|
122
135
|
# which is easy to use to create identifiers and functions. For more details
|
123
136
|
# on the virtual row support, see the {"Virtual Rows" guide}[link:files/doc/virtual_rows_rdoc.html]
|
124
137
|
#
|
125
|
-
# If both a block and regular argument
|
126
|
-
# are provided, they get ANDed together.
|
138
|
+
# If both a block and regular argument are provided, they get ANDed together.
|
127
139
|
#
|
128
140
|
# Examples:
|
129
141
|
#
|
130
|
-
#
|
131
|
-
#
|
132
|
-
#
|
133
|
-
#
|
134
|
-
#
|
135
|
-
#
|
136
|
-
#
|
137
|
-
#
|
138
|
-
#
|
139
|
-
#
|
140
|
-
#
|
141
|
-
#
|
142
|
+
# DB[:items].filter(:id => 3)
|
143
|
+
# # SELECT * FROM items WHERE (id = 3)
|
144
|
+
#
|
145
|
+
# DB[:items].filter('price < ?', 100)
|
146
|
+
# # SELECT * FROM items WHERE price < 100
|
147
|
+
#
|
148
|
+
# DB[:items].filter([[:id, (1,2,3)], [:id, 0..10]])
|
149
|
+
# # SELECT * FROM items WHERE ((id IN (1, 2, 3)) AND ((id >= 0) AND (id <= 10)))
|
150
|
+
#
|
151
|
+
# DB[:items].filter('price < 100')
|
152
|
+
# # SELECT * FROM items WHERE price < 100
|
153
|
+
#
|
154
|
+
# DB[:items].filter(:active)
|
155
|
+
# # SELECT * FROM items WHERE :active
|
156
|
+
#
|
157
|
+
# DB[:items].filter{price < 100}
|
158
|
+
# # SELECT * FROM items WHERE (price < 100)
|
142
159
|
#
|
143
160
|
# Multiple filter calls can be chained for scoping:
|
144
161
|
#
|
145
|
-
# software = dataset.filter(:category => 'software')
|
146
|
-
# software
|
147
|
-
# "SELECT * FROM items WHERE ((category = 'software') AND (price < 100))"
|
162
|
+
# software = dataset.filter(:category => 'software').filter{price < 100}
|
163
|
+
# # SELECT * FROM items WHERE ((category = 'software') AND (price < 100))
|
148
164
|
#
|
149
165
|
# See the the {"Dataset Filtering" guide}[link:files/doc/dataset_filtering_rdoc.html] for more examples and details.
|
150
166
|
def filter(*cond, &block)
|
@@ -152,15 +168,19 @@ module Sequel
|
|
152
168
|
end
|
153
169
|
|
154
170
|
# Returns a cloned dataset with a :update lock style.
|
171
|
+
#
|
172
|
+
# DB[:table].for_update # SELECT * FROM table FOR UPDATE
|
155
173
|
def for_update
|
156
174
|
lock_style(:update)
|
157
175
|
end
|
158
176
|
|
159
|
-
# Returns a copy of the dataset with the source changed.
|
177
|
+
# Returns a copy of the dataset with the source changed. If no
|
178
|
+
# source is given, removes all tables. If multiple sources
|
179
|
+
# are given, it is the same as using a CROSS JOIN (cartesian product) between all tables.
|
160
180
|
#
|
161
|
-
#
|
162
|
-
#
|
163
|
-
#
|
181
|
+
# DB[:items].from # SQL: SELECT *
|
182
|
+
# DB[:items].from(:blah) # SQL: SELECT * FROM blah
|
183
|
+
# DB[:items].from(:blah, :foo) # SQL: SELECT * FROM blah, foo
|
164
184
|
def from(*source)
|
165
185
|
table_alias_num = 0
|
166
186
|
sources = []
|
@@ -188,35 +208,72 @@ module Sequel
|
|
188
208
|
end
|
189
209
|
|
190
210
|
# Returns a dataset selecting from the current dataset.
|
191
|
-
# Supplying the :alias option controls the
|
211
|
+
# Supplying the :alias option controls the alias of the result.
|
192
212
|
#
|
193
213
|
# ds = DB[:items].order(:name).select(:id, :name)
|
194
|
-
#
|
195
|
-
#
|
196
|
-
# ds.from_self
|
214
|
+
# # SELECT id,name FROM items ORDER BY name
|
215
|
+
#
|
216
|
+
# ds.from_self
|
217
|
+
# # SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS t1
|
218
|
+
#
|
219
|
+
# ds.from_self(:alias=>:foo)
|
220
|
+
# # SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS foo
|
197
221
|
def from_self(opts={})
|
198
222
|
fs = {}
|
199
223
|
@opts.keys.each{|k| fs[k] = nil unless NON_SQL_OPTIONS.include?(k)}
|
200
224
|
clone(fs).from(opts[:alias] ? as(opts[:alias]) : self)
|
201
225
|
end
|
202
226
|
|
203
|
-
#
|
204
|
-
# strings (which use LIKE) or regular expressions (which are only
|
205
|
-
#
|
206
|
-
#
|
227
|
+
# Match any of the columns to any of the patterns. The terms can be
|
228
|
+
# strings (which use LIKE) or regular expressions (which are only
|
229
|
+
# supported on MySQL and PostgreSQL). Note that the total number of
|
230
|
+
# pattern matches will be Array(columns).length * Array(terms).length,
|
207
231
|
# which could cause performance issues.
|
208
232
|
#
|
209
|
-
#
|
210
|
-
#
|
211
|
-
|
212
|
-
|
233
|
+
# Options (all are boolean):
|
234
|
+
#
|
235
|
+
# :all_columns :: All columns must be matched to any of the given patterns.
|
236
|
+
# :all_patterns :: All patterns must match at least one of the columns.
|
237
|
+
# :case_insensitive :: Use a case insensitive pattern match (the default is
|
238
|
+
# case sensitive if the database supports it).
|
239
|
+
#
|
240
|
+
# If both :all_columns and :all_patterns are true, all columns must match all patterns.
|
241
|
+
#
|
242
|
+
# Examples:
|
243
|
+
#
|
244
|
+
# dataset.grep(:a, '%test%')
|
245
|
+
# # SELECT * FROM items WHERE (a LIKE '%test%')
|
246
|
+
#
|
247
|
+
# dataset.grep([:a, :b], %w'%test% foo')
|
248
|
+
# # SELECT * FROM items WHERE ((a LIKE '%test%') OR (a LIKE 'foo') OR (b LIKE '%test%') OR (b LIKE 'foo'))
|
249
|
+
#
|
250
|
+
# dataset.grep([:a, :b], %w'%foo% %bar%', :all_patterns=>true)
|
251
|
+
# # SELECT * FROM a WHERE (((a LIKE '%foo%') OR (b LIKE '%foo%')) AND ((a LIKE '%bar%') OR (b LIKE '%bar%')))
|
252
|
+
#
|
253
|
+
# dataset.grep([:a, :b], %w'%foo% %bar%', :all_columns=>true)
|
254
|
+
# # SELECT * FROM a WHERE (((a LIKE '%foo%') OR (a LIKE '%bar%')) AND ((b LIKE '%foo%') OR (b LIKE '%bar%')))
|
255
|
+
#
|
256
|
+
# dataset.grep([:a, :b], %w'%foo% %bar%', :all_patterns=>true, :all_columns=>true)
|
257
|
+
# # SELECT * FROM a WHERE ((a LIKE '%foo%') AND (b LIKE '%foo%') AND (a LIKE '%bar%') AND (b LIKE '%bar%'))
|
258
|
+
def grep(columns, patterns, opts={})
|
259
|
+
if opts[:all_patterns]
|
260
|
+
conds = Array(patterns).map do |pat|
|
261
|
+
SQL::BooleanExpression.new(opts[:all_columns] ? :AND : :OR, *Array(columns).map{|c| SQL::StringExpression.like(c, pat, opts)})
|
262
|
+
end
|
263
|
+
filter(SQL::BooleanExpression.new(opts[:all_patterns] ? :AND : :OR, *conds))
|
264
|
+
else
|
265
|
+
conds = Array(columns).map do |c|
|
266
|
+
SQL::BooleanExpression.new(:OR, *Array(patterns).map{|pat| SQL::StringExpression.like(c, pat, opts)})
|
267
|
+
end
|
268
|
+
filter(SQL::BooleanExpression.new(opts[:all_columns] ? :AND : :OR, *conds))
|
269
|
+
end
|
213
270
|
end
|
214
271
|
|
215
272
|
# Returns a copy of the dataset with the results grouped by the value of
|
216
273
|
# the given columns.
|
217
274
|
#
|
218
|
-
#
|
219
|
-
#
|
275
|
+
# DB[:items].group(:id) # SELECT * FROM items GROUP BY id
|
276
|
+
# DB[:items].group(:id, :name) # SELECT * FROM items GROUP BY id, name
|
220
277
|
def group(*columns)
|
221
278
|
clone(:group => (columns.compact.empty? ? nil : columns))
|
222
279
|
end
|
@@ -231,16 +288,25 @@ module Sequel
|
|
231
288
|
#
|
232
289
|
# Examples:
|
233
290
|
#
|
234
|
-
#
|
235
|
-
#
|
236
|
-
#
|
291
|
+
# DB[:items].group_and_count(:name).all
|
292
|
+
# # SELECT name, count(*) AS count FROM items GROUP BY name
|
293
|
+
# # => [{:name=>'a', :count=>1}, ...]
|
294
|
+
#
|
295
|
+
# DB[:items].group_and_count(:first_name, :last_name).all
|
296
|
+
# # SELECT first_name, last_name, count(*) AS count FROM items GROUP BY first_name, last_name
|
297
|
+
# # => [{:first_name=>'a', :last_name=>'b', :count=>1}, ...]
|
298
|
+
#
|
299
|
+
# DB[:items].group_and_count(:first_name___name).all
|
300
|
+
# # SELECT first_name AS name, count(*) AS count FROM items GROUP BY first_name
|
301
|
+
# # => [{:name=>'a', :count=>1}, ...]
|
237
302
|
def group_and_count(*columns)
|
238
303
|
group(*columns.map{|c| unaliased_identifier(c)}).select(*(columns + [COUNT_OF_ALL_AS_COUNT]))
|
239
304
|
end
|
240
305
|
|
241
306
|
# Returns a copy of the dataset with the HAVING conditions changed. See #filter for argument types.
|
242
307
|
#
|
243
|
-
#
|
308
|
+
# DB[:items].group(:sum).having(:sum=>10)
|
309
|
+
# # SELECT * FROM items GROUP BY sum HAVING (sum = 10)
|
244
310
|
def having(*cond, &block)
|
245
311
|
_filter(:having, *cond, &block)
|
246
312
|
end
|
@@ -248,13 +314,20 @@ module Sequel
|
|
248
314
|
# Adds an INTERSECT clause using a second dataset object.
|
249
315
|
# An INTERSECT compound dataset returns all rows in both the current dataset
|
250
316
|
# and the given dataset.
|
251
|
-
# Raises an InvalidOperation if the operation is not supported.
|
317
|
+
# Raises an +InvalidOperation+ if the operation is not supported.
|
252
318
|
# Options:
|
253
|
-
#
|
254
|
-
#
|
319
|
+
# :alias :: Use the given value as the from_self alias
|
320
|
+
# :all :: Set to true to use INTERSECT ALL instead of INTERSECT, so duplicate rows can occur
|
321
|
+
# :from_self :: Set to false to not wrap the returned dataset in a from_self, use with care.
|
255
322
|
#
|
256
|
-
# DB[:items].intersect(DB[:other_items])
|
257
|
-
#
|
323
|
+
# DB[:items].intersect(DB[:other_items])
|
324
|
+
# # SELECT * FROM (SELECT * FROM items INTERSECT SELECT * FROM other_items) AS t1
|
325
|
+
#
|
326
|
+
# DB[:items].intersect(DB[:other_items], :all=>true, :from_self=>false)
|
327
|
+
# # SELECT * FROM items INTERSECT ALL SELECT * FROM other_items
|
328
|
+
#
|
329
|
+
# DB[:items].intersect(DB[:other_items], :alias=>:i)
|
330
|
+
# # SELECT * FROM (SELECT * FROM items INTERSECT SELECT * FROM other_items) AS i
|
258
331
|
def intersect(dataset, opts={})
|
259
332
|
opts = {:all=>opts} unless opts.is_a?(Hash)
|
260
333
|
raise(InvalidOperation, "INTERSECT not supported") unless supports_intersect_except?
|
@@ -262,10 +335,13 @@ module Sequel
|
|
262
335
|
compound_clone(:intersect, dataset, opts)
|
263
336
|
end
|
264
337
|
|
265
|
-
# Inverts the current filter
|
338
|
+
# Inverts the current filter.
|
339
|
+
#
|
340
|
+
# DB[:items].filter(:category => 'software').invert
|
341
|
+
# # SELECT * FROM items WHERE (category != 'software')
|
266
342
|
#
|
267
|
-
#
|
268
|
-
#
|
343
|
+
# DB[:items].filter(:category => 'software', :id=>3).invert
|
344
|
+
# # SELECT * FROM items WHERE ((category != 'software') OR (id != 3))
|
269
345
|
def invert
|
270
346
|
having, where = @opts[:having], @opts[:where]
|
271
347
|
raise(Error, "No current filter") unless having || where
|
@@ -275,7 +351,7 @@ module Sequel
|
|
275
351
|
clone(o)
|
276
352
|
end
|
277
353
|
|
278
|
-
# Alias of inner_join
|
354
|
+
# Alias of +inner_join+
|
279
355
|
def join(*args, &block)
|
280
356
|
inner_join(*args, &block)
|
281
357
|
end
|
@@ -288,7 +364,7 @@ module Sequel
|
|
288
364
|
# * Model (or anything responding to :table_name) - table.table_name
|
289
365
|
# * String, Symbol: table
|
290
366
|
# * expr - specifies conditions, depends on type:
|
291
|
-
# * Hash, Array
|
367
|
+
# * Hash, Array of two element arrays - Assumes key (1st arg) is column of joined table (unless already
|
292
368
|
# qualified), and value (2nd arg) is column of the last joined or primary table (or the
|
293
369
|
# :implicit_qualifier option).
|
294
370
|
# To specify multiple conditions on a single joined table column, you must use an array.
|
@@ -297,10 +373,10 @@ module Sequel
|
|
297
373
|
# uses a JOIN with a USING clause. Most databases will remove duplicate columns from
|
298
374
|
# the result set if this is used.
|
299
375
|
# * nil - If a block is not given, doesn't use ON or USING, so the JOIN should be a NATURAL
|
300
|
-
# or CROSS join. If a block is given, uses
|
376
|
+
# or CROSS join. If a block is given, uses an ON clause based on the block, see below.
|
301
377
|
# * Everything else - pretty much the same as a using the argument in a call to filter,
|
302
|
-
# so strings are considered literal, symbols specify boolean columns, and
|
303
|
-
#
|
378
|
+
# so strings are considered literal, symbols specify boolean columns, and Sequel
|
379
|
+
# expressions can be used. Uses a JOIN with an ON clause.
|
304
380
|
# * options - a hash of options, with any of the following keys:
|
305
381
|
# * :table_alias - the name of the table's alias when joining, necessary for joining
|
306
382
|
# to the same table more than once. No alias is used by default.
|
@@ -309,7 +385,7 @@ module Sequel
|
|
309
385
|
# * block - The block argument should only be given if a JOIN with an ON clause is used,
|
310
386
|
# in which case it yields the table alias/name for the table currently being joined,
|
311
387
|
# the table alias/name for the last joined (or first table), and an array of previous
|
312
|
-
# SQL::JoinClause.
|
388
|
+
# SQL::JoinClause. Unlike +filter+, this block is not treated as a virtual row block.
|
313
389
|
def join_table(type, table, expr=nil, options={}, &block)
|
314
390
|
using_join = expr.is_a?(Array) && !expr.empty? && expr.all?{|x| x.is_a?(Symbol)}
|
315
391
|
if using_join && !supports_join_using?
|
@@ -375,10 +451,14 @@ module Sequel
|
|
375
451
|
|
376
452
|
# If given an integer, the dataset will contain only the first l results.
|
377
453
|
# If given a range, it will contain only those at offsets within that
|
378
|
-
# range. If a second argument is given, it is used as an offset.
|
379
|
-
#
|
380
|
-
#
|
381
|
-
#
|
454
|
+
# range. If a second argument is given, it is used as an offset. To use
|
455
|
+
# an offset without a limit, pass nil as the first argument.
|
456
|
+
#
|
457
|
+
# DB[:items].limit(10) # SELECT * FROM items LIMIT 10
|
458
|
+
# DB[:items].limit(10, 20) # SELECT * FROM items LIMIT 10 OFFSET 20
|
459
|
+
# DB[:items].limit(10...20) # SELECT * FROM items LIMIT 10 OFFSET 10
|
460
|
+
# DB[:items].limit(10..20) # SELECT * FROM items LIMIT 11 OFFSET 10
|
461
|
+
# DB[:items].limit(nil, 20) # SELECT * FROM items OFFSET 20
|
382
462
|
def limit(l, o = nil)
|
383
463
|
return from_self.limit(l, o) if @opts[:sql]
|
384
464
|
|
@@ -405,12 +485,18 @@ module Sequel
|
|
405
485
|
# string, it will be used directly. Otherwise, a symbol may be used
|
406
486
|
# for database independent locking. Currently :update is respected
|
407
487
|
# by most databases, and :share is supported by some.
|
488
|
+
#
|
489
|
+
# DB[:items].lock_style('FOR SHARE') # SELECT * FROM items FOR SHARE
|
408
490
|
def lock_style(style)
|
409
491
|
clone(:lock => style)
|
410
492
|
end
|
411
493
|
|
412
|
-
# Returns a
|
413
|
-
#
|
494
|
+
# Returns a cloned dataset without a row_proc.
|
495
|
+
#
|
496
|
+
# ds = DB[:items]
|
497
|
+
# ds.row_proc = proc{|r| r.invert}
|
498
|
+
# ds.all # => [{2=>:id}]
|
499
|
+
# ds.naked.all # => [{:id=>2}]
|
414
500
|
def naked
|
415
501
|
ds = clone
|
416
502
|
ds.row_proc = nil
|
@@ -418,9 +504,9 @@ module Sequel
|
|
418
504
|
end
|
419
505
|
|
420
506
|
# Adds an alternate filter to an existing filter using OR. If no filter
|
421
|
-
# exists an
|
507
|
+
# exists an +Error+ is raised.
|
422
508
|
#
|
423
|
-
#
|
509
|
+
# DB[:items].filter(:a).or(:b) # SELECT * FROM items WHERE a OR b
|
424
510
|
def or(*cond, &block)
|
425
511
|
clause = (@opts[:having] ? :having : :where)
|
426
512
|
raise(InvalidOperation, "No existing filter found.") unless @opts[clause]
|
@@ -428,19 +514,20 @@ module Sequel
|
|
428
514
|
clone(clause => SQL::BooleanExpression.new(:OR, @opts[clause], filter_expr(cond, &block)))
|
429
515
|
end
|
430
516
|
|
431
|
-
# Returns a copy of the dataset with the order changed. If
|
517
|
+
# Returns a copy of the dataset with the order changed. If the dataset has an
|
518
|
+
# existing order, it is ignored and overwritten with this order. If a nil is given
|
432
519
|
# the returned dataset has no order. This can accept multiple arguments
|
433
|
-
# of varying kinds,
|
434
|
-
# as a virtual row block, similar to filter
|
435
|
-
#
|
436
|
-
#
|
437
|
-
#
|
438
|
-
#
|
439
|
-
#
|
440
|
-
#
|
441
|
-
#
|
442
|
-
#
|
443
|
-
#
|
520
|
+
# of varying kinds, such as SQL functions. If a block is given, it is treated
|
521
|
+
# as a virtual row block, similar to +filter+.
|
522
|
+
#
|
523
|
+
# DB[:items].order(:name) # SELECT * FROM items ORDER BY name
|
524
|
+
# DB[:items].order(:a, :b) # SELECT * FROM items ORDER BY a, b
|
525
|
+
# DB[:items].order('a + b'.lit) # SELECT * FROM items ORDER BY a + b
|
526
|
+
# DB[:items].order(:a + :b) # SELECT * FROM items ORDER BY (a + b)
|
527
|
+
# DB[:items].order(:name.desc) # SELECT * FROM items ORDER BY name DESC
|
528
|
+
# DB[:items].order(:name.asc(:nulls=>:last)) # SELECT * FROM items ORDER BY name ASC NULLS LAST
|
529
|
+
# DB[:items].order{sum(name).desc} # SELECT * FROM items ORDER BY sum(name) DESC
|
530
|
+
# DB[:items].order(nil) # SELECT * FROM items
|
444
531
|
def order(*columns, &block)
|
445
532
|
columns += Array(Sequel.virtual_row(&block)) if block
|
446
533
|
clone(:order => (columns.compact.empty?) ? nil : columns)
|
@@ -459,8 +546,8 @@ module Sequel
|
|
459
546
|
# Returns a copy of the dataset with the order columns added
|
460
547
|
# to the end of the existing order.
|
461
548
|
#
|
462
|
-
#
|
463
|
-
#
|
549
|
+
# DB[:items].order(:a).order(:b) # SELECT * FROM items ORDER BY b
|
550
|
+
# DB[:items].order(:a).order_more(:b) # SELECT * FROM items ORDER BY a, b
|
464
551
|
def order_more(*columns, &block)
|
465
552
|
columns = @opts[:order] + columns if @opts[:order]
|
466
553
|
order(*columns, &block)
|
@@ -469,14 +556,20 @@ module Sequel
|
|
469
556
|
# Returns a copy of the dataset with the order columns added
|
470
557
|
# to the beginning of the existing order.
|
471
558
|
#
|
472
|
-
#
|
473
|
-
#
|
559
|
+
# DB[:items].order(:a).order(:b) # SELECT * FROM items ORDER BY b
|
560
|
+
# DB[:items].order(:a).order_prepend(:b) # SELECT * FROM items ORDER BY b, a
|
474
561
|
def order_prepend(*columns, &block)
|
475
562
|
ds = order(*columns, &block)
|
476
563
|
@opts[:order] ? ds.order_more(*@opts[:order]) : ds
|
477
564
|
end
|
478
565
|
|
479
566
|
# Qualify to the given table, or first source if not table is given.
|
567
|
+
#
|
568
|
+
# DB[:items].filter(:id=>1).qualify
|
569
|
+
# # SELECT items.* FROM items WHERE (items.id = 1)
|
570
|
+
#
|
571
|
+
# DB[:items].filter(:id=>1).qualify(:i)
|
572
|
+
# # SELECT i.* FROM items WHERE (i.id = 1)
|
480
573
|
def qualify(table=first_source)
|
481
574
|
qualify_to(table)
|
482
575
|
end
|
@@ -485,6 +578,9 @@ module Sequel
|
|
485
578
|
# SELECT, WHERE, GROUP, HAVING, and ORDER clauses qualified by the
|
486
579
|
# given table. If no columns are currently selected, select all
|
487
580
|
# columns of the given table.
|
581
|
+
#
|
582
|
+
# DB[:items].filter(:id=>1).qualify_to(:i)
|
583
|
+
# # SELECT i.* FROM items WHERE (i.id = 1)
|
488
584
|
def qualify_to(table)
|
489
585
|
o = @opts
|
490
586
|
return clone if o[:sql]
|
@@ -500,29 +596,36 @@ module Sequel
|
|
500
596
|
# if you have unqualified identifiers in the query that all refer to
|
501
597
|
# the first source, and you want to join to another table which
|
502
598
|
# has columns with the same name as columns in the current dataset.
|
503
|
-
# See qualify_to
|
599
|
+
# See +qualify_to+.
|
600
|
+
#
|
601
|
+
# DB[:items].filter(:id=>1).qualify_to_first_source
|
602
|
+
# # SELECT items.* FROM items WHERE (items.id = 1)
|
504
603
|
def qualify_to_first_source
|
505
604
|
qualify_to(first_source)
|
506
605
|
end
|
507
606
|
|
508
607
|
# Returns a copy of the dataset with the order reversed. If no order is
|
509
608
|
# given, the existing order is inverted.
|
609
|
+
#
|
610
|
+
# DB[:items].reverse(:id) # SELECT * FROM items ORDER BY id DESC
|
611
|
+
# DB[:items].order(:id).reverse # SELECT * FROM items ORDER BY id DESC
|
612
|
+
# DB[:items].order(:id).reverse(:name.asc) # SELECT * FROM items ORDER BY name ASC
|
510
613
|
def reverse(*order)
|
511
614
|
order(*invert_order(order.empty? ? @opts[:order] : order))
|
512
615
|
end
|
513
616
|
|
514
|
-
# Alias of reverse
|
617
|
+
# Alias of +reverse+
|
515
618
|
def reverse_order(*order)
|
516
619
|
reverse(*order)
|
517
620
|
end
|
518
621
|
|
519
622
|
# Returns a copy of the dataset with the columns selected changed
|
520
623
|
# to the given columns. This also takes a virtual row block,
|
521
|
-
# similar to filter
|
624
|
+
# similar to +filter+.
|
522
625
|
#
|
523
|
-
#
|
524
|
-
#
|
525
|
-
#
|
626
|
+
# DB[:items].select(:a) # SELECT a FROM items
|
627
|
+
# DB[:items].select(:a, :b) # SELECT a, b FROM items
|
628
|
+
# DB[:items].select{[a, sum(b)]} # SELECT a, sum(b) FROM items
|
526
629
|
def select(*columns, &block)
|
527
630
|
columns += Array(Sequel.virtual_row(&block)) if block
|
528
631
|
m = []
|
@@ -534,18 +637,18 @@ module Sequel
|
|
534
637
|
|
535
638
|
# Returns a copy of the dataset selecting the wildcard.
|
536
639
|
#
|
537
|
-
#
|
640
|
+
# DB[:items].select(:a).select_all # SELECT * FROM items
|
538
641
|
def select_all
|
539
642
|
clone(:select => nil)
|
540
643
|
end
|
541
644
|
|
542
645
|
# Returns a copy of the dataset with the given columns added
|
543
|
-
# to the existing selected columns. If no columns are currently selected
|
646
|
+
# to the existing selected columns. If no columns are currently selected,
|
544
647
|
# it will select the columns given in addition to *.
|
545
648
|
#
|
546
|
-
#
|
547
|
-
#
|
548
|
-
#
|
649
|
+
# DB[:items].select(:a).select(:b) # SELECT b FROM items
|
650
|
+
# DB[:items].select(:a).select_append(:b) # SELECT a, b FROM items
|
651
|
+
# DB[:items].select_append(:b) # SELECT *, b FROM items
|
549
652
|
def select_append(*columns, &block)
|
550
653
|
cur_sel = @opts[:select]
|
551
654
|
cur_sel = [WILDCARD] if !cur_sel || cur_sel.empty?
|
@@ -556,9 +659,9 @@ module Sequel
|
|
556
659
|
# to the existing selected columns. If no columns are currently selected
|
557
660
|
# it will just select the columns given.
|
558
661
|
#
|
559
|
-
#
|
560
|
-
#
|
561
|
-
#
|
662
|
+
# DB[:items].select(:a).select(:b) # SELECT b FROM items
|
663
|
+
# DB[:items].select(:a).select_more(:b) # SELECT a, b FROM items
|
664
|
+
# DB[:items].select_more(:b) # SELECT b FROM items
|
562
665
|
def select_more(*columns, &block)
|
563
666
|
columns = @opts[:select] + columns if @opts[:select]
|
564
667
|
select(*columns, &block)
|
@@ -566,33 +669,49 @@ module Sequel
|
|
566
669
|
|
567
670
|
# Set the server for this dataset to use. Used to pick a specific database
|
568
671
|
# shard to run a query against, or to override the default (which is SELECT uses
|
569
|
-
# :read_only database and all other queries use the :default database).
|
672
|
+
# :read_only database and all other queries use the :default database). This
|
673
|
+
# method is always available but is only useful when database sharding is being
|
674
|
+
# used.
|
675
|
+
#
|
676
|
+
# DB[:items].all # Uses the :read_only or :default server
|
677
|
+
# DB[:items].delete # Uses the :default server
|
678
|
+
# DB[:items].server(:blah).delete # Uses the :blah server
|
570
679
|
def server(servr)
|
571
680
|
clone(:server=>servr)
|
572
681
|
end
|
573
682
|
|
574
683
|
# Set the default values for insert and update statements. The values hash passed
|
575
|
-
# to insert or update are merged into this hash
|
684
|
+
# to insert or update are merged into this hash, so any values in the hash passed
|
685
|
+
# to insert or update will override values passed to this method.
|
686
|
+
#
|
687
|
+
# DB[:items].set_defaults(:a=>'a', :c=>'c').insert(:a=>'d', :b=>'b')
|
688
|
+
# # INSERT INTO items (a, c, b) VALUES ('d', 'c', 'b')
|
576
689
|
def set_defaults(hash)
|
577
690
|
clone(:defaults=>(@opts[:defaults]||{}).merge(hash))
|
578
691
|
end
|
579
692
|
|
580
693
|
# Set values that override hash arguments given to insert and update statements.
|
581
|
-
# This hash is merged into the hash provided to insert or update
|
694
|
+
# This hash is merged into the hash provided to insert or update, so values
|
695
|
+
# will override any values given in the insert/update hashes.
|
696
|
+
#
|
697
|
+
# DB[:items].set_overrides(:a=>'a', :c=>'c').insert(:a=>'d', :b=>'b')
|
698
|
+
# # INSERT INTO items (a, c, b) VALUES ('a', 'c', 'b')
|
582
699
|
def set_overrides(hash)
|
583
700
|
clone(:overrides=>hash.merge(@opts[:overrides]||{}))
|
584
701
|
end
|
585
702
|
|
586
703
|
# Returns a copy of the dataset with no filters (HAVING or WHERE clause) applied.
|
587
704
|
#
|
588
|
-
#
|
705
|
+
# DB[:items].group(:a).having(:a=>1).where(:b).unfiltered
|
706
|
+
# # SELECT * FROM items GROUP BY a
|
589
707
|
def unfiltered
|
590
708
|
clone(:where => nil, :having => nil)
|
591
709
|
end
|
592
710
|
|
593
711
|
# Returns a copy of the dataset with no grouping (GROUP or HAVING clause) applied.
|
594
712
|
#
|
595
|
-
#
|
713
|
+
# DB[:items].group(:a).having(:a=>1).where(:b).ungrouped
|
714
|
+
# # SELECT * FROM items WHERE b
|
596
715
|
def ungrouped
|
597
716
|
clone(:group => nil, :having => nil)
|
598
717
|
end
|
@@ -601,11 +720,18 @@ module Sequel
|
|
601
720
|
# A UNION compound dataset returns all rows in either the current dataset
|
602
721
|
# or the given dataset.
|
603
722
|
# Options:
|
604
|
-
#
|
605
|
-
#
|
723
|
+
# :alias :: Use the given value as the from_self alias
|
724
|
+
# :all :: Set to true to use UNION ALL instead of UNION, so duplicate rows can occur
|
725
|
+
# :from_self :: Set to false to not wrap the returned dataset in a from_self, use with care.
|
606
726
|
#
|
607
727
|
# DB[:items].union(DB[:other_items]).sql
|
608
728
|
# #=> "SELECT * FROM items UNION SELECT * FROM other_items"
|
729
|
+
#
|
730
|
+
# DB[:items].union(DB[:other_items], :all=>true, :from_self=>false)
|
731
|
+
# # SELECT * FROM items UNION ALL SELECT * FROM other_items
|
732
|
+
#
|
733
|
+
# DB[:items].union(DB[:other_items], :alias=>:i)
|
734
|
+
# # SELECT * FROM (SELECT * FROM items UNION SELECT * FROM other_items) AS i
|
609
735
|
def union(dataset, opts={})
|
610
736
|
opts = {:all=>opts} unless opts.is_a?(Hash)
|
611
737
|
compound_clone(:union, dataset, opts)
|
@@ -613,31 +739,37 @@ module Sequel
|
|
613
739
|
|
614
740
|
# Returns a copy of the dataset with no limit or offset.
|
615
741
|
#
|
616
|
-
#
|
742
|
+
# DB[:items].limit(10, 20).unlimited # SELECT * FROM items
|
617
743
|
def unlimited
|
618
744
|
clone(:limit=>nil, :offset=>nil)
|
619
745
|
end
|
620
746
|
|
621
747
|
# Returns a copy of the dataset with no order.
|
622
748
|
#
|
623
|
-
#
|
749
|
+
# DB[:items].order(:a).unordered # SELECT * FROM items
|
624
750
|
def unordered
|
625
751
|
order(nil)
|
626
752
|
end
|
627
753
|
|
628
|
-
# Add a condition to the WHERE clause. See
|
754
|
+
# Add a condition to the WHERE clause. See +filter+ for argument types.
|
629
755
|
#
|
630
|
-
#
|
631
|
-
#
|
756
|
+
# DB[:items].group(:a).having(:a).filter(:b)
|
757
|
+
# # SELECT * FROM items GROUP BY a HAVING a AND b
|
758
|
+
#
|
759
|
+
# DB[:items].group(:a).having(:a).where(:b)
|
760
|
+
# # SELECT * FROM items WHERE b GROUP BY a HAVING a
|
632
761
|
def where(*cond, &block)
|
633
762
|
_filter(:where, *cond, &block)
|
634
763
|
end
|
635
764
|
|
636
|
-
# Add a
|
765
|
+
# Add a common table expression (CTE) with the given name and a dataset that defines the CTE.
|
637
766
|
# A common table expression acts as an inline view for the query.
|
638
767
|
# Options:
|
639
|
-
#
|
640
|
-
#
|
768
|
+
# :args :: Specify the arguments/columns for the CTE, should be an array of symbols.
|
769
|
+
# :recursive :: Specify that this is a recursive CTE
|
770
|
+
#
|
771
|
+
# DB[:items].with(:items, DB[:syx].filter(:name.like('A%')))
|
772
|
+
# # WITH items AS (SELECT * FROM syx WHERE (name LIKE 'A%')) SELECT * FROM items
|
641
773
|
def with(name, dataset, opts={})
|
642
774
|
raise(Error, 'This datatset does not support common table expressions') unless supports_cte?
|
643
775
|
clone(:with=>(@opts[:with]||[]) + [opts.merge(:name=>name, :dataset=>dataset)])
|
@@ -646,8 +778,20 @@ module Sequel
|
|
646
778
|
# Add a recursive common table expression (CTE) with the given name, a dataset that
|
647
779
|
# defines the nonrecursive part of the CTE, and a dataset that defines the recursive part
|
648
780
|
# of the CTE. Options:
|
649
|
-
#
|
650
|
-
#
|
781
|
+
# :args :: Specify the arguments/columns for the CTE, should be an array of symbols.
|
782
|
+
# :union_all :: Set to false to use UNION instead of UNION ALL combining the nonrecursive and recursive parts.
|
783
|
+
#
|
784
|
+
# DB[:t].select(:i___id, :pi___parent_id).
|
785
|
+
# with_recursive(:t,
|
786
|
+
# DB[:i1].filter(:parent_id=>nil),
|
787
|
+
# DB[:t].join(:t, :i=>:parent_id).select(:i1__id, :i1__parent_id),
|
788
|
+
# :args=>[:i, :pi])
|
789
|
+
# # WITH RECURSIVE t(i, pi) AS (
|
790
|
+
# # SELECT * FROM i1 WHERE (parent_id IS NULL)
|
791
|
+
# # UNION ALL
|
792
|
+
# # SELECT i1.id, i1.parent_id FROM t INNER JOIN t ON (t.i = t.parent_id)
|
793
|
+
# # )
|
794
|
+
# # SELECT i AS id, pi AS parent_id FROM t
|
651
795
|
def with_recursive(name, nonrecursive, recursive, opts={})
|
652
796
|
raise(Error, 'This datatset does not support common table expressions') unless supports_cte?
|
653
797
|
clone(:with=>(@opts[:with]||[]) + [opts.merge(:recursive=>true, :name=>name, :dataset=>nonrecursive.union(recursive, {:all=>opts[:union_all] != false, :from_self=>false}))])
|
@@ -656,7 +800,7 @@ module Sequel
|
|
656
800
|
# Returns a copy of the dataset with the static SQL used. This is useful if you want
|
657
801
|
# to keep the same row_proc/graph, but change the SQL used to custom SQL.
|
658
802
|
#
|
659
|
-
#
|
803
|
+
# DB[:items].with_sql('SELECT * FROM foo') # SELECT * FROM foo
|
660
804
|
def with_sql(sql, *args)
|
661
805
|
sql = SQL::PlaceholderLiteralString.new(sql, args) unless args.empty?
|
662
806
|
clone(:sql=>sql)
|
@@ -695,7 +839,7 @@ module Sequel
|
|
695
839
|
opts[:from_self] == false ? ds : ds.from_self(opts)
|
696
840
|
end
|
697
841
|
|
698
|
-
# SQL
|
842
|
+
# SQL expression object based on the expr type. See +filter+.
|
699
843
|
def filter_expr(expr = nil, &block)
|
700
844
|
expr = nil if expr == []
|
701
845
|
if expr && block
|
@@ -732,9 +876,8 @@ module Sequel
|
|
732
876
|
# Inverts the given order by breaking it into a list of column references
|
733
877
|
# and inverting them.
|
734
878
|
#
|
735
|
-
#
|
736
|
-
#
|
737
|
-
# [:category.desc, :price]
|
879
|
+
# DB[:items].invert_order([:id.desc]]) #=> [:id]
|
880
|
+
# DB[:items].invert_order(:category, :price.desc]) #=> [:category.desc, :price]
|
738
881
|
def invert_order(order)
|
739
882
|
return nil unless order
|
740
883
|
new_order = []
|