sequel 5.45.0 → 5.77.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 (218) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +434 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +59 -27
  5. data/bin/sequel +11 -3
  6. data/doc/advanced_associations.rdoc +16 -14
  7. data/doc/association_basics.rdoc +119 -24
  8. data/doc/cheat_sheet.rdoc +11 -3
  9. data/doc/mass_assignment.rdoc +1 -1
  10. data/doc/migration.rdoc +27 -6
  11. data/doc/model_hooks.rdoc +1 -1
  12. data/doc/object_model.rdoc +8 -8
  13. data/doc/opening_databases.rdoc +28 -12
  14. data/doc/postgresql.rdoc +16 -8
  15. data/doc/querying.rdoc +5 -3
  16. data/doc/release_notes/5.46.0.txt +87 -0
  17. data/doc/release_notes/5.47.0.txt +59 -0
  18. data/doc/release_notes/5.48.0.txt +14 -0
  19. data/doc/release_notes/5.49.0.txt +59 -0
  20. data/doc/release_notes/5.50.0.txt +78 -0
  21. data/doc/release_notes/5.51.0.txt +47 -0
  22. data/doc/release_notes/5.52.0.txt +87 -0
  23. data/doc/release_notes/5.53.0.txt +23 -0
  24. data/doc/release_notes/5.54.0.txt +27 -0
  25. data/doc/release_notes/5.55.0.txt +21 -0
  26. data/doc/release_notes/5.56.0.txt +51 -0
  27. data/doc/release_notes/5.57.0.txt +23 -0
  28. data/doc/release_notes/5.58.0.txt +31 -0
  29. data/doc/release_notes/5.59.0.txt +73 -0
  30. data/doc/release_notes/5.60.0.txt +22 -0
  31. data/doc/release_notes/5.61.0.txt +43 -0
  32. data/doc/release_notes/5.62.0.txt +132 -0
  33. data/doc/release_notes/5.63.0.txt +33 -0
  34. data/doc/release_notes/5.64.0.txt +50 -0
  35. data/doc/release_notes/5.65.0.txt +21 -0
  36. data/doc/release_notes/5.66.0.txt +24 -0
  37. data/doc/release_notes/5.67.0.txt +32 -0
  38. data/doc/release_notes/5.68.0.txt +61 -0
  39. data/doc/release_notes/5.69.0.txt +26 -0
  40. data/doc/release_notes/5.70.0.txt +35 -0
  41. data/doc/release_notes/5.71.0.txt +21 -0
  42. data/doc/release_notes/5.72.0.txt +33 -0
  43. data/doc/release_notes/5.73.0.txt +66 -0
  44. data/doc/release_notes/5.74.0.txt +45 -0
  45. data/doc/release_notes/5.75.0.txt +35 -0
  46. data/doc/release_notes/5.76.0.txt +86 -0
  47. data/doc/release_notes/5.77.0.txt +63 -0
  48. data/doc/schema_modification.rdoc +1 -1
  49. data/doc/security.rdoc +9 -9
  50. data/doc/sharding.rdoc +3 -1
  51. data/doc/sql.rdoc +27 -15
  52. data/doc/testing.rdoc +23 -13
  53. data/doc/transactions.rdoc +6 -6
  54. data/doc/virtual_rows.rdoc +1 -1
  55. data/lib/sequel/adapters/ado/access.rb +1 -1
  56. data/lib/sequel/adapters/ado.rb +1 -1
  57. data/lib/sequel/adapters/amalgalite.rb +3 -5
  58. data/lib/sequel/adapters/ibmdb.rb +3 -3
  59. data/lib/sequel/adapters/jdbc/derby.rb +8 -0
  60. data/lib/sequel/adapters/jdbc/h2.rb +63 -10
  61. data/lib/sequel/adapters/jdbc/hsqldb.rb +8 -0
  62. data/lib/sequel/adapters/jdbc/postgresql.rb +7 -4
  63. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +15 -0
  64. data/lib/sequel/adapters/jdbc/sqlserver.rb +4 -0
  65. data/lib/sequel/adapters/jdbc.rb +24 -22
  66. data/lib/sequel/adapters/mysql.rb +92 -67
  67. data/lib/sequel/adapters/mysql2.rb +56 -51
  68. data/lib/sequel/adapters/odbc/mssql.rb +1 -1
  69. data/lib/sequel/adapters/odbc.rb +1 -1
  70. data/lib/sequel/adapters/oracle.rb +4 -3
  71. data/lib/sequel/adapters/postgres.rb +89 -45
  72. data/lib/sequel/adapters/shared/access.rb +11 -1
  73. data/lib/sequel/adapters/shared/db2.rb +42 -0
  74. data/lib/sequel/adapters/shared/mssql.rb +91 -10
  75. data/lib/sequel/adapters/shared/mysql.rb +78 -3
  76. data/lib/sequel/adapters/shared/oracle.rb +86 -7
  77. data/lib/sequel/adapters/shared/postgres.rb +576 -171
  78. data/lib/sequel/adapters/shared/sqlanywhere.rb +21 -5
  79. data/lib/sequel/adapters/shared/sqlite.rb +92 -8
  80. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  81. data/lib/sequel/adapters/sqlite.rb +99 -18
  82. data/lib/sequel/adapters/tinytds.rb +1 -1
  83. data/lib/sequel/adapters/trilogy.rb +117 -0
  84. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  85. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  86. data/lib/sequel/ast_transformer.rb +6 -0
  87. data/lib/sequel/connection_pool/sharded_single.rb +5 -7
  88. data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
  89. data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
  90. data/lib/sequel/connection_pool/single.rb +6 -8
  91. data/lib/sequel/connection_pool/threaded.rb +14 -8
  92. data/lib/sequel/connection_pool/timed_queue.rb +270 -0
  93. data/lib/sequel/connection_pool.rb +57 -31
  94. data/lib/sequel/core.rb +17 -18
  95. data/lib/sequel/database/connecting.rb +27 -3
  96. data/lib/sequel/database/dataset.rb +16 -6
  97. data/lib/sequel/database/misc.rb +70 -14
  98. data/lib/sequel/database/query.rb +73 -2
  99. data/lib/sequel/database/schema_generator.rb +11 -6
  100. data/lib/sequel/database/schema_methods.rb +23 -4
  101. data/lib/sequel/database/transactions.rb +6 -0
  102. data/lib/sequel/dataset/actions.rb +111 -15
  103. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
  104. data/lib/sequel/dataset/features.rb +20 -1
  105. data/lib/sequel/dataset/misc.rb +12 -2
  106. data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
  107. data/lib/sequel/dataset/query.rb +170 -41
  108. data/lib/sequel/dataset/sql.rb +190 -71
  109. data/lib/sequel/dataset.rb +4 -0
  110. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  111. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  112. data/lib/sequel/extensions/any_not_empty.rb +2 -2
  113. data/lib/sequel/extensions/async_thread_pool.rb +14 -13
  114. data/lib/sequel/extensions/auto_cast_date_and_time.rb +94 -0
  115. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  116. data/lib/sequel/extensions/connection_expiration.rb +15 -9
  117. data/lib/sequel/extensions/connection_validator.rb +16 -11
  118. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  119. data/lib/sequel/extensions/core_refinements.rb +36 -11
  120. data/lib/sequel/extensions/date_arithmetic.rb +36 -8
  121. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  122. data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
  123. data/lib/sequel/extensions/duplicate_columns_handler.rb +11 -10
  124. data/lib/sequel/extensions/index_caching.rb +5 -1
  125. data/lib/sequel/extensions/inflector.rb +1 -1
  126. data/lib/sequel/extensions/is_distinct_from.rb +141 -0
  127. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  128. data/lib/sequel/extensions/migration.rb +57 -15
  129. data/lib/sequel/extensions/named_timezones.rb +22 -6
  130. data/lib/sequel/extensions/pagination.rb +1 -1
  131. data/lib/sequel/extensions/pg_array.rb +33 -4
  132. data/lib/sequel/extensions/pg_array_ops.rb +2 -2
  133. data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
  134. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
  135. data/lib/sequel/extensions/pg_enum.rb +1 -2
  136. data/lib/sequel/extensions/pg_extended_date_support.rb +39 -28
  137. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  138. data/lib/sequel/extensions/pg_hstore.rb +6 -1
  139. data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
  140. data/lib/sequel/extensions/pg_inet.rb +10 -11
  141. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  142. data/lib/sequel/extensions/pg_interval.rb +11 -11
  143. data/lib/sequel/extensions/pg_json.rb +13 -15
  144. data/lib/sequel/extensions/pg_json_ops.rb +125 -2
  145. data/lib/sequel/extensions/pg_multirange.rb +367 -0
  146. data/lib/sequel/extensions/pg_range.rb +13 -26
  147. data/lib/sequel/extensions/pg_range_ops.rb +37 -9
  148. data/lib/sequel/extensions/pg_row.rb +20 -19
  149. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  150. data/lib/sequel/extensions/pg_timestamptz.rb +27 -3
  151. data/lib/sequel/extensions/round_timestamps.rb +1 -1
  152. data/lib/sequel/extensions/s.rb +2 -1
  153. data/lib/sequel/extensions/schema_caching.rb +1 -1
  154. data/lib/sequel/extensions/schema_dumper.rb +45 -11
  155. data/lib/sequel/extensions/server_block.rb +10 -13
  156. data/lib/sequel/extensions/set_literalizer.rb +58 -0
  157. data/lib/sequel/extensions/sql_comments.rb +110 -3
  158. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  159. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  160. data/lib/sequel/extensions/string_agg.rb +1 -1
  161. data/lib/sequel/extensions/string_date_time.rb +19 -23
  162. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  163. data/lib/sequel/extensions/transaction_connection_validator.rb +78 -0
  164. data/lib/sequel/model/associations.rb +286 -92
  165. data/lib/sequel/model/base.rb +53 -33
  166. data/lib/sequel/model/dataset_module.rb +3 -0
  167. data/lib/sequel/model/errors.rb +10 -1
  168. data/lib/sequel/model/exceptions.rb +15 -3
  169. data/lib/sequel/model/inflections.rb +1 -1
  170. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  171. data/lib/sequel/plugins/auto_validations.rb +74 -16
  172. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  173. data/lib/sequel/plugins/column_encryption.rb +29 -8
  174. data/lib/sequel/plugins/composition.rb +3 -2
  175. data/lib/sequel/plugins/concurrent_eager_loading.rb +4 -4
  176. data/lib/sequel/plugins/constraint_validations.rb +8 -5
  177. data/lib/sequel/plugins/defaults_setter.rb +16 -0
  178. data/lib/sequel/plugins/dirty.rb +1 -1
  179. data/lib/sequel/plugins/enum.rb +124 -0
  180. data/lib/sequel/plugins/finder.rb +4 -2
  181. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  182. data/lib/sequel/plugins/instance_specific_default.rb +1 -1
  183. data/lib/sequel/plugins/json_serializer.rb +2 -2
  184. data/lib/sequel/plugins/lazy_attributes.rb +3 -0
  185. data/lib/sequel/plugins/list.rb +8 -3
  186. data/lib/sequel/plugins/many_through_many.rb +109 -10
  187. data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
  188. data/lib/sequel/plugins/nested_attributes.rb +4 -4
  189. data/lib/sequel/plugins/optimistic_locking.rb +9 -42
  190. data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
  191. data/lib/sequel/plugins/paged_operations.rb +181 -0
  192. data/lib/sequel/plugins/pg_array_associations.rb +46 -34
  193. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +9 -3
  194. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
  195. data/lib/sequel/plugins/prepared_statements.rb +12 -2
  196. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
  197. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  198. data/lib/sequel/plugins/rcte_tree.rb +7 -4
  199. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  200. data/lib/sequel/plugins/serialization.rb +1 -0
  201. data/lib/sequel/plugins/serialization_modification_detection.rb +1 -0
  202. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  203. data/lib/sequel/plugins/sql_comments.rb +189 -0
  204. data/lib/sequel/plugins/static_cache.rb +39 -1
  205. data/lib/sequel/plugins/static_cache_cache.rb +5 -1
  206. data/lib/sequel/plugins/subclasses.rb +28 -11
  207. data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
  208. data/lib/sequel/plugins/timestamps.rb +1 -1
  209. data/lib/sequel/plugins/unused_associations.rb +521 -0
  210. data/lib/sequel/plugins/update_or_create.rb +1 -1
  211. data/lib/sequel/plugins/validate_associated.rb +22 -12
  212. data/lib/sequel/plugins/validation_helpers.rb +41 -11
  213. data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
  214. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  215. data/lib/sequel/sql.rb +1 -1
  216. data/lib/sequel/timezones.rb +12 -14
  217. data/lib/sequel/version.rb +1 -1
  218. metadata +109 -19
