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.
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