sequel 5.39.0 → 5.63.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (187) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +308 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +57 -25
  5. data/bin/sequel +11 -3
  6. data/doc/advanced_associations.rdoc +13 -13
  7. data/doc/association_basics.rdoc +89 -24
  8. data/doc/cheat_sheet.rdoc +11 -3
  9. data/doc/migration.rdoc +12 -6
  10. data/doc/model_hooks.rdoc +1 -1
  11. data/doc/object_model.rdoc +8 -8
  12. data/doc/opening_databases.rdoc +18 -11
  13. data/doc/postgresql.rdoc +16 -8
  14. data/doc/querying.rdoc +5 -3
  15. data/doc/release_notes/5.40.0.txt +40 -0
  16. data/doc/release_notes/5.41.0.txt +25 -0
  17. data/doc/release_notes/5.42.0.txt +136 -0
  18. data/doc/release_notes/5.43.0.txt +98 -0
  19. data/doc/release_notes/5.44.0.txt +32 -0
  20. data/doc/release_notes/5.45.0.txt +34 -0
  21. data/doc/release_notes/5.46.0.txt +87 -0
  22. data/doc/release_notes/5.47.0.txt +59 -0
  23. data/doc/release_notes/5.48.0.txt +14 -0
  24. data/doc/release_notes/5.49.0.txt +59 -0
  25. data/doc/release_notes/5.50.0.txt +78 -0
  26. data/doc/release_notes/5.51.0.txt +47 -0
  27. data/doc/release_notes/5.52.0.txt +87 -0
  28. data/doc/release_notes/5.53.0.txt +23 -0
  29. data/doc/release_notes/5.54.0.txt +27 -0
  30. data/doc/release_notes/5.55.0.txt +21 -0
  31. data/doc/release_notes/5.56.0.txt +51 -0
  32. data/doc/release_notes/5.57.0.txt +23 -0
  33. data/doc/release_notes/5.58.0.txt +31 -0
  34. data/doc/release_notes/5.59.0.txt +73 -0
  35. data/doc/release_notes/5.60.0.txt +22 -0
  36. data/doc/release_notes/5.61.0.txt +43 -0
  37. data/doc/release_notes/5.62.0.txt +132 -0
  38. data/doc/release_notes/5.63.0.txt +33 -0
  39. data/doc/schema_modification.rdoc +1 -1
  40. data/doc/security.rdoc +9 -9
  41. data/doc/sql.rdoc +27 -15
  42. data/doc/testing.rdoc +22 -11
  43. data/doc/transactions.rdoc +6 -6
  44. data/doc/virtual_rows.rdoc +2 -2
  45. data/lib/sequel/adapters/ado/access.rb +1 -1
  46. data/lib/sequel/adapters/ado.rb +17 -17
  47. data/lib/sequel/adapters/amalgalite.rb +3 -5
  48. data/lib/sequel/adapters/ibmdb.rb +2 -2
  49. data/lib/sequel/adapters/jdbc/derby.rb +8 -0
  50. data/lib/sequel/adapters/jdbc/h2.rb +60 -10
  51. data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
  52. data/lib/sequel/adapters/jdbc/postgresql.rb +4 -4
  53. data/lib/sequel/adapters/jdbc.rb +16 -18
  54. data/lib/sequel/adapters/mysql.rb +80 -67
  55. data/lib/sequel/adapters/mysql2.rb +54 -49
  56. data/lib/sequel/adapters/odbc.rb +6 -2
  57. data/lib/sequel/adapters/oracle.rb +3 -3
  58. data/lib/sequel/adapters/postgres.rb +83 -40
  59. data/lib/sequel/adapters/shared/access.rb +11 -1
  60. data/lib/sequel/adapters/shared/db2.rb +30 -0
  61. data/lib/sequel/adapters/shared/mssql.rb +58 -7
  62. data/lib/sequel/adapters/shared/mysql.rb +40 -2
  63. data/lib/sequel/adapters/shared/oracle.rb +76 -0
  64. data/lib/sequel/adapters/shared/postgres.rb +418 -174
  65. data/lib/sequel/adapters/shared/sqlanywhere.rb +10 -0
  66. data/lib/sequel/adapters/shared/sqlite.rb +102 -11
  67. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  68. data/lib/sequel/adapters/sqlite.rb +60 -18
  69. data/lib/sequel/adapters/tinytds.rb +1 -1
  70. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  71. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  72. data/lib/sequel/ast_transformer.rb +6 -0
  73. data/lib/sequel/connection_pool/sharded_single.rb +5 -7
  74. data/lib/sequel/connection_pool/sharded_threaded.rb +5 -1
  75. data/lib/sequel/connection_pool/single.rb +6 -8
  76. data/lib/sequel/connection_pool/threaded.rb +8 -8
  77. data/lib/sequel/connection_pool/timed_queue.rb +257 -0
  78. data/lib/sequel/connection_pool.rb +47 -30
  79. data/lib/sequel/core.rb +28 -18
  80. data/lib/sequel/database/connecting.rb +26 -2
  81. data/lib/sequel/database/misc.rb +69 -14
  82. data/lib/sequel/database/query.rb +38 -1
  83. data/lib/sequel/database/schema_generator.rb +45 -52
  84. data/lib/sequel/database/schema_methods.rb +17 -1
  85. data/lib/sequel/dataset/actions.rb +107 -13
  86. data/lib/sequel/dataset/features.rb +20 -0
  87. data/lib/sequel/dataset/misc.rb +1 -1
  88. data/lib/sequel/dataset/prepared_statements.rb +2 -0
  89. data/lib/sequel/dataset/query.rb +118 -16
  90. data/lib/sequel/dataset/sql.rb +177 -47
  91. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  92. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  93. data/lib/sequel/extensions/any_not_empty.rb +1 -1
  94. data/lib/sequel/extensions/async_thread_pool.rb +438 -0
  95. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  96. data/lib/sequel/extensions/blank.rb +8 -0
  97. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  98. data/lib/sequel/extensions/core_refinements.rb +36 -11
  99. data/lib/sequel/extensions/date_arithmetic.rb +71 -31
  100. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  101. data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
  102. data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
  103. data/lib/sequel/extensions/eval_inspect.rb +2 -0
  104. data/lib/sequel/extensions/inflector.rb +9 -1
  105. data/lib/sequel/extensions/is_distinct_from.rb +141 -0
  106. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  107. data/lib/sequel/extensions/migration.rb +7 -2
  108. data/lib/sequel/extensions/named_timezones.rb +26 -6
  109. data/lib/sequel/extensions/pagination.rb +1 -1
  110. data/lib/sequel/extensions/pg_array.rb +23 -3
  111. data/lib/sequel/extensions/pg_array_ops.rb +2 -2
  112. data/lib/sequel/extensions/pg_auto_parameterize.rb +478 -0
  113. data/lib/sequel/extensions/pg_enum.rb +1 -1
  114. data/lib/sequel/extensions/pg_extended_date_support.rb +28 -25
  115. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  116. data/lib/sequel/extensions/pg_hstore.rb +6 -1
  117. data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
  118. data/lib/sequel/extensions/pg_inet.rb +10 -11
  119. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  120. data/lib/sequel/extensions/pg_interval.rb +45 -19
  121. data/lib/sequel/extensions/pg_json.rb +13 -15
  122. data/lib/sequel/extensions/pg_json_ops.rb +73 -2
  123. data/lib/sequel/extensions/pg_loose_count.rb +3 -1
  124. data/lib/sequel/extensions/pg_multirange.rb +367 -0
  125. data/lib/sequel/extensions/pg_range.rb +10 -23
  126. data/lib/sequel/extensions/pg_range_ops.rb +37 -9
  127. data/lib/sequel/extensions/pg_row.rb +19 -13
  128. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  129. data/lib/sequel/extensions/query.rb +2 -0
  130. data/lib/sequel/extensions/s.rb +2 -1
  131. data/lib/sequel/extensions/schema_dumper.rb +13 -2
  132. data/lib/sequel/extensions/server_block.rb +8 -12
  133. data/lib/sequel/extensions/sql_comments.rb +110 -3
  134. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  135. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  136. data/lib/sequel/extensions/string_agg.rb +1 -1
  137. data/lib/sequel/extensions/string_date_time.rb +19 -23
  138. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  139. data/lib/sequel/model/associations.rb +325 -96
  140. data/lib/sequel/model/base.rb +51 -27
  141. data/lib/sequel/model/errors.rb +10 -1
  142. data/lib/sequel/model/inflections.rb +1 -1
  143. data/lib/sequel/model/plugins.rb +5 -0
  144. data/lib/sequel/plugins/association_proxies.rb +2 -0
  145. data/lib/sequel/plugins/async_thread_pool.rb +39 -0
  146. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  147. data/lib/sequel/plugins/auto_validations.rb +87 -15
  148. data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
  149. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  150. data/lib/sequel/plugins/column_encryption.rb +728 -0
  151. data/lib/sequel/plugins/composition.rb +10 -4
  152. data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
  153. data/lib/sequel/plugins/constraint_validations.rb +2 -1
  154. data/lib/sequel/plugins/dataset_associations.rb +4 -1
  155. data/lib/sequel/plugins/dirty.rb +1 -1
  156. data/lib/sequel/plugins/enum.rb +124 -0
  157. data/lib/sequel/plugins/finder.rb +3 -1
  158. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  159. data/lib/sequel/plugins/instance_specific_default.rb +1 -1
  160. data/lib/sequel/plugins/json_serializer.rb +39 -24
  161. data/lib/sequel/plugins/lazy_attributes.rb +3 -0
  162. data/lib/sequel/plugins/list.rb +3 -1
  163. data/lib/sequel/plugins/many_through_many.rb +108 -9
  164. data/lib/sequel/plugins/nested_attributes.rb +12 -7
  165. data/lib/sequel/plugins/pg_array_associations.rb +56 -38
  166. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +3 -1
  167. data/lib/sequel/plugins/prepared_statements.rb +10 -1
  168. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  169. data/lib/sequel/plugins/rcte_tree.rb +27 -19
  170. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  171. data/lib/sequel/plugins/serialization.rb +9 -3
  172. data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
  173. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  174. data/lib/sequel/plugins/sql_comments.rb +189 -0
  175. data/lib/sequel/plugins/static_cache.rb +1 -1
  176. data/lib/sequel/plugins/subclasses.rb +28 -11
  177. data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
  178. data/lib/sequel/plugins/timestamps.rb +1 -1
  179. data/lib/sequel/plugins/unused_associations.rb +521 -0
  180. data/lib/sequel/plugins/update_or_create.rb +1 -1
  181. data/lib/sequel/plugins/validate_associated.rb +22 -12
  182. data/lib/sequel/plugins/validation_helpers.rb +38 -11
  183. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  184. data/lib/sequel/sql.rb +1 -1
  185. data/lib/sequel/timezones.rb +12 -14
  186. data/lib/sequel/version.rb +1 -1
  187. metadata +97 -43
