sequel 4.10.0 → 4.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +58 -0
  3. data/doc/association_basics.rdoc +1 -1
  4. data/doc/cheat_sheet.rdoc +0 -1
  5. data/doc/core_extensions.rdoc +2 -2
  6. data/doc/dataset_filtering.rdoc +5 -5
  7. data/doc/model_hooks.rdoc +9 -0
  8. data/doc/object_model.rdoc +7 -13
  9. data/doc/opening_databases.rdoc +3 -1
  10. data/doc/querying.rdoc +8 -8
  11. data/doc/release_notes/4.11.0.txt +147 -0
  12. data/doc/sql.rdoc +11 -7
  13. data/doc/virtual_rows.rdoc +4 -5
  14. data/lib/sequel/adapters/ibmdb.rb +24 -16
  15. data/lib/sequel/adapters/jdbc/h2.rb +5 -0
  16. data/lib/sequel/adapters/jdbc/hsqldb.rb +5 -0
  17. data/lib/sequel/adapters/mock.rb +14 -2
  18. data/lib/sequel/adapters/shared/access.rb +6 -9
  19. data/lib/sequel/adapters/shared/cubrid.rb +5 -0
  20. data/lib/sequel/adapters/shared/db2.rb +5 -0
  21. data/lib/sequel/adapters/shared/firebird.rb +5 -0
  22. data/lib/sequel/adapters/shared/mssql.rb +23 -16
  23. data/lib/sequel/adapters/shared/mysql.rb +12 -2
  24. data/lib/sequel/adapters/shared/oracle.rb +31 -15
  25. data/lib/sequel/adapters/shared/postgres.rb +28 -4
  26. data/lib/sequel/adapters/shared/sqlanywhere.rb +5 -0
  27. data/lib/sequel/adapters/shared/sqlite.rb +12 -1
  28. data/lib/sequel/ast_transformer.rb +9 -7
  29. data/lib/sequel/connection_pool.rb +10 -4
  30. data/lib/sequel/database/features.rb +15 -0
  31. data/lib/sequel/database/schema_generator.rb +2 -2
  32. data/lib/sequel/database/schema_methods.rb +21 -3
  33. data/lib/sequel/database/transactions.rb +8 -4
  34. data/lib/sequel/dataset/actions.rb +13 -7
  35. data/lib/sequel/dataset/features.rb +7 -0
  36. data/lib/sequel/dataset/query.rb +28 -11
  37. data/lib/sequel/dataset/sql.rb +90 -14
  38. data/lib/sequel/extensions/constraint_validations.rb +2 -2
  39. data/lib/sequel/extensions/date_arithmetic.rb +1 -1
  40. data/lib/sequel/extensions/eval_inspect.rb +12 -6
  41. data/lib/sequel/extensions/pg_array_ops.rb +11 -2
  42. data/lib/sequel/extensions/pg_json.rb +130 -23
  43. data/lib/sequel/extensions/pg_json_ops.rb +196 -28
  44. data/lib/sequel/extensions/to_dot.rb +5 -7
  45. data/lib/sequel/model/associations.rb +0 -50
  46. data/lib/sequel/plugins/class_table_inheritance.rb +49 -21
  47. data/lib/sequel/plugins/many_through_many.rb +10 -11
  48. data/lib/sequel/plugins/serialization.rb +4 -1
  49. data/lib/sequel/plugins/sharding.rb +0 -9
  50. data/lib/sequel/plugins/single_table_inheritance.rb +4 -2
  51. data/lib/sequel/plugins/timestamps.rb +2 -2
  52. data/lib/sequel/sql.rb +166 -44
  53. data/lib/sequel/version.rb +1 -1
  54. data/spec/adapters/postgres_spec.rb +199 -133
  55. data/spec/core/connection_pool_spec.rb +6 -0
  56. data/spec/core/database_spec.rb +12 -0
  57. data/spec/core/dataset_spec.rb +58 -3
  58. data/spec/core/expression_filters_spec.rb +67 -5
  59. data/spec/core/mock_adapter_spec.rb +8 -4
  60. data/spec/core/schema_spec.rb +7 -0
  61. data/spec/core_extensions_spec.rb +14 -0
  62. data/spec/extensions/class_table_inheritance_spec.rb +23 -3
  63. data/spec/extensions/core_refinements_spec.rb +14 -0
  64. data/spec/extensions/eval_inspect_spec.rb +8 -4
  65. data/spec/extensions/pg_array_ops_spec.rb +6 -0
  66. data/spec/extensions/pg_json_ops_spec.rb +99 -0
  67. data/spec/extensions/pg_json_spec.rb +104 -4
  68. data/spec/extensions/serialization_spec.rb +19 -0
  69. data/spec/extensions/single_table_inheritance_spec.rb +11 -3
  70. data/spec/extensions/timestamps_spec.rb +10 -0
  71. data/spec/extensions/to_dot_spec.rb +8 -4
  72. data/spec/integration/database_test.rb +1 -1
  73. data/spec/integration/dataset_test.rb +9 -0
  74. data/spec/integration/schema_test.rb +27 -0
  75. metadata +4 -2
@@ -235,6 +235,11 @@ module Sequel
235
235
  select_map(:a__name).
236
236
  map{|n| m.call(n)}
237
237
  end
238
+
239
+ # SQLAnywhere supports views with check option, but not local.
240
+ def view_with_check_option_support
241
+ true
242
+ end
238
243
  end
239
244
 
240
245
  module DatasetMethods
@@ -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
- SQL::Function.new(o.f, *v(o.args))
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.table), v(o.table_alias))
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.table), v(o.table_alias))
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.table), v(o.table_alias))
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 :: The proc called after each new connection is made, with the
73
- # connection object, useful for customizations that you want to apply to all
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
- @after_connect.call(conn) if @after_connect
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
- # DB.create_view(:ruby_items, DB[:items].filter(:category => 'ruby'))
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 option:
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
- depth = savepoint_level(conn)
217
- if depth > 1
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[:commit_every] || opts[:slice]
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].select_hash(:name, :id) # SELECT id, name FROM 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].select_hash([:first, :middle], [:last, :id]) # SELECT * FROM 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].to_hash(:name, :id) # SELECT * FROM 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].to_hash(:name) # SELECT * FROM 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].to_hash([:first, :middle], [:last, :id]) # SELECT * FROM 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].to_hash([:first, :middle]) # SELECT * FROM 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
@@ -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%') OR (a LIKE 'foo') OR (b LIKE '%test%') OR (b LIKE 'foo'))
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%') OR (b LIKE '%foo%')) AND ((a LIKE '%bar%') OR (b LIKE '%bar%')))
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%') OR (a LIKE '%bar%')) AND ((b LIKE '%foo%') OR (b LIKE '%bar%')))
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%') AND (b LIKE '%foo%') AND (a LIKE '%bar%') AND (b LIKE '%bar%'))
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?(Dataset)
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, table, table_alias)
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, table, table_alias)
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, table, table_alias)
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)
@@ -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 a +LiteralString+.
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
- FUNCTION_EMPTY = '()'.freeze
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
- # Append literalization of emulated function call to SQL string.
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
- _function_sql_append(sql, f.f, f.args)
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
- # Append literalization of window function calls to SQL string.
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
- # Backbone of function_sql_append and emulated_function_sql_append.
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