sequel 3.13.0 → 3.14.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 = []
|