@@ -12,64 +12,31 @@ module Sequel
12
12
  # p1 = Person[1]
13
13
  # p2 = Person[1]
14
14
  # p1.update(name: 'Jim') # works
15
- # p2.update(name: 'Bob') # raises Sequel::Plugins::OptimisticLocking::Error
15
+ # p2.update(name: 'Bob') # raises Sequel::NoExistingObject
16
16
  #
17
17
  # In order for this plugin to work, you need to make sure that the database
18
- # table has a +lock_version+ column (or other column you name via the lock_column
19
- # class level accessor) that defaults to 0.
18
+ # table has a +lock_version+ column that defaults to 0. To change the column
19
+ # used, provide a +:lock_column+ option when loading the plugin:
20
+ #
21
+ # plugin :optimistic_locking, lock_column: :version
20
22
  #
21
23
  # This plugin relies on the instance_filters plugin.
22
24
  module OptimisticLocking
23
25
  # Exception class raised when trying to update or destroy a stale object.
24
26
  Error = Sequel::NoExistingObject
25
27
 
26
- # Load the instance_filters plugin into the model.
27
28
  def self.apply(model, opts=OPTS)
28
- model.plugin :instance_filters
29
+ model.plugin(:optimistic_locking_base)
29
30
  end
30
31
 
