sequel 5.39.0 → 5.63.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 (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?