sequel 3.10.0 → 3.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|