31
- # Set the lock_column to the :lock_column option, or :lock_version if
32
- # that option is not given.
32
+ # Set the lock column
33
33
  def self.configure(model, opts=OPTS)
34
- model.lock_column = opts[:lock_column] || :lock_version
34
+ model.lock_column = opts[:lock_column] || model.lock_column || :lock_version
35
35
  end
36
-
37
- module ClassMethods
38
- # The column holding the version of the lock
39
- attr_accessor :lock_column
40
-
41
- Plugins.inherited_instance_variables(self, :@lock_column=>nil)
42
- end
43
-
36
+
44
37
  module InstanceMethods
45
- # Add the lock column instance filter to the object before destroying it.
46
- def before_destroy
47
- lock_column_instance_filter
48
- super
49
- end
50
-
51
- # Add the lock column instance filter to the object before updating it.
52
- def before_update
53
- lock_column_instance_filter
54
- super
55
- end
56
-
57
38
  private
58
39
 
59
- # Add the lock column instance filter to the object.
60
- def lock_column_instance_filter
61
- lc = model.lock_column
62
- instance_filter(lc=>get_column_value(lc))
63
- end
64
-
65
- # Clear the instance filters when refreshing, so that attempting to
66
- # refresh after a failed save removes the previous lock column filter
67
- # (the new one will be added before updating).
68
- def _refresh(ds)
69
- clear_instance_filters
70
- super
71
- end
72
-
73
40
  # Only update the row if it has the same lock version, and increment the