@@ -65,7 +65,7 @@ module Sequel
65
65
  Sequel.synchronize{EXTENSIONS[ext] = block}
66
66
  end
67
67
 
68
- # On Ruby 2.4+, use clone(:freeze=>false) to create clones, because
68
+ # On Ruby 2.4+, use clone(freeze: false) to create clones, because
69
69
  # we use true freezing in that case, and we need to modify the opts
70
70
  # in the frozen copy.
71
71
  #
@@ -116,7 +116,7 @@ module Sequel
116
116
  # DB[:items].order(:id).distinct(:id) # SQL: SELECT DISTINCT ON (id) * FROM items ORDER BY id
117
117
  # DB[:items].order(:id).distinct{func(:id)} # SQL: SELECT DISTINCT ON (func(id)) * FROM items ORDER BY id
118
118
  #
119
- # There is support for emualting the DISTINCT ON support in MySQL, but it
119
+ # There is support for emulating the DISTINCT ON support in MySQL, but it
120
120
  # does not support the ORDER of the dataset, and also doesn't work in many
121
121
  # cases if the ONLY_FULL_GROUP_BY sql_mode is used, which is the default on
122
122
  # MySQL 5.7.5+.
@@ -508,6 +508,7 @@ module Sequel
508
508
  # argument.
