sequel 3.10.0 → 3.11.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +68 -0
- data/COPYING +1 -1
- data/README.rdoc +87 -27
- data/bin/sequel +2 -4
- data/doc/association_basics.rdoc +1383 -0
- data/doc/dataset_basics.rdoc +106 -0
- data/doc/opening_databases.rdoc +45 -16
- data/doc/querying.rdoc +210 -0
- data/doc/release_notes/3.11.0.txt +254 -0
- data/doc/virtual_rows.rdoc +217 -31
- data/lib/sequel/adapters/ado.rb +28 -12
- data/lib/sequel/adapters/ado/mssql.rb +33 -1
- data/lib/sequel/adapters/amalgalite.rb +13 -8
- data/lib/sequel/adapters/db2.rb +1 -2
- data/lib/sequel/adapters/dbi.rb +7 -4
- data/lib/sequel/adapters/do.rb +14 -15
- data/lib/sequel/adapters/do/postgres.rb +4 -5
- data/lib/sequel/adapters/do/sqlite.rb +9 -0
- data/lib/sequel/adapters/firebird.rb +5 -10
- data/lib/sequel/adapters/informix.rb +2 -4
- data/lib/sequel/adapters/jdbc.rb +111 -49
- data/lib/sequel/adapters/jdbc/mssql.rb +1 -2
- data/lib/sequel/adapters/jdbc/mysql.rb +11 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +4 -7
- data/lib/sequel/adapters/jdbc/postgresql.rb +8 -1
- data/lib/sequel/adapters/jdbc/sqlite.rb +12 -0
- data/lib/sequel/adapters/mysql.rb +14 -5
- data/lib/sequel/adapters/odbc.rb +2 -4
- data/lib/sequel/adapters/odbc/mssql.rb +2 -4
- data/lib/sequel/adapters/openbase.rb +1 -2
- data/lib/sequel/adapters/oracle.rb +4 -8
- data/lib/sequel/adapters/postgres.rb +4 -11
- data/lib/sequel/adapters/shared/mssql.rb +22 -9
- data/lib/sequel/adapters/shared/mysql.rb +33 -30
- data/lib/sequel/adapters/shared/oracle.rb +0 -5
- data/lib/sequel/adapters/shared/postgres.rb +13 -11
- data/lib/sequel/adapters/shared/sqlite.rb +56 -10
- data/lib/sequel/adapters/sqlite.rb +16 -9
- data/lib/sequel/connection_pool.rb +6 -1
- data/lib/sequel/connection_pool/single.rb +1 -0
- data/lib/sequel/core.rb +6 -1
- data/lib/sequel/database.rb +52 -23
- data/lib/sequel/database/schema_generator.rb +6 -0
- data/lib/sequel/database/schema_methods.rb +5 -5
- data/lib/sequel/database/schema_sql.rb +1 -1
- data/lib/sequel/dataset.rb +4 -190
- data/lib/sequel/dataset/actions.rb +323 -1
- data/lib/sequel/dataset/features.rb +18 -2
- data/lib/sequel/dataset/graph.rb +7 -0
- data/lib/sequel/dataset/misc.rb +119 -0
- data/lib/sequel/dataset/mutation.rb +64 -0
- data/lib/sequel/dataset/prepared_statements.rb +6 -0
- data/lib/sequel/dataset/query.rb +272 -6
- data/lib/sequel/dataset/sql.rb +186 -394
- data/lib/sequel/model.rb +4 -2
- data/lib/sequel/model/associations.rb +31 -14
- data/lib/sequel/model/base.rb +32 -13
- data/lib/sequel/model/exceptions.rb +8 -4
- data/lib/sequel/model/plugins.rb +3 -13
- data/lib/sequel/plugins/active_model.rb +26 -7
- data/lib/sequel/plugins/instance_filters.rb +98 -0
- data/lib/sequel/plugins/many_through_many.rb +1 -1
- data/lib/sequel/plugins/optimistic_locking.rb +25 -9
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +26 -0
- data/spec/adapters/mysql_spec.rb +33 -4
- data/spec/adapters/postgres_spec.rb +24 -1
- data/spec/adapters/spec_helper.rb +6 -0
- data/spec/adapters/sqlite_spec.rb +28 -0
- data/spec/core/connection_pool_spec.rb +17 -5
- data/spec/core/database_spec.rb +101 -1
- data/spec/core/dataset_spec.rb +42 -4
- data/spec/core/schema_spec.rb +13 -0
- data/spec/extensions/active_model_spec.rb +34 -11
- data/spec/extensions/caching_spec.rb +2 -0
- data/spec/extensions/instance_filters_spec.rb +55 -0
- data/spec/extensions/spec_helper.rb +2 -0
- data/spec/integration/dataset_test.rb +12 -1
- data/spec/integration/model_test.rb +12 -0
- data/spec/integration/plugin_test.rb +61 -1
- data/spec/integration/schema_test.rb +14 -3
- data/spec/model/base_spec.rb +27 -0
- data/spec/model/plugins_spec.rb +0 -22
- data/spec/model/record_spec.rb +32 -1
- data/spec/model/spec_helper.rb +2 -0
- metadata +14 -3
- data/lib/sequel/dataset/convenience.rb +0 -326
@@ -1,10 +1,26 @@
|
|
1
1
|
module Sequel
|
2
2
|
class Dataset
|
3
|
+
# ---------------------
|
4
|
+
# :section: Methods that describe what the dataset supports
|
5
|
+
# These methods all return booleans, with most describing whether or not the
|
6
|
+
# dataset supports a feature.
|
7
|
+
# ---------------------
|
8
|
+
|
9
|
+
# Method used to check if WITH is supported
|
10
|
+
WITH_SUPPORTED=:select_with_sql
|
11
|
+
|
3
12
|
# Whether this dataset quotes identifiers.
|
4
13
|
def quote_identifiers?
|
5
14
|
@quote_identifiers
|
6
15
|
end
|
7
16
|
|
17
|
+
# Whether this dataset will provide accurate number of rows matched for
|
18
|
+
# delete and update statements. Accurate in this case is the number of
|
19
|
+
# rows matched by the dataset's filter.
|
20
|
+
def provides_accurate_rows_matched?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
8
24
|
# Whether the dataset requires SQL standard datetimes (false by default,
|
9
25
|
# as most allow strings with ISO 8601 format.
|
10
26
|
def requires_sql_standard_datetimes?
|
@@ -16,9 +32,9 @@ module Sequel
|
|
16
32
|
select_clause_methods.include?(WITH_SUPPORTED)
|
17
33
|
end
|
18
34
|
|
19
|
-
# Whether the dataset supports the DISTINCT ON clause,
|
35
|
+
# Whether the dataset supports the DISTINCT ON clause, false by default.
|
20
36
|
def supports_distinct_on?
|
21
|
-
|
37
|
+
false
|
22
38
|
end
|
23
39
|
|
24
40
|
# Whether the dataset supports the INTERSECT and EXCEPT compound operations, true by default.
|
data/lib/sequel/dataset/graph.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
module Sequel
|
2
2
|
class Dataset
|
3
|
+
# ---------------------
|
4
|
+
# :section: Methods related to dataset graphing
|
5
|
+
# Dataset graphing changes the dataset to yield hashes where keys are table
|
6
|
+
# name symbols and columns are hashes representing the values related to
|
7
|
+
# that table. All of these methods return modified copies of the receiver.
|
8
|
+
# ---------------------
|
9
|
+
|
3
10
|
# Adds the given graph aliases to the list of graph aliases to use,
|
4
11
|
# unlike #set_graph_aliases, which replaces the list. See
|
5
12
|
# #set_graph_aliases.
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module Sequel
|
2
|
+
class Dataset
|
3
|
+
# ---------------------
|
4
|
+
# :section: Miscellaneous methods
|
5
|
+
# These methods don't fit cleanly into another section.
|
6
|
+
# ---------------------
|
7
|
+
|
8
|
+
NOTIMPL_MSG = "This method must be overridden in Sequel adapters".freeze
|
9
|
+
ARRAY_ACCESS_ERROR_MSG = 'You cannot call Dataset#[] with an integer or with no arguments.'.freeze
|
10
|
+
ARG_BLOCK_ERROR_MSG = 'Must use either an argument or a block, not both'.freeze
|
11
|
+
IMPORT_ERROR_MSG = 'Using Sequel::Dataset#import an empty column array is not allowed'.freeze
|
12
|
+
|
13
|
+
# The database that corresponds to this dataset
|
14
|
+
attr_accessor :db
|
15
|
+
|
16
|
+
# The hash of options for this dataset, keys are symbols.
|
17
|
+
attr_accessor :opts
|
18
|
+
|
19
|
+
# Constructs a new Dataset instance with an associated database and
|
20
|
+
# options. Datasets are usually constructed by invoking the Database#[] method:
|
21
|
+
#
|
22
|
+
# DB[:posts]
|
23
|
+
#
|
24
|
+
# Sequel::Dataset is an abstract class that is not useful by itself. Each
|
25
|
+
# database adaptor should provide a subclass of Sequel::Dataset, and have
|
26
|
+
# the Database#dataset method return an instance of that class.
|
27
|
+
def initialize(db, opts = nil)
|
28
|
+
@db = db
|
29
|
+
@quote_identifiers = db.quote_identifiers? if db.respond_to?(:quote_identifiers?)
|
30
|
+
@identifier_input_method = db.identifier_input_method if db.respond_to?(:identifier_input_method)
|
31
|
+
@identifier_output_method = db.identifier_output_method if db.respond_to?(:identifier_output_method)
|
32
|
+
@opts = opts || {}
|
33
|
+
@row_proc = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
# Return the dataset as an aliased expression with the given alias. You can
|
37
|
+
# use this as a FROM or JOIN dataset, or as a column if this dataset
|
38
|
+
# returns a single row and column.
|
39
|
+
def as(aliaz)
|
40
|
+
::Sequel::SQL::AliasedExpression.new(self, aliaz)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Yield a dataset for each server in the connection pool that is tied to that server.
|
44
|
+
# Intended for use in sharded environments where all servers need to be modified
|
45
|
+
# with the same data:
|
46
|
+
#
|
47
|
+
# DB[:configs].where(:key=>'setting').each_server{|ds| ds.update(:value=>'new_value')}
|
48
|
+
def each_server
|
49
|
+
db.servers.each{|s| yield server(s)}
|
50
|
+
end
|
51
|
+
|
52
|
+
# The first source (primary table) for this dataset. If the dataset doesn't
|
53
|
+
# have a table, raises an error. If the table is aliased, returns the aliased name.
|
54
|
+
def first_source_alias
|
55
|
+
source = @opts[:from]
|
56
|
+
if source.nil? || source.empty?
|
57
|
+
raise Error, 'No source specified for query'
|
58
|
+
end
|
59
|
+
case s = source.first
|
60
|
+
when SQL::AliasedExpression
|
61
|
+
s.aliaz
|
62
|
+
when Symbol
|
63
|
+
sch, table, aliaz = split_symbol(s)
|
64
|
+
aliaz ? aliaz.to_sym : s
|
65
|
+
else
|
66
|
+
s
|
67
|
+
end
|
68
|
+
end
|
69
|
+
alias first_source first_source_alias
|
70
|
+
|
71
|
+
# The first source (primary table) for this dataset. If the dataset doesn't
|
72
|
+
# have a table, raises an error. If the table is aliased, returns the original
|
73
|
+
# table, not the alias
|
74
|
+
def first_source_table
|
75
|
+
source = @opts[:from]
|
76
|
+
if source.nil? || source.empty?
|
77
|
+
raise Error, 'No source specified for query'
|
78
|
+
end
|
79
|
+
case s = source.first
|
80
|
+
when SQL::AliasedExpression
|
81
|
+
s.expression
|
82
|
+
when Symbol
|
83
|
+
sch, table, aliaz = split_symbol(s)
|
84
|
+
aliaz ? (sch ? SQL::QualifiedIdentifier.new(sch, table) : table.to_sym) : s
|
85
|
+
else
|
86
|
+
s
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns a string representation of the dataset including the class name
|
91
|
+
# and the corresponding SQL select statement.
|
92
|
+
def inspect
|
93
|
+
"#<#{self.class}: #{sql.inspect}>"
|
94
|
+
end
|
95
|
+
|
96
|
+
# Creates a unique table alias that hasn't already been used in the dataset.
|
97
|
+
# table_alias can be any type of object accepted by alias_symbol.
|
98
|
+
# The symbol returned will be the implicit alias in the argument,
|
99
|
+
# possibly appended with "_N" if the implicit alias has already been
|
100
|
+
# used, where N is an integer starting at 0 and increasing until an
|
101
|
+
# unused one is found.
|
102
|
+
def unused_table_alias(table_alias)
|
103
|
+
table_alias = alias_symbol(table_alias)
|
104
|
+
used_aliases = []
|
105
|
+
used_aliases += opts[:from].map{|t| alias_symbol(t)} if opts[:from]
|
106
|
+
used_aliases += opts[:join].map{|j| j.table_alias ? alias_alias_symbol(j.table_alias) : alias_symbol(j.table)} if opts[:join]
|
107
|
+
if used_aliases.include?(table_alias)
|
108
|
+
i = 0
|
109
|
+
loop do
|
110
|
+
ta = :"#{table_alias}_#{i}"
|
111
|
+
return ta unless used_aliases.include?(ta)
|
112
|
+
i += 1
|
113
|
+
end
|
114
|
+
else
|
115
|
+
table_alias
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Sequel
|
2
|
+
class Dataset
|
3
|
+
# ---------------------
|
4
|
+
# :section: Mutation methods
|
5
|
+
# These methods modify the receiving dataset and should be used with care.
|
6
|
+
# ---------------------
|
7
|
+
|
8
|
+
# All methods that should have a ! method added that modifies
|
9
|
+
# the receiver.
|
10
|
+
MUTATION_METHODS = %w'add_graph_aliases and cross_join distinct except exclude
|
11
|
+
filter for_update from from_self full_join full_outer_join graph
|
12
|
+
group group_and_count group_by having inner_join intersect invert join join_table left_join
|
13
|
+
left_outer_join limit lock_style naked natural_full_join natural_join
|
14
|
+
natural_left_join natural_right_join or order order_by order_more paginate qualify query
|
15
|
+
reverse reverse_order right_join right_outer_join select select_all select_append select_more server
|
16
|
+
set_defaults set_graph_aliases set_overrides unfiltered ungraphed ungrouped union
|
17
|
+
unlimited unordered where with with_recursive with_sql'.collect{|x| x.to_sym}
|
18
|
+
|
19
|
+
# Setup mutation (e.g. filter!) methods. These operate the same as the
|
20
|
+
# non-! methods, but replace the options of the current dataset with the
|
21
|
+
# options of the resulting dataset.
|
22
|
+
def self.def_mutation_method(*meths)
|
23
|
+
meths.each do |meth|
|
24
|
+
class_eval("def #{meth}!(*args, &block); mutation_method(:#{meth}, *args, &block) end", __FILE__, __LINE__)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Add the mutation methods via metaprogramming
|
29
|
+
def_mutation_method(*MUTATION_METHODS)
|
30
|
+
|
31
|
+
|
32
|
+
# Set the method to call on identifiers going into the database for this dataset
|
33
|
+
attr_accessor :identifier_input_method
|
34
|
+
|
35
|
+
# Set the method to call on identifiers coming the database for this dataset
|
36
|
+
attr_accessor :identifier_output_method
|
37
|
+
|
38
|
+
# Whether to quote identifiers for this dataset
|
39
|
+
attr_writer :quote_identifiers
|
40
|
+
|
41
|
+
# The row_proc for this database, should be a Proc that takes
|
42
|
+
# a single hash argument and returns the object you want
|
43
|
+
# each to return.
|
44
|
+
attr_accessor :row_proc
|
45
|
+
|
46
|
+
# Add a mutation method to this dataset instance.
|
47
|
+
def def_mutation_method(*meths)
|
48
|
+
meths.each do |meth|
|
49
|
+
instance_eval("def #{meth}!(*args, &block); mutation_method(:#{meth}, *args, &block) end", __FILE__, __LINE__)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# Modify the receiver with the results of sending the meth, args, and block
|
56
|
+
# to the receiver and merging the options of the resulting dataset into
|
57
|
+
# the receiver's options.
|
58
|
+
def mutation_method(meth, *args, &block)
|
59
|
+
copy = send(meth, *args, &block)
|
60
|
+
@opts.merge!(copy.opts)
|
61
|
+
self
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -1,5 +1,11 @@
|
|
1
1
|
module Sequel
|
2
2
|
class Dataset
|
3
|
+
# ---------------------
|
4
|
+
# :section: Methods related to prepared statements or bound variables
|
5
|
+
# On some adapters, these use native prepared statements and bound variables, on others
|
6
|
+
# support is emulated. For details, see the {"Prepared Statements/Bound Variables" guide}[link:files/doc/prepared_statements_rdoc.html].
|
7
|
+
# ---------------------
|
8
|
+
|
3
9
|
PREPARED_ARG_PLACEHOLDER = LiteralString.new('?').freeze
|
4
10
|
|
5
11
|
# Default implementation of the argument mapper to allow
|
data/lib/sequel/dataset/query.rb
CHANGED
@@ -1,5 +1,28 @@
|
|
1
1
|
module Sequel
|
2
2
|
class Dataset
|
3
|
+
# ---------------------
|
4
|
+
# :section: Methods that return modified datasets
|
5
|
+
# These methods all return modified copies of the receiver.
|
6
|
+
# ---------------------
|
7
|
+
# The dataset options that require the removal of cached columns
|
8
|
+
# if changed.
|
9
|
+
COLUMN_CHANGE_OPTS = [:select, :sql, :from, :join].freeze
|
10
|
+
|
11
|
+
# Which options don't affect the SQL generation. Used by simple_select_all?
|
12
|
+
# to determine if this is a simple SELECT * FROM table.
|
13
|
+
NON_SQL_OPTIONS = [:server, :defaults, :overrides, :graph, :eager_graph, :graph_aliases]
|
14
|
+
|
15
|
+
# These symbols have _join methods created (e.g. inner_join) that
|
16
|
+
# call join_table with the symbol, passing along the arguments and
|
17
|
+
# block from the method call.
|
18
|
+
CONDITIONED_JOIN_TYPES = [:inner, :full_outer, :right_outer, :left_outer, :full, :right, :left]
|
19
|
+
|
20
|
+
# These symbols have _join methods created (e.g. natural_join) that
|
21
|
+
# call join_table with the symbol. They only accept a single table
|
22
|
+
# argument which is passed to join_table, and they raise an error
|
23
|
+
# if called with a block.
|
24
|
+
UNCONDITIONED_JOIN_TYPES = [:natural, :natural_left, :natural_right, :natural_full, :cross]
|
25
|
+
|
3
26
|
# Adds an further filter to an existing filter using AND. If no filter
|
4
27
|
# exists an error is raised. This method is identical to #filter except
|
5
28
|
# it expects an existing filter.
|
@@ -9,6 +32,16 @@ module Sequel
|
|
9
32
|
raise(InvalidOperation, "No existing filter found.") unless @opts[:having] || @opts[:where]
|
10
33
|
filter(*cond, &block)
|
11
34
|
end
|
35
|
+
|
36
|
+
# Returns a new clone of the dataset with with the given options merged.
|
37
|
+
# If the options changed include options in COLUMN_CHANGE_OPTS, the cached
|
38
|
+
# columns are deleted.
|
39
|
+
def clone(opts = {})
|
40
|
+
c = super()
|
41
|
+
c.opts = @opts.merge(opts)
|
42
|
+
c.instance_variable_set(:@columns, nil) if opts.keys.any?{|o| COLUMN_CHANGE_OPTS.include?(o)}
|
43
|
+
c
|
44
|
+
end
|
12
45
|
|
13
46
|
# Returns a copy of the dataset with the SQL DISTINCT clause.
|
14
47
|
# The DISTINCT clause is used to remove duplicate rows from the
|
@@ -74,7 +107,8 @@ module Sequel
|
|
74
107
|
#
|
75
108
|
# filter also takes a block, which should return one of the above argument
|
76
109
|
# types, and is treated the same way. This block yields a virtual row object,
|
77
|
-
# which is easy to use to create identifiers and functions.
|
110
|
+
# which is easy to use to create identifiers and functions. For more details
|
111
|
+
# on the virtual row support, see the {"Virtual Rows" guide}[link:files/doc/virtual_rows_rdoc.html]
|
78
112
|
#
|
79
113
|
# If both a block and regular argument
|
80
114
|
# are provided, they get ANDed together.
|
@@ -100,7 +134,7 @@ module Sequel
|
|
100
134
|
# software.filter{|o| o.price < 100}.sql #=>
|
101
135
|
# "SELECT * FROM items WHERE ((category = 'software') AND (price < 100))"
|
102
136
|
#
|
103
|
-
# See doc/
|
137
|
+
# See the the {"Dataset Filtering" guide}[link:files/doc/dataset_filtering_rdoc.html] for more examples and details.
|
104
138
|
def filter(*cond, &block)
|
105
139
|
_filter(@opts[:having] ? :having : :where, *cond, &block)
|
106
140
|
end
|
@@ -175,6 +209,19 @@ module Sequel
|
|
175
209
|
clone(:group => (columns.compact.empty? ? nil : columns))
|
176
210
|
end
|
177
211
|
alias group_by group
|
212
|
+
|
213
|
+
# Returns a dataset grouped by the given column with count by group,
|
214
|
+
# order by the count of records. Column aliases may be supplied, and will
|
215
|
+
# be included in the select clause.
|
216
|
+
#
|
217
|
+
# Examples:
|
218
|
+
#
|
219
|
+
# ds.group_and_count(:name).all => [{:name=>'a', :count=>1}, ...]
|
220
|
+
# ds.group_and_count(:first_name, :last_name).all => [{:first_name=>'a', :last_name=>'b', :count=>1}, ...]
|
221
|
+
# ds.group_and_count(:first_name___name).all => [{:name=>'a', :count=>1}, ...]
|
222
|
+
def group_and_count(*columns)
|
223
|
+
group(*columns.map{|c| unaliased_identifier(c)}).select(*(columns + [COUNT_OF_ALL_AS_COUNT]))
|
224
|
+
end
|
178
225
|
|
179
226
|
# Returns a copy of the dataset with the HAVING conditions changed. See #filter for argument types.
|
180
227
|
#
|
@@ -213,6 +260,100 @@ module Sequel
|
|
213
260
|
clone(o)
|
214
261
|
end
|
215
262
|
|
263
|
+
# Returns a joined dataset. Uses the following arguments:
|
264
|
+
#
|
265
|
+
# * type - The type of join to do (e.g. :inner)
|
266
|
+
# * table - Depends on type:
|
267
|
+
# * Dataset - a subselect is performed with an alias of tN for some value of N
|
268
|
+
# * Model (or anything responding to :table_name) - table.table_name
|
269
|
+
# * String, Symbol: table
|
270
|
+
# * expr - specifies conditions, depends on type:
|
271
|
+
# * Hash, Array with all two pairs - Assumes key (1st arg) is column of joined table (unless already
|
272
|
+
# qualified), and value (2nd arg) is column of the last joined or primary table (or the
|
273
|
+
# :implicit_qualifier option).
|
274
|
+
# To specify multiple conditions on a single joined table column, you must use an array.
|
275
|
+
# Uses a JOIN with an ON clause.
|
276
|
+
# * Array - If all members of the array are symbols, considers them as columns and
|
277
|
+
# uses a JOIN with a USING clause. Most databases will remove duplicate columns from
|
278
|
+
# the result set if this is used.
|
279
|
+
# * nil - If a block is not given, doesn't use ON or USING, so the JOIN should be a NATURAL
|
280
|
+
# or CROSS join. If a block is given, uses a ON clause based on the block, see below.
|
281
|
+
# * Everything else - pretty much the same as a using the argument in a call to filter,
|
282
|
+
# so strings are considered literal, symbols specify boolean columns, and blockless
|
283
|
+
# filter expressions can be used. Uses a JOIN with an ON clause.
|
284
|
+
# * options - a hash of options, with any of the following keys:
|
285
|
+
# * :table_alias - the name of the table's alias when joining, necessary for joining
|
286
|
+
# to the same table more than once. No alias is used by default.
|
287
|
+
# * :implicit_qualifier - The name to use for qualifying implicit conditions. By default,
|
288
|
+
# the last joined or primary table is used.
|
289
|
+
# * block - The block argument should only be given if a JOIN with an ON clause is used,
|
290
|
+
# in which case it yields the table alias/name for the table currently being joined,
|
291
|
+
# the table alias/name for the last joined (or first table), and an array of previous
|
292
|
+
# SQL::JoinClause.
|
293
|
+
def join_table(type, table, expr=nil, options={}, &block)
|
294
|
+
using_join = expr.is_a?(Array) && !expr.empty? && expr.all?{|x| x.is_a?(Symbol)}
|
295
|
+
if using_join && !supports_join_using?
|
296
|
+
h = {}
|
297
|
+
expr.each{|s| h[s] = s}
|
298
|
+
return join_table(type, table, h, options)
|
299
|
+
end
|
300
|
+
|
301
|
+
case options
|
302
|
+
when Hash
|
303
|
+
table_alias = options[:table_alias]
|
304
|
+
last_alias = options[:implicit_qualifier]
|
305
|
+
when Symbol, String, SQL::Identifier
|
306
|
+
table_alias = options
|
307
|
+
last_alias = nil
|
308
|
+
else
|
309
|
+
raise Error, "invalid options format for join_table: #{options.inspect}"
|
310
|
+
end
|
311
|
+
|
312
|
+
if Dataset === table
|
313
|
+
if table_alias.nil?
|
314
|
+
table_alias_num = (@opts[:num_dataset_sources] || 0) + 1
|
315
|
+
table_alias = dataset_alias(table_alias_num)
|
316
|
+
end
|
317
|
+
table_name = table_alias
|
318
|
+
else
|
319
|
+
table = table.table_name if table.respond_to?(:table_name)
|
320
|
+
table_name = table_alias || table
|
321
|
+
end
|
322
|
+
|
323
|
+
join = if expr.nil? and !block_given?
|
324
|
+
SQL::JoinClause.new(type, table, table_alias)
|
325
|
+
elsif using_join
|
326
|
+
raise(Sequel::Error, "can't use a block if providing an array of symbols as expr") if block_given?
|
327
|
+
SQL::JoinUsingClause.new(expr, type, table, table_alias)
|
328
|
+
else
|
329
|
+
last_alias ||= @opts[:last_joined_table] || first_source_alias
|
330
|
+
if Sequel.condition_specifier?(expr)
|
331
|
+
expr = expr.collect do |k, v|
|
332
|
+
k = qualified_column_name(k, table_name) if k.is_a?(Symbol)
|
333
|
+
v = qualified_column_name(v, last_alias) if v.is_a?(Symbol)
|
334
|
+
[k,v]
|
335
|
+
end
|
336
|
+
end
|
337
|
+
if block_given?
|
338
|
+
expr2 = yield(table_name, last_alias, @opts[:join] || [])
|
339
|
+
expr = expr ? SQL::BooleanExpression.new(:AND, expr, expr2) : expr2
|
340
|
+
end
|
341
|
+
SQL::JoinOnClause.new(expr, type, table, table_alias)
|
342
|
+
end
|
343
|
+
|
344
|
+
opts = {:join => (@opts[:join] || []) + [join], :last_joined_table => table_name}
|
345
|
+
opts[:num_dataset_sources] = table_alias_num if table_alias_num
|
346
|
+
clone(opts)
|
347
|
+
end
|
348
|
+
|
349
|
+
CONDITIONED_JOIN_TYPES.each do |jtype|
|
350
|
+
class_eval("def #{jtype}_join(*args, &block); join_table(:#{jtype}, *args, &block) end", __FILE__, __LINE__)
|
351
|
+
end
|
352
|
+
UNCONDITIONED_JOIN_TYPES.each do |jtype|
|
353
|
+
class_eval("def #{jtype}_join(table); raise(Sequel::Error, '#{jtype}_join does not accept join table blocks') if block_given?; join_table(:#{jtype}, table) end", __FILE__, __LINE__)
|
354
|
+
end
|
355
|
+
alias join inner_join
|
356
|
+
|
216
357
|
# If given an integer, the dataset will contain only the first l results.
|
217
358
|
# If given a range, it will contain only those at offsets within that
|
218
359
|
# range. If a second argument is given, it is used as an offset.
|
@@ -249,6 +390,14 @@ module Sequel
|
|
249
390
|
clone(:lock => style)
|
250
391
|
end
|
251
392
|
|
393
|
+
# Returns a naked dataset clone - i.e. a dataset that returns records as
|
394
|
+
# hashes instead of calling the row proc.
|
395
|
+
def naked
|
396
|
+
ds = clone
|
397
|
+
ds.row_proc = nil
|
398
|
+
ds
|
399
|
+
end
|
400
|
+
|
252
401
|
# Adds an alternate filter to an existing filter using OR. If no filter
|
253
402
|
# exists an error is raised.
|
254
403
|
#
|
@@ -289,6 +438,35 @@ module Sequel
|
|
289
438
|
order(*columns, &block)
|
290
439
|
end
|
291
440
|
|
441
|
+
# Qualify to the given table, or first source if not table is given.
|
442
|
+
def qualify(table=first_source)
|
443
|
+
qualify_to(table)
|
444
|
+
end
|
445
|
+
|
446
|
+
# Return a copy of the dataset with unqualified identifiers in the
|
447
|
+
# SELECT, WHERE, GROUP, HAVING, and ORDER clauses qualified by the
|
448
|
+
# given table. If no columns are currently selected, select all
|
449
|
+
# columns of the given table.
|
450
|
+
def qualify_to(table)
|
451
|
+
o = @opts
|
452
|
+
return clone if o[:sql]
|
453
|
+
h = {}
|
454
|
+
(o.keys & QUALIFY_KEYS).each do |k|
|
455
|
+
h[k] = qualified_expression(o[k], table)
|
456
|
+
end
|
457
|
+
h[:select] = [SQL::ColumnAll.new(table)] if !o[:select] || o[:select].empty?
|
458
|
+
clone(h)
|
459
|
+
end
|
460
|
+
|
461
|
+
# Qualify the dataset to its current first source. This is useful
|
462
|
+
# if you have unqualified identifiers in the query that all refer to
|
463
|
+
# the first source, and you want to join to another table which
|
464
|
+
# has columns with the same name as columns in the current dataset.
|
465
|
+
# See qualify_to.
|
466
|
+
def qualify_to_first_source
|
467
|
+
qualify_to(first_source)
|
468
|
+
end
|
469
|
+
|
292
470
|
# Returns a copy of the dataset with the order reversed. If no order is
|
293
471
|
# given, the existing order is inverted.
|
294
472
|
def reverse_order(*order)
|
@@ -318,17 +496,51 @@ module Sequel
|
|
318
496
|
def select_all
|
319
497
|
clone(:select => nil)
|
320
498
|
end
|
499
|
+
|
500
|
+
# Returns a copy of the dataset with the given columns added
|
501
|
+
# to the existing selected columns. If no columns are currently selected
|
502
|
+
# it will select the columns given in addition to *.
|
503
|
+
#
|
504
|
+
# dataset.select(:a).select(:b) # SELECT b FROM items
|
505
|
+
# dataset.select(:a).select_append(:b) # SELECT a, b FROM items
|
506
|
+
# dataset.select_append(:b) # SELECT *, b FROM items
|
507
|
+
def select_append(*columns, &block)
|
508
|
+
cur_sel = @opts[:select]
|
509
|
+
cur_sel = [WILDCARD] if !cur_sel || cur_sel.empty?
|
510
|
+
select(*(cur_sel + columns), &block)
|
511
|
+
end
|
321
512
|
|
322
513
|
# Returns a copy of the dataset with the given columns added
|
323
|
-
# to the existing selected columns.
|
514
|
+
# to the existing selected columns. If no columns are currently selected
|
515
|
+
# it will just select the columns given.
|
324
516
|
#
|
325
517
|
# dataset.select(:a).select(:b) # SELECT b FROM items
|
326
518
|
# dataset.select(:a).select_more(:b) # SELECT a, b FROM items
|
519
|
+
# dataset.select_more(:b) # SELECT b FROM items
|
327
520
|
def select_more(*columns, &block)
|
328
521
|
columns = @opts[:select] + columns if @opts[:select]
|
329
522
|
select(*columns, &block)
|
330
523
|
end
|
331
524
|
|
525
|
+
# Set the server for this dataset to use. Used to pick a specific database
|
526
|
+
# shard to run a query against, or to override the default (which is SELECT uses
|
527
|
+
# :read_only database and all other queries use the :default database).
|
528
|
+
def server(servr)
|
529
|
+
clone(:server=>servr)
|
530
|
+
end
|
531
|
+
|
532
|
+
# Set the default values for insert and update statements. The values hash passed
|
533
|
+
# to insert or update are merged into this hash.
|
534
|
+
def set_defaults(hash)
|
535
|
+
clone(:defaults=>(@opts[:defaults]||{}).merge(hash))
|
536
|
+
end
|
537
|
+
|
538
|
+
# Set values that override hash arguments given to insert and update statements.
|
539
|
+
# This hash is merged into the hash provided to insert or update.
|
540
|
+
def set_overrides(hash)
|
541
|
+
clone(:overrides=>hash.merge(@opts[:overrides]||{}))
|
542
|
+
end
|
543
|
+
|
332
544
|
# Returns a copy of the dataset with no filters (HAVING or WHERE clause) applied.
|
333
545
|
#
|
334
546
|
# dataset.group(:a).having(:a=>1).where(:b).unfiltered # SELECT * FROM items GROUP BY a
|
@@ -370,15 +582,69 @@ module Sequel
|
|
370
582
|
def unordered
|
371
583
|
order(nil)
|
372
584
|
end
|
585
|
+
|
586
|
+
# Add a condition to the WHERE clause. See #filter for argument types.
|
587
|
+
#
|
588
|
+
# dataset.group(:a).having(:a).filter(:b) # SELECT * FROM items GROUP BY a HAVING a AND b
|
589
|
+
# dataset.group(:a).having(:a).where(:b) # SELECT * FROM items WHERE b GROUP BY a HAVING a
|
590
|
+
def where(*cond, &block)
|
591
|
+
_filter(:where, *cond, &block)
|
592
|
+
end
|
593
|
+
|
594
|
+
# Add a simple common table expression (CTE) with the given name and a dataset that defines the CTE.
|
595
|
+
# A common table expression acts as an inline view for the query.
|
596
|
+
# Options:
|
597
|
+
# * :args - Specify the arguments/columns for the CTE, should be an array of symbols.
|
598
|
+
# * :recursive - Specify that this is a recursive CTE
|
599
|
+
def with(name, dataset, opts={})
|
600
|
+
raise(Error, 'This datatset does not support common table expressions') unless supports_cte?
|
601
|
+
clone(:with=>(@opts[:with]||[]) + [opts.merge(:name=>name, :dataset=>dataset)])
|
602
|
+
end
|
603
|
+
|
604
|
+
# Add a recursive common table expression (CTE) with the given name, a dataset that
|
605
|
+
# defines the nonrecursive part of the CTE, and a dataset that defines the recursive part
|
606
|
+
# of the CTE. Options:
|
607
|
+
# * :args - Specify the arguments/columns for the CTE, should be an array of symbols.
|
608
|
+
# * :union_all - Set to false to use UNION instead of UNION ALL combining the nonrecursive and recursive parts.
|
609
|
+
def with_recursive(name, nonrecursive, recursive, opts={})
|
610
|
+
raise(Error, 'This datatset does not support common table expressions') unless supports_cte?
|
611
|
+
clone(:with=>(@opts[:with]||[]) + [opts.merge(:recursive=>true, :name=>name, :dataset=>nonrecursive.union(recursive, {:all=>opts[:union_all] != false, :from_self=>false}))])
|
612
|
+
end
|
613
|
+
|
614
|
+
# Returns a copy of the dataset with the static SQL used. This is useful if you want
|
615
|
+
# to keep the same row_proc/graph, but change the SQL used to custom SQL.
|
616
|
+
#
|
617
|
+
# dataset.with_sql('SELECT * FROM foo') # SELECT * FROM foo
|
618
|
+
def with_sql(sql, *args)
|
619
|
+
sql = SQL::PlaceholderLiteralString.new(sql, args) unless args.empty?
|
620
|
+
clone(:sql=>sql)
|
621
|
+
end
|
622
|
+
|
623
|
+
protected
|
624
|
+
|
625
|
+
# Return true if the dataset has a non-nil value for any key in opts.
|
626
|
+
def options_overlap(opts)
|
627
|
+
!(@opts.collect{|k,v| k unless v.nil?}.compact & opts).empty?
|
628
|
+
end
|
629
|
+
|
630
|
+
# Whether this dataset is a simple SELECT * FROM table.
|
631
|
+
def simple_select_all?
|
632
|
+
o = @opts.reject{|k,v| v.nil? || NON_SQL_OPTIONS.include?(k)}
|
633
|
+
o.length == 1 && (f = o[:from]) && f.length == 1 && f.first.is_a?(Symbol)
|
634
|
+
end
|
373
635
|
|
374
636
|
private
|
375
637
|
|
376
638
|
# Internal filter method so it works on either the having or where clauses.
|
377
639
|
def _filter(clause, *cond, &block)
|
378
640
|
cond = cond.first if cond.size == 1
|
379
|
-
cond
|
380
|
-
|
381
|
-
|
641
|
+
if cond.respond_to?(:empty?) && cond.empty? && !block
|
642
|
+
clone
|
643
|
+
else
|
644
|
+
cond = filter_expr(cond, &block)
|
645
|
+
cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
|
646
|
+
clone(clause => cond)
|
647
|
+
end
|
382
648
|
end
|
383
649
|
|
384
650
|
# Add the dataset to the list of compounds
|