74
41
  # lock version.
75
42
  def _update_columns(columns)
@@ -0,0 +1,55 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # Base for other optimistic locking plugins
6
+ module OptimisticLockingBase
7
+ # Load the instance_filters plugin into the model.
8
+ def self.apply(model)
9
+ model.plugin :instance_filters
10
+ end
11
+
12
+ module ClassMethods
13
+ # The column holding the version of the lock
14
+ attr_accessor :lock_column
15
+
16
+ Plugins.inherited_instance_variables(self, :@lock_column=>nil)
17
+ end
18
+
19
+ module InstanceMethods
20
+ # Add the lock column instance filter to the object before destroying it.
21
+ def before_destroy
22
+ lock_column_instance_filter
23
+ super
24
+ end
25
+
26
+ # Add the lock column instance filter to the object before updating it.
27
+ def before_update
28
+ lock_column_instance_filter
29
+ super
30
+ end
31
+
32
+ private
33
+
34
+ # Add the lock column instance filter to the object.
35
+ def lock_column_instance_filter
36
+ instance_filter(model.lock_column=>lock_column_instance_filter_value)
37
+ end
38
+
39
+ # Use the current value of the lock column
40
+ def lock_column_instance_filter_value
41
+ public_send(model.lock_column)
42
+ end
43
+
44
+ # Clear the instance filters when refreshing, so that attempting to
45
+ # refresh after a failed save removes the previous lock column filter
46
+ # (the new one will be added before updating).
47
+ def _refresh(ds)
48
+ clear_instance_filters
49
+ super
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
@@ -0,0 +1,181 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # The paged_operations plugin adds +paged_update+ and
6
+ # +paged_delete+ dataset methods. These behave similarly to
7
+ # the default +update+ and +delete+ dataset methods, except
8
+ # that the update or deletion is done in potentially multiple
9
+ # queries (by default, affecting 1000 rows per query).
10
+ # For a large table, this prevents the change from
11
+ # locking the table for a long period of time.
12
+ #
13
+ # Because the point of this is to prevent locking tables for
14
+ # long periods of time, the separate queries are not contained
15
+ # in a transaction, which means if a later query fails,
16
+ # earlier queries will still be committed. You could prevent
17
+ # this by using a transaction manually, but that defeats the
18
+ # purpose of using these methods.
19
+ #
20
+ # Examples:
21
+ #
22
+ # Album.where{name <= 'M'}.paged_update(updated_at: Sequel::CURRENT_TIMESTAMP)
23
+ # # SELECT id FROM albums WHERE (name <= 'M') ORDER BY id LIMIT 1 OFFSET 1000
24
+ # # UPDATE albums SET updated_at = CURRENT_TIMESTAMP WHERE ((name <= 'M') AND ("id" < 1002))
25
+ # # SELECT id FROM albums WHERE ((name <= 'M') AND (id >= 1002)) ORDER BY id LIMIT 1 OFFSET 1000
26
+ # # UPDATE albums SET updated_at = CURRENT_TIMESTAMP WHERE ((name <= 'M') AND ("id" < 2002) AND (id >= 1002))
27
+ # # ...
28
+ # # SELECT id FROM albums WHERE ((name <= 'M') AND (id >= 10002)) ORDER BY id LIMIT 1 OFFSET 1000
29
+ # # UPDATE albums SET updated_at = CURRENT_TIMESTAMP WHERE ((name <= 'M') AND (id >= 10002))
30
+ #
31
+ # Album.where{name > 'M'}.paged_delete
32
+ # # SELECT id FROM albums WHERE (name > 'M') ORDER BY id LIMIT 1 OFFSET 1000
33
+ # # DELETE FROM albums WHERE ((name > 'M') AND (id < 1002))
34
+ # # SELECT id FROM albums WHERE (name > 'M') ORDER BY id LIMIT 1 OFFSET 1000
35
+ # # DELETE FROM albums WHERE ((name > 'M') AND (id < 2002))
36
+ # # ...
37
+ # # SELECT id FROM albums WHERE (name > 'M') ORDER BY id LIMIT 1 OFFSET 1000
38
+ # # DELETE FROM albums WHERE (name > 'M')
39
+ #
40
+ # The plugin also adds a +paged_datasets+ method that will yield
41
+ # separate datasets limited in size that in total handle all
42
+ # rows in the receiver:
43
+ #
44
+ # Album.where{name > 'M'}.paged_datasets{|ds| puts ds.sql}
45
+ # # Runs: SELECT id FROM albums WHERE (name <= 'M') ORDER BY id LIMIT 1 OFFSET 1000
46
+ # # Prints: SELECT * FROM albums WHERE ((name <= 'M') AND ("id" < 1002))
47
+ # # Runs: SELECT id FROM albums WHERE ((name <= 'M') AND (id >= 1002)) ORDER BY id LIMIT 1 OFFSET 1000
48
+ # # Prints: SELECT * FROM albums WHERE ((name <= 'M') AND ("id" < 2002) AND (id >= 1002))
49
+ # # ...
50
+ # # Runs: SELECT id FROM albums WHERE ((name <= 'M') AND (id >= 10002)) ORDER BY id LIMIT 1 OFFSET 1000
51
+ # # Prints: SELECT * FROM albums WHERE ((name <= 'M') AND (id >= 10002))
52
+ #
53
+ # To set the number of rows per page, pass a :rows_per_page option:
54
+ #
55
+ # Album.where{name <= 'M'}.paged_update({x: Sequel[:x] + 1}, rows_per_page: 4)
56
+ # # SELECT id FROM albums WHERE (name <= 'M') ORDER BY id LIMIT 1 OFFSET 4
57
+ # # UPDATE albums SET x = x + 1 WHERE ((name <= 'M') AND ("id" < 5))
58
+ # # SELECT id FROM albums WHERE ((name <= 'M') AND (id >= 5)) ORDER BY id LIMIT 1 OFFSET 4
59
+ # # UPDATE albums SET x = x + 1 WHERE ((name <= 'M') AND ("id" < 9) AND (id >= 5))
60
+ # # ...
61
+ # # SELECT id FROM albums WHERE ((name <= 'M') AND (id >= 12345)) ORDER BY id LIMIT 1 OFFSET 4
62
+ # # UPDATE albums SET x = x + 1 WHERE ((name <= 'M') AND (id >= 12345))
63
+ #
64
+ # You should avoid using +paged_update+ or +paged_datasets+
65
+ # with updates that modify the primary key, as such usage is
66
+ # not supported by this plugin.
67
+ #
68
+ # This plugin only supports models with scalar primary keys.
69
+ #
70
+ # Usage:
71
+ #
72
+ # # Make all model subclasses support paged update/delete/datasets
73
+ # # (called before loading subclasses)
74
+ # Sequel::Model.plugin :paged_operations
75
+ #
76
+ # # Make the Album class support paged update/delete/datasets
77
+ # Album.plugin :paged_operations
78
+ module PagedOperations
79
+ module ClassMethods
80
+ Plugins.def_dataset_methods(self, [:paged_datasets, :paged_delete, :paged_update])
81
+ end
82
+
83
+ module DatasetMethods
84
+ # Yield datasets for subsets of the receiver that are limited
85
+ # to no more than 1000 rows (you can configure the number of
86
+ # rows using +:rows_per_page+).
87
+ #
88
+ # Options:
89
+ # :rows_per_page :: The maximum number of rows in each yielded dataset
90
+ # (unless concurrent modifications are made to the table).
91
+ def paged_datasets(opts=OPTS)
92
+ unless defined?(yield)
93
+ return enum_for(:paged_datasets, opts)
94
+ end
95
+
96
+ pk = _paged_operations_pk(:paged_update)
97
+ base_offset_ds = offset_ds = _paged_operations_offset_ds(opts)
98
+ first = nil
99
+
100
+ while last = offset_ds.get(pk)
101
+ ds = where(pk < last)
102
+ ds = ds.where(pk >= first) if first
103
+ yield ds
104
+ first = last
105
+ offset_ds = base_offset_ds.where(pk >= first)
106
+ end
107
+
108
+ ds = self
109
+ ds = ds.where(pk >= first) if first
110
+ yield ds
111
+ nil
112
+ end
113
+
114
+ # Delete all rows of the dataset using using multiple queries so that
115
+ # no more than 1000 rows are deleted at a time (you can configure the
116
+ # number of rows using +:rows_per_page+).
117
+ #
118
+ # Options:
119
+ # :rows_per_page :: The maximum number of rows affected by each DELETE query
120
+ # (unless concurrent modifications are made to the table).
121
+ def paged_delete(opts=OPTS)
122
+ if (db.database_type == :oracle && !supports_fetch_next_rows?) || (db.database_type == :mssql && !is_2012_or_later?)
123
+ raise Error, "paged_delete is not supported on MSSQL/Oracle when using emulated offsets"
124
+ end
125
+ pk = _paged_operations_pk(:paged_delete)
126
+ rows_deleted = 0
127
+ offset_ds = _paged_operations_offset_ds(opts)
128
+ while last = offset_ds.get(pk)
129
+ rows_deleted += where(pk < last).delete
130
+ end
131
+ rows_deleted + delete
132
+ end
133
+
134
+ # Update all rows of the dataset using using multiple queries so that
135
+ # no more than 1000 rows are updated at a time (you can configure the
136
+ # number of rows using +:rows_per_page+). All arguments are
137
+ # passed to Dataset#update.
138
+ #
139
+ # Options:
140
+ # :rows_per_page :: The maximum number of rows affected by each UPDATE query
141
+ # (unless concurrent modifications are made to the table).
142
+ def paged_update(values, opts=OPTS)
143
+ rows_updated = 0
144
+ paged_datasets(opts) do |ds|
145
+ rows_updated += ds.update(values)
146
+ end
147
+ rows_updated
148
+ end
149
+
150
+ private
151
+
152
+ # Run some basic checks common to paged_{datasets,delete,update}
153
+ # and return the primary key to operate on as a Sequel::Identifier.
154
+ def _paged_operations_pk(meth)
155
+ raise Error, "cannot use #{meth} if dataset has a limit or offset" if @opts[:limit] || @opts[:offset]
156
+ if db.database_type == :db2 && db.offset_strategy == :emulate
157
+ raise Error, "the paged_operations plugin is not supported on DB2 when using emulated offsets, set the :offset_strategy Database option to 'limit_offset' or 'offset_fetch'"
158
+ end
159
+
160
+ case pk = model.primary_key
161
+ when Symbol
162
+ Sequel.identifier(pk)
163
+ when Array
164
+ raise Error, "cannot use #{meth} on a model with a composite primary key"
165
+ else
166
+ raise Error, "cannot use #{meth} on a model without a primary key"
167
+ end
168
+ end
169
+
170
+ # The dataset that will be used by paged_{datasets,delete,update}
171
+ # to get the upper limit for the next query.
172
+ def _paged_operations_offset_ds(opts)
173
+ if rows_per_page = opts[:rows_per_page]
174
+ raise Error, ":rows_per_page option must be at least 1" unless rows_per_page >= 1
175
+ end
176
+ _force_primary_key_order.offset(rows_per_page || 1000)
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
@@ -384,26 +384,32 @@ module Sequel
384
384
  save_opts = {:validate=>opts[:validate]}