509
509
  # :implicit_qualifier :: The name to use for qualifying implicit conditions. By default,
510
510
  # the last joined or primary table is used.
511
+ # :join_using :: Force the using of JOIN USING, even if +expr+ is not an array of symbols.
511
512
  # :reset_implicit_qualifier :: Can set to false to ignore this join when future joins determine qualifier
512
513
  # for implicit conditions.
513
514
  # :qualify :: Can be set to false to not do any implicit qualification. Can be set
@@ -541,7 +542,7 @@ module Sequel
541
542
  return s.join_table(type, ds, expr, options, &block)
542
543
  end
543
544
 
544
- using_join = expr.is_a?(Array) && !expr.empty? && expr.all?{|x| x.is_a?(Symbol)}
545
+ using_join = options[:join_using] || (expr.is_a?(Array) && !expr.empty? && expr.all?{|x| x.is_a?(Symbol)})
545
546
  if using_join && !supports_join_using?
546
547
  h = {}
547
548
  expr.each{|e| h[e] = e}
@@ -616,7 +617,7 @@ module Sequel
616
617
  UNCONDITIONED_JOIN_TYPES.each do |jtype|
617
618
  class_eval(<<-END, __FILE__, __LINE__+1)
618
619
  def #{jtype}_join(table, opts=Sequel::OPTS)
619
- raise(Sequel::Error, '#{jtype}_join does not accept join table blocks') if block_given?
620
+ raise(Sequel::Error, '#{jtype}_join does not accept join table blocks') if defined?(yield)
620
621
  raise(Sequel::Error, '#{jtype}_join 2nd argument should be an options hash, not conditions') unless opts.is_a?(Hash)
