sequel 4.10.0 → 4.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.
- checksums.yaml +4 -4
- data/CHANGELOG +58 -0
- data/doc/association_basics.rdoc +1 -1
- data/doc/cheat_sheet.rdoc +0 -1
- data/doc/core_extensions.rdoc +2 -2
- data/doc/dataset_filtering.rdoc +5 -5
- data/doc/model_hooks.rdoc +9 -0
- data/doc/object_model.rdoc +7 -13
- data/doc/opening_databases.rdoc +3 -1
- data/doc/querying.rdoc +8 -8
- data/doc/release_notes/4.11.0.txt +147 -0
- data/doc/sql.rdoc +11 -7
- data/doc/virtual_rows.rdoc +4 -5
- data/lib/sequel/adapters/ibmdb.rb +24 -16
- data/lib/sequel/adapters/jdbc/h2.rb +5 -0
- data/lib/sequel/adapters/jdbc/hsqldb.rb +5 -0
- data/lib/sequel/adapters/mock.rb +14 -2
- data/lib/sequel/adapters/shared/access.rb +6 -9
- data/lib/sequel/adapters/shared/cubrid.rb +5 -0
- data/lib/sequel/adapters/shared/db2.rb +5 -0
- data/lib/sequel/adapters/shared/firebird.rb +5 -0
- data/lib/sequel/adapters/shared/mssql.rb +23 -16
- data/lib/sequel/adapters/shared/mysql.rb +12 -2
- data/lib/sequel/adapters/shared/oracle.rb +31 -15
- data/lib/sequel/adapters/shared/postgres.rb +28 -4
- data/lib/sequel/adapters/shared/sqlanywhere.rb +5 -0
- data/lib/sequel/adapters/shared/sqlite.rb +12 -1
- data/lib/sequel/ast_transformer.rb +9 -7
- data/lib/sequel/connection_pool.rb +10 -4
- data/lib/sequel/database/features.rb +15 -0
- data/lib/sequel/database/schema_generator.rb +2 -2
- data/lib/sequel/database/schema_methods.rb +21 -3
- data/lib/sequel/database/transactions.rb +8 -4
- data/lib/sequel/dataset/actions.rb +13 -7
- data/lib/sequel/dataset/features.rb +7 -0
- data/lib/sequel/dataset/query.rb +28 -11
- data/lib/sequel/dataset/sql.rb +90 -14
- data/lib/sequel/extensions/constraint_validations.rb +2 -2
- data/lib/sequel/extensions/date_arithmetic.rb +1 -1
- data/lib/sequel/extensions/eval_inspect.rb +12 -6
- data/lib/sequel/extensions/pg_array_ops.rb +11 -2
- data/lib/sequel/extensions/pg_json.rb +130 -23
- data/lib/sequel/extensions/pg_json_ops.rb +196 -28
- data/lib/sequel/extensions/to_dot.rb +5 -7
- data/lib/sequel/model/associations.rb +0 -50
- data/lib/sequel/plugins/class_table_inheritance.rb +49 -21
- data/lib/sequel/plugins/many_through_many.rb +10 -11
- data/lib/sequel/plugins/serialization.rb +4 -1
- data/lib/sequel/plugins/sharding.rb +0 -9
- data/lib/sequel/plugins/single_table_inheritance.rb +4 -2
- data/lib/sequel/plugins/timestamps.rb +2 -2
- data/lib/sequel/sql.rb +166 -44
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +199 -133
- data/spec/core/connection_pool_spec.rb +6 -0
- data/spec/core/database_spec.rb +12 -0
- data/spec/core/dataset_spec.rb +58 -3
- data/spec/core/expression_filters_spec.rb +67 -5
- data/spec/core/mock_adapter_spec.rb +8 -4
- data/spec/core/schema_spec.rb +7 -0
- data/spec/core_extensions_spec.rb +14 -0
- data/spec/extensions/class_table_inheritance_spec.rb +23 -3
- data/spec/extensions/core_refinements_spec.rb +14 -0
- data/spec/extensions/eval_inspect_spec.rb +8 -4
- data/spec/extensions/pg_array_ops_spec.rb +6 -0
- data/spec/extensions/pg_json_ops_spec.rb +99 -0
- data/spec/extensions/pg_json_spec.rb +104 -4
- data/spec/extensions/serialization_spec.rb +19 -0
- data/spec/extensions/single_table_inheritance_spec.rb +11 -3
- data/spec/extensions/timestamps_spec.rb +10 -0
- data/spec/extensions/to_dot_spec.rb +8 -4
- data/spec/integration/database_test.rb +1 -1
- data/spec/integration/dataset_test.rb +9 -0
- data/spec/integration/schema_test.rb +27 -0
- metadata +4 -2
@@ -613,6 +613,11 @@ module Sequel
|
|
613
613
|
db.sqlite_version >= 30803
|
614
614
|
end
|
615
615
|
|
616
|
+
# SQLite does not support table aliases with column aliases
|
617
|
+
def supports_derived_column_lists?
|
618
|
+
false
|
619
|
+
end
|
620
|
+
|
616
621
|
# SQLite does not support INTERSECT ALL or EXCEPT ALL
|
617
622
|
def supports_intersect_except_all?
|
618
623
|
false
|
@@ -643,7 +648,8 @@ module Sequel
|
|
643
648
|
private
|
644
649
|
|
645
650
|
# SQLite uses string literals instead of identifiers in AS clauses.
|
646
|
-
def as_sql_append(sql, aliaz)
|
651
|
+
def as_sql_append(sql, aliaz, column_aliases=nil)
|
652
|
+
raise Error, "sqlite does not support derived column lists" if column_aliases
|
647
653
|
aliaz = aliaz.value if aliaz.is_a?(SQL::Identifier)
|
648
654
|
sql << AS
|
649
655
|
literal_append(sql, aliaz.to_s)
|
@@ -666,6 +672,11 @@ module Sequel
|
|
666
672
|
end
|
667
673
|
end
|
668
674
|
|
675
|
+
# SQLite supports a maximum of 500 rows in a VALUES clause.
|
676
|
+
def default_import_slice
|
677
|
+
500
|
678
|
+
end
|
679
|
+
|
669
680
|
# SQL fragment specifying a list of identifiers
|
670
681
|
def identifier_list(columns)
|
671
682
|
columns.map{|i| quote_identifier(i)}.join(COMMA)
|
@@ -33,7 +33,7 @@ module Sequel
|
|
33
33
|
when SQL::OrderedExpression
|
34
34
|
SQL::OrderedExpression.new(v(o.expression), o.descending, :nulls=>o.nulls)
|
35
35
|
when SQL::AliasedExpression
|
36
|
-
SQL::AliasedExpression.new(v(o.expression), o.alias)
|
36
|
+
SQL::AliasedExpression.new(v(o.expression), o.alias, o.columns)
|
37
37
|
when SQL::CaseExpression
|
38
38
|
args = [v(o.conditions), v(o.default)]
|
39
39
|
args << v(o.expression) if o.expression?
|
@@ -41,11 +41,13 @@ module Sequel
|
|
41
41
|
when SQL::Cast
|
42
42
|
SQL::Cast.new(v(o.expr), o.type)
|
43
43
|
when SQL::Function
|
44
|
-
|
44
|
+
h = {}
|
45
|
+
o.opts.each do |k, val|
|
46
|
+
h[k] = v(val)
|
47
|
+
end
|
48
|
+
SQL::Function.new!(o.name, v(o.args), h)
|
45
49
|
when SQL::Subscript
|
46
50
|
SQL::Subscript.new(v(o.f), v(o.sub))
|
47
|
-
when SQL::WindowFunction
|
48
|
-
SQL::WindowFunction.new(v(o.function), v(o.window))
|
49
51
|
when SQL::Window
|
50
52
|
opts = o.opts.dup
|
51
53
|
opts[:partition] = v(opts[:partition]) if opts[:partition]
|
@@ -61,11 +63,11 @@ module Sequel
|
|
61
63
|
end
|
62
64
|
SQL::PlaceholderLiteralString.new(o.str, args, o.parens)
|
63
65
|
when SQL::JoinOnClause
|
64
|
-
SQL::JoinOnClause.new(v(o.on), o.join_type, v(o.
|
66
|
+
SQL::JoinOnClause.new(v(o.on), o.join_type, v(o.table_expr))
|
65
67
|
when SQL::JoinUsingClause
|
66
|
-
SQL::JoinUsingClause.new(v(o.using), o.join_type, v(o.
|
68
|
+
SQL::JoinUsingClause.new(v(o.using), o.join_type, v(o.table_expr))
|
67
69
|
when SQL::JoinClause
|
68
|
-
SQL::JoinClause.new(o.join_type, v(o.
|
70
|
+
SQL::JoinClause.new(o.join_type, v(o.table_expr))
|
69
71
|
when SQL::DelayedEvaluation
|
70
72
|
SQL::DelayedEvaluation.new(lambda{v(o.callable.call)})
|
71
73
|
when SQL::Wrapper
|
@@ -69,9 +69,9 @@ class Sequel::ConnectionPool
|
|
69
69
|
# with a single symbol (specifying the server/shard to use) every time a new
|
70
70
|
# connection is needed. The following options are respected for all connection
|
71
71
|
# pools:
|
72
|
-
# :after_connect ::
|
73
|
-
# connection object
|
74
|
-
# connections.
|
72
|
+
# :after_connect :: A callable object called after each new connection is made, with the
|
73
|
+
# connection object (and server argument if the callable accepts 2 arguments),
|
74
|
+
# useful for customizations that you want to apply to all connections.
|
75
75
|
def initialize(db, opts=OPTS)
|
76
76
|
@db = db
|
77
77
|
@after_connect = opts[:after_connect]
|
@@ -94,7 +94,13 @@ class Sequel::ConnectionPool
|
|
94
94
|
def make_new(server)
|
95
95
|
begin
|
96
96
|
conn = @db.connect(server)
|
97
|
-
|
97
|
+
if ac = @after_connect
|
98
|
+
if ac.arity == 2
|
99
|
+
ac.call(conn, server)
|
100
|
+
else
|
101
|
+
ac.call(conn)
|
102
|
+
end
|
103
|
+
end
|
98
104
|
rescue Exception=>exception
|
99
105
|
raise Sequel.convert_exception_class(exception, Sequel::DatabaseConnectionError)
|
100
106
|
end
|
@@ -94,6 +94,16 @@ module Sequel
|
|
94
94
|
false
|
95
95
|
end
|
96
96
|
|
97
|
+
# Whether CREATE VIEW ... WITH CHECK OPTION is supported, false by default.
|
98
|
+
def supports_views_with_check_option?
|
99
|
+
!!view_with_check_option_support
|
100
|
+
end
|
101
|
+
|
102
|
+
# Whether CREATE VIEW ... WITH LOCAL CHECK OPTION is supported, false by default.
|
103
|
+
def supports_views_with_local_check_option?
|
104
|
+
view_with_check_option_support == :local
|
105
|
+
end
|
106
|
+
|
97
107
|
private
|
98
108
|
|
99
109
|
# Whether the database supports combining multiple alter table
|
@@ -115,5 +125,10 @@ module Sequel
|
|
115
125
|
def supports_named_column_constraints?
|
116
126
|
true
|
117
127
|
end
|
128
|
+
|
129
|
+
# Don't advertise support for WITH CHECK OPTION by default.
|
130
|
+
def view_with_check_option_support
|
131
|
+
nil
|
132
|
+
end
|
118
133
|
end
|
119
134
|
end
|
@@ -322,9 +322,9 @@ module Sequel
|
|
322
322
|
# See CreateTableGenerator#constraint.
|
323
323
|
#
|
324
324
|
# add_constraint(:valid_name, Sequel.like(:name, 'A%'))
|
325
|
-
# # ADD CONSTRAINT valid_name CHECK (name LIKE 'A%')
|
325
|
+
# # ADD CONSTRAINT valid_name CHECK (name LIKE 'A%' ESCAPE '\')
|
326
326
|
# add_constraint({:name=>:valid_name, :deferrable=>true}, :num=>1..5)
|
327
|
-
# # CONSTRAINT valid_name CHECK (name LIKE 'A%') DEFERRABLE INITIALLY DEFERRED
|
327
|
+
# # ADD CONSTRAINT valid_name CHECK (name LIKE 'A%' ESCAPE '\') DEFERRABLE INITIALLY DEFERRED
|
328
328
|
def add_constraint(name, *args, &block)
|
329
329
|
opts = name.is_a?(Hash) ? name : {:name=>name}
|
330
330
|
@operations << opts.merge(:op=>:add_constraint, :type=>:check, :check=>block || args)
|
@@ -241,16 +241,30 @@ module Sequel
|
|
241
241
|
# Creates a view based on a dataset or an SQL string:
|
242
242
|
#
|
243
243
|
# DB.create_view(:cheap_items, "SELECT * FROM items WHERE price < 100")
|
244
|
-
#
|
244
|
+
# # CREATE VIEW cheap_items AS
|
245
|
+
# # SELECT * FROM items WHERE price < 100
|
246
|
+
#
|
247
|
+
# DB.create_view(:ruby_items, DB[:items].where(:category => 'ruby'))
|
248
|
+
# # CREATE VIEW ruby_items AS
|
249
|
+
# # SELECT * FROM items WHERE (category = 'ruby')
|
250
|
+
#
|
251
|
+
# DB.create_view(:checked_items, DB[:items].where(:foo), :check=>true)
|
252
|
+
# # CREATE VIEW checked_items AS
|
253
|
+
# # SELECT * FROM items WHERE foo
|
254
|
+
# # WITH CHECK OPTION
|
245
255
|
#
|
246
256
|
# Options:
|
247
257
|
# :columns :: The column names to use for the view. If not given,
|
248
258
|
# automatically determined based on the input dataset.
|
259
|
+
# :check :: Adds a WITH CHECK OPTION clause, so that attempting to modify
|
260
|
+
# rows in the underlying table that would not be returned by the
|
261
|
+
# view is not allowed. This can be set to :local to use WITH
|
262
|
+
# LOCAL CHECK OPTION.
|
249
263
|
#
|
250
264
|
# PostgreSQL/SQLite specific option:
|
251
265
|
# :temp :: Create a temporary view, automatically dropped on disconnect.
|
252
266
|
#
|
253
|
-
# PostgreSQL specific
|
267
|
+
# PostgreSQL specific options:
|
254
268
|
# :materialized :: Creates a materialized view, similar to a regular view,
|
255
269
|
# but backed by a physical table.
|
256
270
|
# :recursive :: Creates a recursive view. As columns must be specified for
|
@@ -683,7 +697,11 @@ module Sequel
|
|
683
697
|
# DDL statement for creating a view.
|
684
698
|
def create_view_sql(name, source, options)
|
685
699
|
source = source.sql if source.is_a?(Dataset)
|
686
|
-
"#{create_view_prefix_sql(name, options)} AS #{source}"
|
700
|
+
sql = "#{create_view_prefix_sql(name, options)} AS #{source}"
|
701
|
+
if check = options[:check]
|
702
|
+
sql << " WITH#{' LOCAL' if check == :local} CHECK OPTION"
|
703
|
+
end
|
704
|
+
sql
|
687
705
|
end
|
688
706
|
|
689
707
|
# Append the column list to the SQL, if a column list is given.
|
@@ -198,7 +198,12 @@ module Sequel
|
|
198
198
|
def already_in_transaction?(conn, opts)
|
199
199
|
_trans(conn) && (!supports_savepoints? || !opts[:savepoint])
|
200
200
|
end
|
201
|
-
|
201
|
+
|
202
|
+
# Issue query to begin a new savepoint.
|
203
|
+
def begin_savepoint(conn, opts)
|
204
|
+
log_connection_execute(conn, begin_savepoint_sql(savepoint_level(conn)-1))
|
205
|
+
end
|
206
|
+
|
202
207
|
# SQL to start a new savepoint
|
203
208
|
def begin_savepoint_sql(depth)
|
204
209
|
SQL_SAVEPOINT % depth
|
@@ -213,9 +218,8 @@ module Sequel
|
|
213
218
|
# Start a new database transaction or a new savepoint on the given connection.
|
214
219
|
def begin_transaction(conn, opts=OPTS)
|
215
220
|
if supports_savepoints?
|
216
|
-
|
217
|
-
|
218
|
-
log_connection_execute(conn, begin_savepoint_sql(depth-1))
|
221
|
+
if savepoint_level(conn) > 1
|
222
|
+
begin_savepoint(conn, opts)
|
219
223
|
else
|
220
224
|
begin_new_transaction(conn, opts)
|
221
225
|
end
|
@@ -279,7 +279,7 @@ module Sequel
|
|
279
279
|
raise(Error, IMPORT_ERROR_MSG) if columns.empty?
|
280
280
|
ds = opts[:server] ? server(opts[:server]) : self
|
281
281
|
|
282
|
-
if slice_size = opts
|
282
|
+
if slice_size = opts.fetch(:commit_every, opts.fetch(:slice, default_import_slice))
|
283
283
|
offset = 0
|
284
284
|
rows = []
|
285
285
|
while offset < values.length
|
@@ -572,13 +572,13 @@ module Sequel
|
|
572
572
|
# Returns a hash with key_column values as keys and an array of value_column values.
|
573
573
|
# Similar to to_hash_groups, but only selects the columns given.
|
574
574
|
#
|
575
|
-
# DB[:table].
|
575
|
+
# DB[:table].select_hash_groups(:name, :id) # SELECT id, name FROM table
|
576
576
|
# # => {'a'=>[1, 4, ...], 'b'=>[2, ...], ...}
|
577
577
|
#
|
578
578
|
# You can also provide an array of column names for either the key_column,
|
579
579
|
# the value column, or both:
|
580
580
|
#
|
581
|
-
# DB[:table].
|
581
|
+
# DB[:table].select_hash_groups([:first, :middle], [:last, :id]) # SELECT * FROM table
|
582
582
|
# # {['a', 'b']=>[['c', 1], ['d', 2], ...], ...}
|
583
583
|
#
|
584
584
|
# When using this method, you must be sure that each expression has an alias
|
@@ -708,19 +708,19 @@ module Sequel
|
|
708
708
|
# array of column values. If the value_column is not given or nil, uses
|
709
709
|
# the entire hash as the value.
|
710
710
|
#
|
711
|
-
# DB[:table].
|
711
|
+
# DB[:table].to_hash_groups(:name, :id) # SELECT * FROM table
|
712
712
|
# # {'Jim'=>[1, 4, 16, ...], 'Bob'=>[2], ...}
|
713
713
|
#
|
714
|
-
# DB[:table].
|
714
|
+
# DB[:table].to_hash_groups(:name) # SELECT * FROM table
|
715
715
|
# # {'Jim'=>[{:id=>1, :name=>'Jim'}, {:id=>4, :name=>'Jim'}, ...], 'Bob'=>[{:id=>2, :name=>'Bob'}], ...}
|
716
716
|
#
|
717
717
|
# You can also provide an array of column names for either the key_column,
|
718
718
|
# the value column, or both:
|
719
719
|
#
|
720
|
-
# DB[:table].
|
720
|
+
# DB[:table].to_hash_groups([:first, :middle], [:last, :id]) # SELECT * FROM table
|
721
721
|
# # {['Jim', 'Bob']=>[['Smith', 1], ['Jackson', 4], ...], ...}
|
722
722
|
#
|
723
|
-
# DB[:table].
|
723
|
+
# DB[:table].to_hash_groups([:first, :middle]) # SELECT * FROM table
|
724
724
|
# # {['Jim', 'Bob']=>[{:id=>1, :first=>'Jim', :middle=>'Bob', :last=>'Smith'}, ...], ...}
|
725
725
|
def to_hash_groups(key_column, value_column = nil)
|
726
726
|
h = {}
|
@@ -889,6 +889,12 @@ module Sequel
|
|
889
889
|
end
|
890
890
|
end
|
891
891
|
|
892
|
+
# The default number of rows that can be inserted in a single INSERT statement via import.
|
893
|
+
# The default is for no limit.
|
894
|
+
def default_import_slice
|
895
|
+
nil
|
896
|
+
end
|
897
|
+
|
892
898
|
# Set the server to use to :default unless it is already set in the passed opts
|
893
899
|
def default_server_opts(opts)
|
894
900
|
{:server=>@opts[:server] || :default}.merge(opts)
|
@@ -54,6 +54,13 @@ module Sequel
|
|
54
54
|
false
|
55
55
|
end
|
56
56
|
|
57
|
+
# Whether the database supports derived column lists (e.g.
|
58
|
+
# "table_expr AS table_alias(column_alias1, column_alias2, ...)"), true by
|
59
|
+
# default.
|
60
|
+
def supports_derived_column_lists?
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
57
64
|
# Whether the dataset supports or can emulate the DISTINCT ON clause, false by default.
|
58
65
|
def supports_distinct_on?
|
59
66
|
false
|
data/lib/sequel/dataset/query.rb
CHANGED
@@ -213,10 +213,13 @@ module Sequel
|
|
213
213
|
#
|
214
214
|
# ds.from_self(:alias=>:foo)
|
215
215
|
# # SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS foo
|
216
|
+
#
|
217
|
+
# ds.from_self(:alias=>:foo, :column_aliases=>[:c1, :c2])
|
218
|
+
# # SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS foo(c1, c2)
|
216
219
|
def from_self(opts=OPTS)
|
217
220
|
fs = {}
|
218
221
|
@opts.keys.each{|k| fs[k] = nil unless NON_SQL_OPTIONS.include?(k)}
|
219
|
-
clone(fs).from(opts[:alias] ? as(opts[:alias]) : self)
|
222
|
+
clone(fs).from(opts[:alias] ? as(opts[:alias], opts[:column_aliases]) : self)
|
220
223
|
end
|
221
224
|
|
222
225
|
# Match any of the columns to any of the patterns. The terms can be
|
@@ -237,19 +240,23 @@ module Sequel
|
|
237
240
|
# Examples:
|
238
241
|
#
|
239
242
|
# dataset.grep(:a, '%test%')
|
240
|
-
# # SELECT * FROM items WHERE (a LIKE '%test%')
|
243
|
+
# # SELECT * FROM items WHERE (a LIKE '%test%' ESCAPE '\')
|
241
244
|
#
|
242
245
|
# dataset.grep([:a, :b], %w'%test% foo')
|
243
|
-
# # SELECT * FROM items WHERE ((a LIKE '%test%'
|
246
|
+
# # SELECT * FROM items WHERE ((a LIKE '%test%' ESCAPE '\') OR (a LIKE 'foo' ESCAPE '\')
|
247
|
+
# # OR (b LIKE '%test%' ESCAPE '\') OR (b LIKE 'foo' ESCAPE '\'))
|
244
248
|
#
|
245
249
|
# dataset.grep([:a, :b], %w'%foo% %bar%', :all_patterns=>true)
|
246
|
-
# # SELECT * FROM a WHERE (((a LIKE '%foo%'
|
250
|
+
# # SELECT * FROM a WHERE (((a LIKE '%foo%' ESCAPE '\') OR (b LIKE '%foo%' ESCAPE '\'))
|
251
|
+
# # AND ((a LIKE '%bar%' ESCAPE '\') OR (b LIKE '%bar%' ESCAPE '\')))
|
247
252
|
#
|
248
253
|
# dataset.grep([:a, :b], %w'%foo% %bar%', :all_columns=>true)
|
249
|
-
# # SELECT * FROM a WHERE (((a LIKE '%foo%'
|
254
|
+
# # SELECT * FROM a WHERE (((a LIKE '%foo%' ESCAPE '\') OR (a LIKE '%bar%' ESCAPE '\'))
|
255
|
+
# # AND ((b LIKE '%foo%' ESCAPE '\') OR (b LIKE '%bar%' ESCAPE '\')))
|
250
256
|
#
|
251
257
|
# dataset.grep([:a, :b], %w'%foo% %bar%', :all_patterns=>true, :all_columns=>true)
|
252
|
-
# # SELECT * FROM a WHERE ((a LIKE '%foo%'
|
258
|
+
# # SELECT * FROM a WHERE ((a LIKE '%foo%' ESCAPE '\') AND (b LIKE '%foo%' ESCAPE '\')
|
259
|
+
# # AND (a LIKE '%bar%' ESCAPE '\') AND (b LIKE '%bar%' ESCAPE '\'))
|
253
260
|
def grep(columns, patterns, opts=OPTS)
|
254
261
|
if opts[:all_patterns]
|
255
262
|
conds = Array(patterns).map do |pat|
|
@@ -445,23 +452,33 @@ module Sequel
|
|
445
452
|
last_alias = options[:implicit_qualifier]
|
446
453
|
qualify_type = options[:qualify]
|
447
454
|
|
448
|
-
if table.is_a?(
|
455
|
+
if table.is_a?(SQL::AliasedExpression)
|
456
|
+
table_expr = if table_alias
|
457
|
+
SQL::AliasedExpression.new(table.expression, table_alias, table.columns)
|
458
|
+
else
|
459
|
+
table
|
460
|
+
end
|
461
|
+
table = table_expr.expression
|
462
|
+
table_name = table_alias = table_expr.alias
|
463
|
+
elsif table.is_a?(Dataset)
|
449
464
|
if table_alias.nil?
|
450
465
|
table_alias_num = (@opts[:num_dataset_sources] || 0) + 1
|
451
466
|
table_alias = dataset_alias(table_alias_num)
|
452
467
|
end
|
453
468
|
table_name = table_alias
|
469
|
+
table_expr = SQL::AliasedExpression.new(table, table_alias)
|
454
470
|
else
|
455
471
|
table, implicit_table_alias = split_alias(table)
|
456
472
|
table_alias ||= implicit_table_alias
|
457
473
|
table_name = table_alias || table
|
474
|
+
table_expr = table_alias ? SQL::AliasedExpression.new(table, table_alias) : table
|
458
475
|
end
|
459
476
|
|
460
477
|
join = if expr.nil? and !block
|
461
|
-
SQL::JoinClause.new(type,
|
478
|
+
SQL::JoinClause.new(type, table_expr)
|
462
479
|
elsif using_join
|
463
480
|
raise(Sequel::Error, "can't use a block if providing an array of symbols as expr") if block
|
464
|
-
SQL::JoinUsingClause.new(expr, type,
|
481
|
+
SQL::JoinUsingClause.new(expr, type, table_expr)
|
465
482
|
else
|
466
483
|
last_alias ||= @opts[:last_joined_table] || first_source_alias
|
467
484
|
if Sequel.condition_specifier?(expr)
|
@@ -485,7 +502,7 @@ module Sequel
|
|
485
502
|
expr2 = yield(table_name, last_alias, @opts[:join] || [])
|
486
503
|
expr = expr ? SQL::BooleanExpression.new(:AND, expr, expr2) : expr2
|
487
504
|
end
|
488
|
-
SQL::JoinOnClause.new(expr, type,
|
505
|
+
SQL::JoinOnClause.new(expr, type, table_expr)
|
489
506
|
end
|
490
507
|
|
491
508
|
opts = {:join => (@opts[:join] || []) + [join], :last_joined_table => table_name}
|
@@ -890,7 +907,7 @@ module Sequel
|
|
890
907
|
# :recursive :: Specify that this is a recursive CTE
|
891
908
|
#
|
892
909
|
# DB[:items].with(:items, DB[:syx].where(:name.like('A%')))
|
893
|
-
# # WITH items AS (SELECT * FROM syx WHERE (name LIKE 'A%')) SELECT * FROM items
|
910
|
+
# # WITH items AS (SELECT * FROM syx WHERE (name LIKE 'A%' ESCAPE '\')) SELECT * FROM items
|
894
911
|
def with(name, dataset, opts=OPTS)
|
895
912
|
raise(Error, 'This dataset does not support common table expressions') unless supports_cte?
|
896
913
|
if hoist_cte?(dataset)
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -5,7 +5,7 @@ module Sequel
|
|
5
5
|
# These are methods you can call to see what SQL will be generated by the dataset.
|
6
6
|
# ---------------------
|
7
7
|
|
8
|
-
# Returns an EXISTS clause for the dataset as
|
8
|
+
# Returns an EXISTS clause for the dataset as an SQL::PlaceholderLiteralString.
|
9
9
|
#
|
10
10
|
# DB.select(1).where(DB[:items].exists)
|
11
11
|
# # SELECT 1 WHERE (EXISTS (SELECT * FROM items))
|
@@ -274,6 +274,7 @@ module Sequel
|
|
274
274
|
ESCAPE = " ESCAPE ".freeze
|
275
275
|
EXTRACT = 'extract('.freeze
|
276
276
|
EXISTS = ['EXISTS '.freeze].freeze
|
277
|
+
FILTER = " FILTER (WHERE ".freeze
|
277
278
|
FOR_UPDATE = ' FOR UPDATE'.freeze
|
278
279
|
FORMAT_DATE = "'%Y-%m-%d'".freeze
|
279
280
|
FORMAT_DATE_STANDARD = "DATE '%Y-%m-%d'".freeze
|
@@ -284,7 +285,7 @@ module Sequel
|
|
284
285
|
FRAME_ALL = "ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING".freeze
|
285
286
|
FRAME_ROWS = "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW".freeze
|
286
287
|
FROM = ' FROM '.freeze
|
287
|
-
|
288
|
+
FUNCTION_DISTINCT = "DISTINCT ".freeze
|
288
289
|
GROUP_BY = " GROUP BY ".freeze
|
289
290
|
HAVING = " HAVING ".freeze
|
290
291
|
INSERT = "INSERT".freeze
|
@@ -332,6 +333,8 @@ module Sequel
|
|
332
333
|
VALUES = " VALUES ".freeze
|
333
334
|
V190 = '1.9.0'.freeze
|
334
335
|
WHERE = " WHERE ".freeze
|
336
|
+
WITH_ORDINALITY = " WITH ORDINALITY".freeze
|
337
|
+
WITHIN_GROUP = " WITHIN GROUP (ORDER BY ".freeze
|
335
338
|
|
336
339
|
[:literal, :quote_identifier, :quote_schema_table].each do |meth|
|
337
340
|
class_eval(<<-END, __FILE__, __LINE__ + 1)
|
@@ -346,7 +349,7 @@ module Sequel
|
|
346
349
|
# Append literalization of aliased expression to SQL string.
|
347
350
|
def aliased_expression_sql_append(sql, ae)
|
348
351
|
literal_append(sql, ae.expression)
|
349
|
-
as_sql_append(sql, ae.alias)
|
352
|
+
as_sql_append(sql, ae.alias, ae.columns)
|
350
353
|
end
|
351
354
|
|
352
355
|
# Append literalization of array to SQL string.
|
@@ -522,28 +525,87 @@ module Sequel
|
|
522
525
|
end
|
523
526
|
end
|
524
527
|
|
525
|
-
#
|
526
|
-
# By default, assumes just the function name may need to
|
527
|
-
# be emulated, adapters should set an EMULATED_FUNCTION_MAP
|
528
|
-
# hash mapping emulated functions to native functions in
|
529
|
-
# their dataset class to setup the emulation.
|
528
|
+
# REMOVE411
|
530
529
|
def emulated_function_sql_append(sql, f)
|
531
530
|
_function_sql_append(sql, native_function_name(f.f), f.args)
|
532
531
|
end
|
533
532
|
|
534
533
|
# Append literalization of function call to SQL string.
|
535
534
|
def function_sql_append(sql, f)
|
536
|
-
|
535
|
+
name = f.name
|
536
|
+
opts = f.opts
|
537
|
+
|
538
|
+
if opts[:emulate]
|
539
|
+
if emulate_function?(name)
|
540
|
+
emulate_function_sql_append(sql, f)
|
541
|
+
return
|
542
|
+
end
|
543
|
+
|
544
|
+
name = native_function_name(name)
|
545
|
+
end
|
546
|
+
|
547
|
+
sql << LATERAL if opts[:lateral]
|
548
|
+
|
549
|
+
case name
|
550
|
+
when SQL::Identifier
|
551
|
+
if supports_quoted_function_names? && opts[:quoted] != false
|
552
|
+
literal_append(sql, name)
|
553
|
+
else
|
554
|
+
sql << name.value.to_s
|
555
|
+
end
|
556
|
+
when SQL::QualifiedIdentifier
|
557
|
+
if supports_quoted_function_names? && opts[:quoted] != false
|
558
|
+
literal_append(sql, name)
|
559
|
+
else
|
560
|
+
sql << split_qualifiers(name).join(DOT)
|
561
|
+
end
|
562
|
+
else
|
563
|
+
if supports_quoted_function_names? && opts[:quoted]
|
564
|
+
quote_identifier_append(sql, name)
|
565
|
+
else
|
566
|
+
sql << name.to_s
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
sql << PAREN_OPEN
|
571
|
+
if opts[:*]
|
572
|
+
sql << WILDCARD
|
573
|
+
else
|
574
|
+
sql << FUNCTION_DISTINCT if opts[:distinct]
|
575
|
+
expression_list_append(sql, f.args)
|
576
|
+
end
|
577
|
+
sql << PAREN_CLOSE
|
578
|
+
|
579
|
+
if group = opts[:within_group]
|
580
|
+
sql << WITHIN_GROUP
|
581
|
+
expression_list_append(sql, group)
|
582
|
+
sql << PAREN_CLOSE
|
583
|
+
end
|
584
|
+
|
585
|
+
if filter = opts[:filter]
|
586
|
+
sql << FILTER
|
587
|
+
literal_append(sql, filter_expr(filter, &opts[:filter_block]))
|
588
|
+
sql << PAREN_CLOSE
|
589
|
+
end
|
590
|
+
|
591
|
+
if window = opts[:over]
|
592
|
+
sql << OVER
|
593
|
+
window_sql_append(sql, window.opts)
|
594
|
+
end
|
595
|
+
|
596
|
+
if opts[:with_ordinality]
|
597
|
+
sql << WITH_ORDINALITY
|
598
|
+
end
|
537
599
|
end
|
538
600
|
|
539
601
|
# Append literalization of JOIN clause without ON or USING to SQL string.
|
540
602
|
def join_clause_sql_append(sql, jc)
|
541
603
|
table = jc.table
|
542
604
|
table_alias = jc.table_alias
|
543
|
-
table_alias = nil if table == table_alias
|
605
|
+
table_alias = nil if table == table_alias && !jc.column_aliases
|
544
606
|
sql << SPACE << join_type_sql(jc.join_type) << SPACE
|
545
607
|
identifier_append(sql, table)
|
546
|
-
as_sql_append(sql, table_alias) if table_alias
|
608
|
+
as_sql_append(sql, table_alias, jc.column_aliases) if table_alias
|
547
609
|
end
|
548
610
|
|
549
611
|
# Append literalization of JOIN ON clause to SQL string.
|
@@ -765,8 +827,9 @@ module Sequel
|
|
765
827
|
sql << PAREN_CLOSE
|
766
828
|
end
|
767
829
|
|
768
|
-
#
|
830
|
+
# REMOVE411
|
769
831
|
def window_function_sql_append(sql, function, window)
|
832
|
+
Deprecation.deprecate("Dataset#window_function_sql_append", "Please use Sequel::SQL::Function.new(name, *args).over(...) to create an SQL window function")
|
770
833
|
literal_append(sql, function)
|
771
834
|
sql << OVER
|
772
835
|
literal_append(sql, window)
|
@@ -782,8 +845,9 @@ module Sequel
|
|
782
845
|
|
783
846
|
private
|
784
847
|
|
785
|
-
#
|
848
|
+
# REMOVE411
|
786
849
|
def _function_sql_append(sql, name, args)
|
850
|
+
Deprecation.deprecate("Dataset#emulated_function_sql_append and #_function_sql_append", "Please use Sequel::SQL::Function.new!(name, args, :emulate=>true) to create an emulated SQL function")
|
787
851
|
case name
|
788
852
|
when SQL::Identifier
|
789
853
|
if supports_quoted_function_names?
|
@@ -857,9 +921,15 @@ module Sequel
|
|
857
921
|
end
|
858
922
|
|
859
923
|
# Append aliasing expression to SQL string.
|
860
|
-
def as_sql_append(sql, aliaz)
|
924
|
+
def as_sql_append(sql, aliaz, column_aliases=nil)
|
861
925
|
sql << AS
|
862
926
|
quote_identifier_append(sql, aliaz)
|
927
|
+
if column_aliases
|
928
|
+
raise Error, "#{db.database_type} does not support derived column lists" unless supports_derived_column_lists?
|
929
|
+
sql << PAREN_OPEN
|
930
|
+
identifier_list_append(sql, column_aliases)
|
931
|
+
sql << PAREN_CLOSE
|
932
|
+
end
|
863
933
|
end
|
864
934
|
|
865
935
|
# Raise an InvalidOperation exception if deletion is not allowed
|
@@ -960,6 +1030,12 @@ module Sequel
|
|
960
1030
|
nil
|
961
1031
|
end
|
962
1032
|
|
1033
|
+
# Whether to emulate the function with the given name. This should only be true
|
1034
|
+
# if the emulation goes beyond choosing a function with a different name.
|
1035
|
+
def emulate_function?(name)
|
1036
|
+
false
|
1037
|
+
end
|
1038
|
+
|
963
1039
|
# Append literalization of array of expressions to SQL string.
|
964
1040
|
def expression_list_append(sql, columns)
|
965
1041
|
c = false
|