385
385
  save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false
386
386
 
387
- opts[:adder] ||= proc do |o|
388
- if array = o.get_column_value(key)
389
- array << get_column_value(pk)
390
- else
391
- o.set_column_value("#{key}=", Sequel.pg_array([get_column_value(pk)], opts.array_type))
387
+ unless opts.has_key?(:adder)
388
+ opts[:adder] = proc do |o|
389
+ if array = o.get_column_value(key)
390
+ array << get_column_value(pk)
391
+ else
392
+ o.set_column_value("#{key}=", Sequel.pg_array([get_column_value(pk)], opts.array_type))
393
+ end
394
+ o.save(save_opts)
392
395
  end
393
- o.save(save_opts)
394
396
  end
395
-
396
- opts[:remover] ||= proc do |o|
397
- if (array = o.get_column_value(key)) && !array.empty?
398
- array.delete(get_column_value(pk))
399
- o.save(save_opts)
397
+
398
+ unless opts.has_key?(:remover)
399
+ opts[:remover] = proc do |o|
400
+ if (array = o.get_column_value(key)) && !array.empty?
401
+ array.delete(get_column_value(pk))
402
+ o.save(save_opts)
403
+ end
400
404
  end
401
405
  end
402
406
 
403
- opts[:clearer] ||= proc do
404
- pk_value = get_column_value(pk)
405
- db_type = opts.array_type
406
- opts.associated_dataset.where(Sequel.pg_array_op(key).contains(Sequel.pg_array([pk_value], db_type))).update(key=>Sequel.function(:array_remove, key, Sequel.cast(pk_value, db_type)))
407
+ unless opts.has_key?(:clearer)
408
+ opts[:clearer] = proc do
409
+ pk_value = get_column_value(pk)
410
+ db_type = opts.array_type
411
+ opts.associated_dataset.where(Sequel.pg_array_op(key).contains(Sequel.pg_array([pk_value], db_type))).update(key=>Sequel.function(:array_remove, key, Sequel.cast(pk_value, db_type)))
412
+ end
407
413
  end