621
622
  join_table(:#{jtype}, table, nil, opts)
622
623
  end
@@ -677,6 +678,56 @@ module Sequel
677
678
  clone(:lock => style)
678
679
  end
679
680
 
681
+ # Return a dataset with a WHEN MATCHED THEN DELETE clause added to the
682
+ # MERGE statement. If a block is passed, treat it as a virtual row and
683
+ # use it as additional conditions for the match.
684
+ #
685
+ # merge_delete
686
+ # # WHEN MATCHED THEN DELETE
687
+ #
688
+ # merge_delete{a > 30}
689
+ # # WHEN MATCHED AND (a > 30) THEN DELETE
690
+ def merge_delete(&block)
691
+ _merge_when(:type=>:delete, &block)
692
+ end
693
+
694
+ # Return a dataset with a WHEN NOT MATCHED THEN INSERT clause added to the
695
+ # MERGE statement. If a block is passed, treat it as a virtual row and
696
+ # use it as additional conditions for the match.
697
+ #
698
+ # The arguments provided can be any arguments that would be accepted by
699
+ # #insert.
700
+ #
701
+ # merge_insert(i1: :i2, a: Sequel[:b]+11)
702
+ # # WHEN NOT MATCHED THEN INSERT (i1, a) VALUES (i2, (b + 11))
703
+ #
704
+ # merge_insert(:i2, Sequel[:b]+11){a > 30}
705
+ # # WHEN NOT MATCHED AND (a > 30) THEN INSERT VALUES (i2, (b + 11))
706
+ def merge_insert(*values, &block)
707
+ _merge_when(:type=>:insert, :values=>values, &block)
708
+ end
709
+
710
+ # Return a dataset with a WHEN MATCHED THEN UPDATE clause added to the
711
+ # MERGE statement. If a block is passed, treat it as a virtual row and
712
+ # use it as additional conditions for the match.
713
+ #
714
+ # merge_update(i1: Sequel[:i1]+:i2+10, a: Sequel[:a]+:b+20)
715
+ # # WHEN MATCHED THEN UPDATE SET i1 = (i1 + i2 + 10), a = (a + b + 20)
716
+ #
717
+ # merge_update(i1: :i2){a > 30}
718
+ # # WHEN MATCHED AND (a > 30) THEN UPDATE SET i1 = i2
719
+ def merge_update(values, &block)
720
+ _merge_when(:type=>:update, :values=>values, &block)
721
+ end
722
+
723
+ # Return a dataset with the source and join condition to use for the MERGE statement.
724
+ #
725
+ # merge_using(:m2, i1: :i2)
726
+ # # USING m2 ON (i1 = i2)
727
+ def merge_using(source, join_condition)
728
+ clone(:merge_using => [source, join_condition].freeze)
729
+ end
730
+
680
731
  # Returns a cloned dataset without a row_proc.
681
732
  #
682
733
  # ds = DB[:items].with_row_proc(:invert.to_proc)
@@ -699,7 +750,7 @@ module Sequel
699
750
  end
700
751
 
701
752
  # Returns a copy of the dataset with a specified order. Can be safely combined with limit.
702
- # If you call limit with an offset, it will override override the offset if you've called
753
+ # If you call limit with an offset, it will override the offset if you've called
703
754
  # offset first.
704
755
  #
705
756
  # DB[:items].offset(10) # SELECT * FROM items OFFSET 10
@@ -736,7 +787,7 @@ module Sequel
736
787
  # DB[:items].order(Sequel.lit('a + b')) # SELECT * FROM items ORDER BY a + b
737
788
  # DB[:items].order(Sequel[:a] + :b) # SELECT * FROM items ORDER BY (a + b)
738
789
  # DB[:items].order(Sequel.desc(:name)) # SELECT * FROM items ORDER BY name DESC
739
- # DB[:items].order(Sequel.asc(:name, :nulls=>:last)) # SELECT * FROM items ORDER BY name ASC NULLS LAST
790
+ # DB[:items].order(Sequel.asc(:name, nulls: :last)) # SELECT * FROM items ORDER BY name ASC NULLS LAST
740
791
  # DB[:items].order{sum(name).desc} # SELECT * FROM items ORDER BY sum(name) DESC
741
792
  # DB[:items].order(nil) # SELECT * FROM items
742
793
  def order(*columns, &block)
@@ -806,13 +857,13 @@ module Sequel
806
857
  # DB[:items].returning(nil) # RETURNING NULL
807
858
  # DB[:items].returning(:id, :name) # RETURNING id, name
808
859
  #
809
- # DB[:items].returning.insert(:a=>1) do |hash|
860
+ # DB[:items].returning.insert(a: 1) do |hash|
810
861
  # # hash for each row inserted, with values for all columns
811
862
  # end
812
- # DB[:items].returning.update(:a=>1) do |hash|
863
+ # DB[:items].returning.update(a: 1) do |hash|
813
864
  # # hash for each row updated, with values for all columns
814
865
  # end
815
- # DB[:items].returning.delete(:a=>1) do |hash|
866
+ # DB[:items].returning.delete(a: 1) do |hash|
816
867
  # # hash for each row deleted, with values for all columns
817
868
  # end
818
869
  def returning(*values)
@@ -1051,7 +1102,7 @@ module Sequel
1051
1102
  # referenced in window functions. See Sequel::SQL::Window for a list of
1052
1103
  # options that can be passed in. Example:
1053
1104
  #
1054
- # DB[:items].window(:w, :partition=>:c1, :order=>:c2)
1105
+ # DB[:items].window(:w, partition: :c1, order: :c2)
1055
1106
  # # SELECT * FROM items WINDOW w AS (PARTITION BY c1 ORDER BY c2)
1056
1107
  def window(name, opts)
1057
1108
  clone(:window=>((@opts[:window]||EMPTY_ARRAY) + [[name, SQL::Window.new(opts)].freeze]).freeze)
@@ -1059,13 +1110,12 @@ module Sequel
1059
1110
 
1060
1111
  # Add a common table expression (CTE) with the given name and a dataset that defines the CTE.
1061
1112
  # A common table expression acts as an inline view for the query.
1113
+ #
1062
1114
  # Options:
1063
1115
  # :args :: Specify the arguments/columns for the CTE, should be an array of symbols.
1064
1116
  # :recursive :: Specify that this is a recursive CTE
1065
- #
1066
- # PostgreSQL Specific Options:
1067
1117
  # :materialized :: Set to false to force inlining of the CTE, or true to force not inlining
1068
- # the CTE (PostgreSQL 12+).
1118
+ # the CTE (PostgreSQL 12+/SQLite 3.35+).
1069
1119
  #
1070
1120
  # DB[:items].with(:items, DB[:syx].where(Sequel[:name].like('A%')))
1071
1121
  # # WITH items AS (SELECT * FROM syx WHERE (name LIKE 'A%' ESCAPE '\')) SELECT * FROM items
@@ -1081,20 +1131,60 @@ module Sequel
1081
1131
 
1082
1132
  # Add a recursive common table expression (CTE) with the given name, a dataset that
1083
1133
  # defines the nonrecursive part of the CTE, and a dataset that defines the recursive part
1084
- # of the CTE. Options:
1134
+ # of the CTE.
1135
+ #
1136
+ # Options:
1085
1137
  # :args :: Specify the arguments/columns for the CTE, should be an array of symbols.
1086
1138
  # :union_all :: Set to false to use UNION instead of UNION ALL combining the nonrecursive and recursive parts.
1087
1139
  #
1140
+ # PostgreSQL 14+ Options:
1141
+ # :cycle :: Stop recursive searching when a cycle is detected. Includes two columns in the
1142
+ # result of the CTE, a cycle column indicating whether a cycle was detected for
1143
+ # the current row, and a path column for the path traversed to get to the current
1144
+ # row. If given, must be a hash with the following keys:
1145
+ # :columns :: (required) The column or array of columns to use to detect a cycle.
1146
+ # If the value of these columns match columns already traversed, then
1147
+ # a cycle is detected, and recursive searching will not traverse beyond
1148
+ # the cycle (the CTE will include the row where the cycle was detected).
1149
+ # :cycle_column :: The name of the cycle column in the output, defaults to :is_cycle.
1150
+ # :cycle_value :: The value of the cycle column in the output if the current row was
1151
+ # detected as a cycle, defaults to true.
1152
+ # :noncycle_value :: The value of the cycle column in the output if the current row
1153
+ # was not detected as a cycle, defaults to false. Only respected
1154
+ # if :cycle_value is given.
1155
+ # :path_column :: The name of the path column in the output, defaults to :path.
1156
+ # :search :: Include an order column in the result of the CTE that allows for breadth or
1157
+ # depth first searching. If given, must be a hash with the following keys:
1158
+ # :by :: (required) The column or array of columns to search by.
1159
+ # :order_column :: The name of the order column in the output, defaults to :ordercol.
1160
+ # :type :: Set to :breadth to use breadth-first searching (depth-first searching
1161
+ # is the default).
1162
+ #
1088
1163
  # DB[:t].with_recursive(:t,
1089
1164
  # DB[:i1].select(:id, :parent_id).where(parent_id: nil),
1090
1165
  # DB[:i1].join(:t, id: :parent_id).select(Sequel[:i1][:id], Sequel[:i1][:parent_id]),
1091
- # :args=>[:id, :parent_id])
1166
+ # args: [:id, :parent_id])
1092
1167
  #
1093
1168
  # # WITH RECURSIVE t(id, parent_id) AS (
1094
1169
  # # SELECT id, parent_id FROM i1 WHERE (parent_id IS NULL)
1095
1170
  # # UNION ALL
1096
1171
  # # SELECT i1.id, i1.parent_id FROM i1 INNER JOIN t ON (t.id = i1.parent_id)
1097
1172
  # # ) SELECT * FROM t
1173
+ #
1174
+ # DB[:t].with_recursive(:t,
1175
+ # DB[:i1].where(parent_id: nil),
1176
+ # DB[:i1].join(:t, id: :parent_id).select_all(:i1),
1177
+ # search: {by: :id, type: :breadth},
1178
+ # cycle: {columns: :id, cycle_value: 1, noncycle_value: 2})
1179
+ #
1180
+ # # WITH RECURSIVE t AS (
1181
+ # # SELECT * FROM i1 WHERE (parent_id IS NULL)
1182
+ # # UNION ALL
1183
+ # # (SELECT i1.* FROM i1 INNER JOIN t ON (t.id = i1.parent_id))
1184
+ # # )
1185
+ # # SEARCH BREADTH FIRST BY id SET ordercol
1186
+ # # CYCLE id SET is_cycle TO 1 DEFAULT 2 USING path
1187
+ # # SELECT * FROM t
1098
1188
  def with_recursive(name, nonrecursive, recursive, opts=OPTS)
1099
1189
  raise(Error, 'This dataset does not support common table expressions') unless supports_cte?
1100
1190
  if hoist_cte?(nonrecursive)
@@ -1151,7 +1241,7 @@ module Sequel
1151
1241
  #
1152
1242
  # You can also provide a method name and arguments to call to get the SQL:
1153
1243
  #
1154
- # DB[:items].with_sql(:insert_sql, :b=>1) # INSERT INTO items (b) VALUES (1)
1244
+ # DB[:items].with_sql(:insert_sql, b: 1) # INSERT INTO items (b) VALUES (1)
1155
1245
  #
1156
1246
  # Note that datasets that specify custom SQL using this method will generally
1157
1247
  # ignore future dataset methods that modify the SQL used, as specifying custom SQL
@@ -1247,6 +1337,18 @@ module Sequel
1247
1337
  end
1248
1338
  end
1249
1339
 
1340
+ # Append to the current MERGE WHEN clauses.
1341
+ # Mutates the hash to add the conditions, if a virtual row block is passed.
1342
+ def _merge_when(hash, &block)
1343
+ hash[:conditions] = Sequel.virtual_row(&block) if block
1344
+
1345
+ if merge_when = @opts[:merge_when]
1346
+ clone(:merge_when => (merge_when.dup << hash.freeze).freeze)
1347
+ else
1348
+ clone(:merge_when => [hash.freeze].freeze)
1349
+ end
1350
+ end
1351
+
1250
1352
  # Add the given filter condition. Arguments:
1251
1353
  # clause :: Symbol or which SQL clause to effect, should be :where or :having
1252
1354
  # cond :: The filter condition to add
@@ -22,31 +22,9 @@ module Sequel
22
22
  def insert_sql(*values)
23
23
  return static_sql(@opts[:sql]) if @opts[:sql]
24
24
 
25
- check_modification_allowed!
26
-
27
- columns = []
28
-
29
- case values.size
30
- when 0
31
- return insert_sql(OPTS)
32
- when 1
33
- case vals = values[0]
34
- when Hash
35
- values = []
36
- vals.each do |k,v|
37
- columns << k
38
- values << v
39
- end
40
- when Dataset, Array, LiteralString
41
- values = vals
42
- end
43
- when 2
44
- if (v0 = values[0]).is_a?(Array) && ((v1 = values[1]).is_a?(Array) || v1.is_a?(Dataset) || v1.is_a?(LiteralString))
45
- columns, values = v0, v1
46
- raise(Error, "Different number of values and columns given to insert_sql") if values.is_a?(Array) and columns.length != values.length
47
- end
48
- end
25
+ check_insert_allowed!
49
26
 
27
+ columns, values = _parse_insert_sql_args(values)
50
28
  if values.is_a?(Array) && values.empty? && !insert_supports_empty_values?
51
29
  columns, values = insert_empty_columns_values
52
30
  elsif values.is_a?(Dataset) && hoist_cte?(values) && supports_cte?(:insert)
@@ -112,6 +90,31 @@ module Sequel
112
90
  end
113
91
  end
114
92
 
93
+ # The SQL to use for the MERGE statement.
94
+ def merge_sql
95
+ raise Error, "This database doesn't support MERGE" unless supports_merge?
96
+ if sql = opts[:sql]
97
+ return static_sql(sql)
98
+ end
99
+ if sql = cache_get(:_merge_sql)
100
+ return sql
101
+ end
102
+ source, join_condition = @opts[:merge_using]
103
+ raise Error, "No USING clause for MERGE" unless source
104
+ sql = @opts[:append_sql] || sql_string_origin
105
+
106
+ select_with_sql(sql)
107
+ sql << "MERGE INTO "
108
+ source_list_append(sql, @opts[:from])
109
+ sql << " USING "
110
+ identifier_append(sql, source)
111
+ sql << " ON "
112
+ literal_append(sql, join_condition)
113
+ _merge_when_sql(sql)
114
+ cache_set(:_merge_sql, sql) if cache_sql?
115
+ sql
116
+ end
117
+
115
118
  # Returns an array of insert statements for inserting multiple records.
116
119
  # This method is used by +multi_insert+ to format insert statements and
117
120
  # expects a keys array and and an array of value arrays.
@@ -172,7 +175,7 @@ module Sequel
172
175
  # than one table.
173
176
  def update_sql(values = OPTS)
174
177
  return static_sql(opts[:sql]) if opts[:sql]
175
- check_modification_allowed!
178
+ check_update_allowed!
176
179
  check_not_limited!(:update)
177
180
 
178
181
  case values
@@ -215,7 +218,7 @@ module Sequel
215
218
  lines << "def #{'_' if priv}#{type}_sql"
216
219
  lines << 'if sql = opts[:sql]; return static_sql(sql) end' unless priv
217
220
  lines << "if sql = cache_get(:_#{type}_sql); return sql end" if cacheable
218
- lines << 'check_modification_allowed!' << 'check_not_limited!(:delete)' if type == :delete
221
+ lines << 'check_delete_allowed!' << 'check_not_limited!(:delete)' if type == :delete
219
222
  lines << 'sql = @opts[:append_sql] || sql_string_origin'
220
223
 
221
224
  if clauses.all?{|c| c.is_a?(Array)}
@@ -559,11 +562,9 @@ module Sequel
559
562
  # Append literalization of JOIN USING clause to SQL string.
560
563
  def join_using_clause_sql_append(sql, jc)
561
564
  join_clause_sql_append(sql, jc)
562
- sql << ' USING ('
563
- column_list_append(sql, jc.using)
564
- sql << ')'
565
+ join_using_clause_using_sql_append(sql, jc.using)
565
566
  end
566
-
567
+
567
568
  # Append literalization of negative boolean constant to SQL string.
568
569
  def negative_boolean_constant_sql_append(sql, constant)
569
570
  sql << 'NOT '
@@ -852,6 +853,83 @@ module Sequel
852
853
 
853
854
  private
854
855
 
856
+ # Append the INSERT sql used in a MERGE
857
+ def _merge_insert_sql(sql, data)
858
+ sql << " THEN INSERT"
859
+ columns, values = _parse_insert_sql_args(data[:values])
860
+ _insert_columns_sql(sql, columns)
861
+ _insert_values_sql(sql, values)
862
+ end
863
+
864
+ def _merge_update_sql(sql, data)
865
+ sql << " THEN UPDATE SET "
866
+ update_sql_values_hash(sql, data[:values])
867
+ end
868
+
869
+ def _merge_delete_sql(sql, data)
870
+ sql << " THEN DELETE"
871
+ end
872
+
873
+ # Mapping of merge types to related SQL
874
+ MERGE_TYPE_SQL = {
875
+ :insert => ' WHEN NOT MATCHED',
876
+ :delete => ' WHEN MATCHED',
877
+ :update => ' WHEN MATCHED',
878
+ :matched => ' WHEN MATCHED',
879
+ :not_matched => ' WHEN NOT MATCHED',
880
+ }.freeze
881
+ private_constant :MERGE_TYPE_SQL
882
+
883
+ # Add the WHEN clauses to the MERGE SQL
884
+ def _merge_when_sql(sql)
885
+ raise Error, "no WHEN [NOT] MATCHED clauses provided for MERGE" unless merge_when = @opts[:merge_when]
886
+ merge_when.each do |data|
887
+ type = data[:type]
888
+ sql << MERGE_TYPE_SQL[type]
889
+ _merge_when_conditions_sql(sql, data)
890
+ send(:"_merge_#{type}_sql", sql, data)
891
+ end
892
+ end
893
+
894
+ # Append MERGE WHEN conditions, if there are conditions provided.
895
+ def _merge_when_conditions_sql(sql, data)
896
+ if data.has_key?(:conditions)
897
+ sql << " AND "
898
+ literal_append(sql, data[:conditions])
899
+ end
900
+ end
901
+
902
+ # Parse the values passed to insert_sql, returning columns and values
903
+ # to use for the INSERT. Returned columns is always an array, but can be empty
904
+ # for an INSERT without explicit column references. Returned values can be an
905
+ # array, dataset, or literal string.
906
+ def _parse_insert_sql_args(values)
907
+ columns = []
908
+
909
+ case values.size
910
+ when 0
911
+ values = []
912
+ when 1
913
+ case vals = values[0]
914
+ when Hash
915
+ values = []
916
+ vals.each do |k,v|
917
+ columns << k
918
+ values << v
919
+ end
920
+ when Dataset, Array, LiteralString
921
+ values = vals
922
+ end
923
+ when 2
924
+ if (v0 = values[0]).is_a?(Array) && ((v1 = values[1]).is_a?(Array) || v1.is_a?(Dataset) || v1.is_a?(LiteralString))
925
+ columns, values = v0, v1
926
+ raise(Error, "Different number of values and columns given to insert_sql") if values.is_a?(Array) and columns.length != values.length
927
+ end
928
+ end
929
+
930
+ [columns, values]
931
+ end
932
+
855
933
  # Formats the truncate statement. Assumes the table given has already been
856
934
  # literalized.
857
935
  def _truncate_sql(table)
@@ -896,9 +974,10 @@ module Sequel
896
974
  # Clone of this dataset usable in aggregate operations. Does
897
975
  # a from_self if dataset contains any parameters that would
898
976
  # affect normal aggregation, or just removes an existing
899
- # order if not.
977
+ # order if not. Also removes the row_proc, which isn't needed
978
+ # for aggregate calculations.
900
979
  def aggregate_dataset
901
- options_overlap(COUNT_FROM_SELF_OPTS) ? from_self : unordered
980
+ (options_overlap(COUNT_FROM_SELF_OPTS) ? from_self : unordered).naked
902
981
  end
903
982
 
904
983
  # Append aliasing expression to SQL string.
@@ -918,10 +997,35 @@ module Sequel
918
997
  !@opts[:no_cache_sql] && !cache_get(:_no_cache_sql)
919
998
  end
920
999
 
921
- # Raise an InvalidOperation exception if deletion is not allowed for this dataset.
1000
+ # Raise an InvalidOperation exception if modification is not allowed for this dataset.
1001
+ # Check whether it is allowed to insert into this dataset.
1002
+ # Only for backwards compatibility with older external adapters.
922
1003
  def check_modification_allowed!
1004
+ # SEQUEL6: Remove
1005
+ Sequel::Deprecation.deprecate("Dataset#check_modification_allowed!", "Use check_{insert,delete,update,truncation}_allowed! instead")
1006
+ _check_modification_allowed!(supports_modifying_joins?)
1007
+ end
1008
+
1009
+ # Check whether it is allowed to insert into this dataset.
1010
+ def check_insert_allowed!
1011
+ _check_modification_allowed!(false)
1012
+ end
1013
+ alias check_truncation_allowed! check_insert_allowed!
1014
+
1015
+ # Check whether it is allowed to delete from this dataset.
1016
+ def check_delete_allowed!
1017
+ _check_modification_allowed!(supports_deleting_joins?)
1018
+ end
1019
+
1020
+ # Check whether it is allowed to update this dataset.
1021
+ def check_update_allowed!
1022
+ _check_modification_allowed!(supports_updating_joins?)
1023
+ end
1024
+
1025
+ # Internals of the check_*_allowed! methods
1026
+ def _check_modification_allowed!(modifying_joins_supported)
923
1027
  raise(InvalidOperation, "Grouped datasets cannot be modified") if opts[:group]
924
- raise(InvalidOperation, "Joined datasets cannot be modified") if !supports_modifying_joins? && joined_dataset?
1028
+ raise(InvalidOperation, "Joined datasets cannot be modified") if !modifying_joins_supported && joined_dataset?
925
1029
  end
926
1030
 
927
1031
  # Raise error if the dataset uses limits or offsets.
@@ -930,11 +1034,6 @@ module Sequel
930
1034
  raise InvalidOperation, "Dataset##{type} not supported on datasets with limits or offsets" if opts[:limit] || opts[:offset]
931
1035
  end
932
1036
 
933
- # Alias of check_modification_allowed!
934
- def check_truncation_allowed!
935
- check_modification_allowed!
936
- end
937
-
938
1037
  # Append column list to SQL string.
939
1038
  # If the column list is empty, a wildcard (*) is appended.
940
1039
  def column_list_append(sql, columns)
@@ -971,7 +1070,9 @@ module Sequel
971
1070
  # operators unsupported by some databases. Used by adapters for databases
972
1071
  # that don't support the operators natively.
973
1072
  def complex_expression_emulate_append(sql, op, args)
1073
+ # :nocov:
974
1074
  case op
1075
+ # :nocov:
975
1076
  when :%
976
1077
  complex_expression_arg_pairs_append(sql, args){|a, b| Sequel.function(:MOD, a, b)}
977
1078
  when :>>
@@ -1144,7 +1245,10 @@ module Sequel
1144
1245
  end
1145
1246
 
1146
1247
  def insert_columns_sql(sql)
1147
- columns = opts[:columns]
1248
+ _insert_columns_sql(sql, opts[:columns])
1249
+ end
1250
+
1251
+ def _insert_columns_sql(sql, columns)
1148
1252
  if columns && !columns.empty?
1149
1253
  sql << ' ('
1150
1254
  identifier_list_append(sql, columns)
@@ -1163,7 +1267,11 @@ module Sequel
1163
1267
  end
1164
1268
 
1165
1269
  def insert_values_sql(sql)
1166
- case values = opts[:values]
1270
+ _insert_values_sql(sql, opts[:values])
1271
+ end
1272
+
1273
+ def _insert_values_sql(sql, values)
1274
+ case values
1167
1275
  when Array
1168
1276
  if values.empty?
1169
1277
  sql << " DEFAULT VALUES"
@@ -1196,6 +1304,13 @@ module Sequel
1196
1304
  "#{join_type.to_s.gsub('_', ' ').upcase} JOIN"
1197
1305
  end
1198
1306
 
1307
+ # Append USING clause for JOIN USING
1308
+ def join_using_clause_using_sql_append(sql, using_columns)
1309
+ sql << ' USING ('
1310
+ column_list_append(sql, using_columns)
1311
+ sql << ')'
1312
+ end
1313
+
1199
1314
  # Append a literalization of the array to SQL string.
1200
1315
  # Treats as an expression if an array of all two pairs, or as a SQL array otherwise.
1201
1316
  def literal_array_append(sql, v)
@@ -1516,15 +1631,14 @@ module Sequel
1516
1631
 
1517
1632
  def select_with_sql(sql)
1518
1633
  return unless supports_cte?
1519
- ws = opts[:with]
1520
- return if !ws || ws.empty?
1634
+ ctes = opts[:with]
1635
+ return if !ctes || ctes.empty?
1521
1636
  sql << select_with_sql_base
1522
1637
  c = false
1523
1638
  comma = ', '
1524
- ws.each do |w|
1639
+ ctes.each do |cte|
1525
1640
  sql << comma if c
1526
- select_with_sql_prefix(sql, w)
1527
- literal_dataset_append(sql, w[:dataset])
1641
+ select_with_sql_cte(sql, cte)
1528
1642
  c ||= true
1529
1643
  end
1530
1644
  sql << ' '
@@ -1537,6 +1651,11 @@ module Sequel
1537
1651
  "WITH "
1538
1652
  end
1539
1653
 
1654
+ def select_with_sql_cte(sql, cte)
1655
+ select_with_sql_prefix(sql, cte)
1656
+ literal_dataset_append(sql, cte[:dataset])
1657
+ end
1658
+
1540
1659
  def select_with_sql_prefix(sql, w)
1541
1660
  quote_identifier_append(sql, w[:name])
1542
1661
  if args = w[:args]
@@ -1545,6 +1664,13 @@ module Sequel
1545
1664
  sql << ')'
1546
1665
  end
1547
1666
  sql << ' AS '
1667
+
1668
+ case w[:materialized]
1669
+ when true
1670
+ sql << "MATERIALIZED "
1671
+ when false
1672
+ sql << "NOT MATERIALIZED "
1673
+ end
1548
1674
  end
1549
1675
 
1550
1676
  # Whether the symbol cache should be skipped when literalizing the dataset
@@ -1599,7 +1725,7 @@ module Sequel
1599
1725
  # Append literalization of the subselect to SQL string.
1600
1726
  def subselect_sql_append(sql, ds)
1601
1727
  sds = subselect_sql_dataset(sql, ds)
1602
- sds.sql
1728
+ subselect_sql_append_sql(sql, sds)
1603
1729
  unless sds.send(:cache_sql?)
1604
1730
  # If subquery dataset does not allow caching SQL,
1605
1731
  # then this dataset should not allow caching SQL.
@@ -1611,6 +1737,10 @@ module Sequel
1611
1737
  ds.clone(:append_sql=>sql)
1612
1738
  end
1613
1739
 
1740
+ def subselect_sql_append_sql(sql, ds)
1741
+ ds.sql
1742
+ end
1743
+
1614
1744
  # The number of decimal digits of precision to use in timestamps.
1615
1745
  def timestamp_precision
1616
1746
  supports_timestamp_usecs? ? 6 : 0
@@ -23,18 +23,6 @@ module Sequel
23
23
  super
24
24
  end
25
25
  end
26
-
27
- private
28
-
29
- # Handle Sequel::Model instances in bound variable arrays.
30
- def bound_variable_array(arg)
31
- case arg
32
- when Sequel::Model
33
- "\"(#{arg.values.values_at(*arg.columns).map{|v| bound_variable_array(v)}.join(',').gsub(/("|\\)/, '\\\\\1')})\""
34
- else
35
- super
36
- end
37
- end
38
26
  end
39
27
  end
40
28
  end
@@ -39,7 +39,7 @@ module Sequel
39
39
 
40
40
  # Hash of the maximum size of the value for each column
41
41
  def self.column_sizes(records, columns) # :nodoc:
42
- sizes = Hash.new {0}
42
+ sizes = Hash.new(0)
43
43
  columns.each do |c|
44
44
  sizes[c] = c.to_s.size
45
45
  end
@@ -33,7 +33,7 @@ module Sequel
33
33
  module AnyNotEmpty
34
34
  # If a block is not given, return whether the dataset is not empty.
35
35
  def any?
36
- if block_given?
36
+ if defined?(yield)
37
37
  super
38
38
  else
39
39
  !empty?