408
414
  end
409
415
 
@@ -486,30 +492,36 @@ module Sequel
486
492
  end
487
493
  end
488
494
 
489
- opts[:adder] ||= proc do |o|
490
- opk = o.get_column_value(opts.primary_key)
491
- if array = get_column_value(key)
492
- modified!(key)
493
- array << opk
494
- else
495
- set_column_value("#{key}=", Sequel.pg_array([opk], opts.array_type))
495
+ unless opts.has_key?(:adder)
496
+ opts[:adder] = proc do |o|
497
+ opk = o.get_column_value(opts.primary_key)
498
+ if array = get_column_value(key)
499
+ modified!(key)
500
+ array << opk
501
+ else
502
+ set_column_value("#{key}=", Sequel.pg_array([opk], opts.array_type))
503
+ end
504
+ save_after_modify.call(self) if save_after_modify
496
505
  end
497
- save_after_modify.call(self) if save_after_modify
498
506
  end
499
-
500
- opts[:remover] ||= proc do |o|
501
- if (array = get_column_value(key)) && !array.empty?
502
- modified!(key)
503
- array.delete(o.get_column_value(opts.primary_key))
504
- save_after_modify.call(self) if save_after_modify
507
+
508
+ unless opts.has_key?(:remover)
509
+ opts[:remover] = proc do |o|
510
+ if (array = get_column_value(key)) && !array.empty?
511
+ modified!(key)
512
+ array.delete(o.get_column_value(opts.primary_key))
513
+ save_after_modify.call(self) if save_after_modify
514
+ end
505
515
  end
506
516
  end
507
517
 
508
- opts[:clearer] ||= proc do
509
- if (array = get_column_value(key)) && !array.empty?
510
- modified!(key)
511
- array.clear
512
- save_after_modify.call(self) if save_after_modify
518
+ unless opts.has_key?(:clearer)
519
+ opts[:clearer] = proc do
520
+ if (array = get_column_value(key)) && !array.empty?
521
+ modified!(key)
522
+ array.clear
523
+ save_after_modify.call(self) if save_after_modify
524
+ end
513
525
  end
514
526
  end
515
527
  end
@@ -28,11 +28,13 @@ module Sequel
28
28
  #
29
29
  # This plugin only works on the postgres adapter when using the pg 0.16+ driver,
30
30
  # PostgreSQL 9.3+ server, and PostgreSQL 9.3+ client library (libpq). In other cases
31
- # it will be a no-op.
31
+ # it will be a no-op. Additionally, the plugin only handles models that select
32
+ # from tables. It does not handle models that select from subqueries, such as
33
+ # subclasses of models using the class_table_inheritance plugin.
32
34
  #
33
35
  # Example:
34
36
  #
35
- # album = Album.new(:artist_id=>1) # Assume no such artist exists
37
+ # album = Album.new(artist_id: 1) # Assume no such artist exists
36
38
  # begin
37
39
  # album.save
38
40
  # rescue Sequel::ValidationFailed
@@ -131,7 +133,11 @@ module Sequel
131
133
  # Dump the in-memory cached metadata to the cache file.
132
134
  def dump_pg_auto_constraint_validations_cache
133
135
  raise Error, "No pg_auto_constraint_validations setup" unless file = @pg_auto_constraint_validations_cache_file
134
- File.open(file, 'wb'){|f| f.write(Marshal.dump(@pg_auto_constraint_validations_cache))}
136
+ pg_auto_constraint_validations_cache = {}
137
+ @pg_auto_constraint_validations_cache.sort.each do |k, v|
138
+ pg_auto_constraint_validations_cache[k] = v
139
+ end
140
+ File.open(file, 'wb'){|f| f.write(Marshal.dump(pg_auto_constraint_validations_cache))}
135
141
  nil
136
142
  end
137
143
 
@@ -0,0 +1,109 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # This plugin implements optimistic locking mechanism on PostgreSQL based
6
+ # on the xmin of the row. The xmin system column is automatically set to
7
+ # the current transaction id whenever the row is inserted or updated:
8
+ #
9
+ # class Person < Sequel::Model
10
+ # plugin :pg_xmin_optimistic_locking
11
+ # end
12
+ # p1 = Person[1]
13
+ # p2 = Person[1]
14
+ # p1.update(name: 'Jim') # works
15
+ # p2.update(name: 'Bob') # raises Sequel::NoExistingObject
16
+ #
17
+ # The advantage of pg_xmin_optimistic_locking plugin compared to the
18
+ # regular optimistic_locking plugin as that it does not require any
19
+ # additional columns setup on the model. This allows it to be loaded
20
+ # in the base model and have all subclasses automatically use
21
+ # optimistic locking. The disadvantage is that testing can be
22
+ # more difficult if you are modifying the underlying row between
23
+ # when a model is retrieved and when it is saved.
24
+ #
25
+ # This plugin may not work with the class_table_inheritance plugin.
26
+ #
27
+ # This plugin relies on the instance_filters plugin.
28
+ module PgXminOptimisticLocking
29
+ WILDCARD = LiteralString.new('*').freeze
30
+
31
+ # Define the xmin column accessor
32
+ def self.apply(model)
33
+ model.instance_exec do
34
+ plugin(:optimistic_locking_base)
35
+ @lock_column = :xmin
36
+ def_column_accessor(:xmin)
37
+ end
38
+ end
39
+
40
+ # Update the dataset to append the xmin column if it is usable
41
+ # and there is a dataset for the model.
42
+ def self.configure(model)
43
+ model.instance_exec do
44
+ set_dataset(@dataset) if @dataset
45
+ end
46
+ end
47
+
48
+ module ClassMethods
49
+ private
50
+
51
+ # Ensure the dataset selects the xmin column if doing so
52
+ def convert_input_dataset(ds)
53
+ append_xmin_column_if_usable(super)
54
+ end
55
+
56
+ # If the xmin column is not already selected, and selecting it does not
57
+ # raise an error, append it to the selections.
58
+ def append_xmin_column_if_usable(ds)
59
+ select = ds.opts[:select]
60
+
61
+ unless select && select.include?(:xmin)
62
+ xmin_ds = ds.select_append(:xmin)
63
+ begin
64
+ columns = xmin_ds.columns!
65
+ rescue Sequel::DatabaseConnectionError, Sequel::DatabaseDisconnectError
66
+ raise
67
+ rescue Sequel::DatabaseError
68
+ # ignore, could be view, subquery, table returning function, etc.
69
+ else
70
+ ds = xmin_ds if columns.include?(:xmin)
71
+ end
72
+ end
73
+
74
+ ds
75
+ end
76
+ end
77
+
78
+ module InstanceMethods
79
+ private
80
+
81
+ # Only set the lock column instance filter if there is an xmin value.
82
+ def lock_column_instance_filter
83
+ super if @values[:xmin]
84
+ end
85
+
86
+ # Include xmin value when inserting initial row
87
+ def _insert_dataset
88
+ super.returning(WILDCARD, :xmin)
89
+ end
90
+
91
+ # Remove the xmin from the columns to update.
92
+ # PostgreSQL automatically updates the xmin value, and it cannot be assigned.
93
+ def _save_update_all_columns_hash
94
+ v = super
95
+ v.delete(:xmin)
96
+ v
97
+ end
98
+
99
+ # Add an RETURNING clause to fetch the updated xmin when updating the row.
100
+ def _update_without_checking(columns)
101
+ ds = _update_dataset
102
+ rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.returning(:xmin).update_sql(columns))).all
103
+ values[:xmin] = rows.first[:xmin] unless rows.empty?
104
+ rows.length
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -3,7 +3,8 @@
3
3
  module Sequel
4
4
  module Plugins
5
5
  # The prepared_statements plugin modifies the model to use prepared statements for
6
- # instance level inserts and updates.
6
+ # instance level inserts and updates. This plugin exists for backwards compatibility
7
+ # and is not recommended for general use.
7
8
  #
8
9
  # Note that this plugin is unsafe in some circumstances, as it can allow up to
9
10
  # 2^N prepared statements to be created for each type of insert and update query, where
@@ -169,8 +170,17 @@ module Sequel
169
170
  end
170
171
 
171
172
  case type
172
- when :insert, :insert_select, :update
173
+ when :insert, :update
173
174
  true
175
+ when :insert_select
176
+ # SQLite RETURNING support has a bug that doesn't allow for committing transactions
177
+ # when a prepared statement with RETURNING has been used on the connection:
178
+ #
179
+ # SQLite3::BusyException: cannot commit transaction - SQL statements in progress: COMMIT
180
+ #
181
+ # Disabling usage of prepared statements for insert_select on SQLite seems to be the
182
+ # simplest way to workaround the problem.
183
+ db.database_type != :sqlite
174
184
  # :nocov:
175
185
  when :delete, :refresh
176
186
  Sequel::Deprecation.deprecate("The :delete and :refresh prepared statement types", "There should be no need to check if these types are supported")
@@ -5,7 +5,8 @@ module Sequel
5
5
  # The prepared_statements_safe plugin modifies the model to reduce the number of
6
6
  # prepared statements that can be created, by setting as many columns as possible
7
7
  # before creating, and by changing +save_changes+ to save all columns instead of
8
- # just the changed ones.
8
+ # just the changed ones. This plugin exists for backwards compatibility
9
+ # and is not recommended for general use.
9
10
  #
10
11
  # This plugin depends on the +prepared_statements+ plugin.
11
12
  #