sequel 5.39.0 → 5.72.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 (219) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +408 -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 +13 -6
  11. data/doc/model_hooks.rdoc +1 -1
  12. data/doc/object_model.rdoc +8 -8
  13. data/doc/opening_databases.rdoc +26 -12
  14. data/doc/postgresql.rdoc +16 -8
  15. data/doc/querying.rdoc +5 -3
  16. data/doc/release_notes/5.40.0.txt +40 -0
  17. data/doc/release_notes/5.41.0.txt +25 -0
  18. data/doc/release_notes/5.42.0.txt +136 -0
  19. data/doc/release_notes/5.43.0.txt +98 -0
  20. data/doc/release_notes/5.44.0.txt +32 -0
  21. data/doc/release_notes/5.45.0.txt +34 -0
  22. data/doc/release_notes/5.46.0.txt +87 -0
  23. data/doc/release_notes/5.47.0.txt +59 -0
  24. data/doc/release_notes/5.48.0.txt +14 -0
  25. data/doc/release_notes/5.49.0.txt +59 -0
  26. data/doc/release_notes/5.50.0.txt +78 -0
  27. data/doc/release_notes/5.51.0.txt +47 -0
  28. data/doc/release_notes/5.52.0.txt +87 -0
  29. data/doc/release_notes/5.53.0.txt +23 -0
  30. data/doc/release_notes/5.54.0.txt +27 -0
  31. data/doc/release_notes/5.55.0.txt +21 -0
  32. data/doc/release_notes/5.56.0.txt +51 -0
  33. data/doc/release_notes/5.57.0.txt +23 -0
  34. data/doc/release_notes/5.58.0.txt +31 -0
  35. data/doc/release_notes/5.59.0.txt +73 -0
  36. data/doc/release_notes/5.60.0.txt +22 -0
  37. data/doc/release_notes/5.61.0.txt +43 -0
  38. data/doc/release_notes/5.62.0.txt +132 -0
  39. data/doc/release_notes/5.63.0.txt +33 -0
  40. data/doc/release_notes/5.64.0.txt +50 -0
  41. data/doc/release_notes/5.65.0.txt +21 -0
  42. data/doc/release_notes/5.66.0.txt +24 -0
  43. data/doc/release_notes/5.67.0.txt +32 -0
  44. data/doc/release_notes/5.68.0.txt +61 -0
  45. data/doc/release_notes/5.69.0.txt +26 -0
  46. data/doc/release_notes/5.70.0.txt +35 -0
  47. data/doc/release_notes/5.71.0.txt +21 -0
  48. data/doc/release_notes/5.72.0.txt +33 -0
  49. data/doc/schema_modification.rdoc +1 -1
  50. data/doc/security.rdoc +9 -9
  51. data/doc/sharding.rdoc +3 -1
  52. data/doc/sql.rdoc +28 -16
  53. data/doc/testing.rdoc +22 -11
  54. data/doc/transactions.rdoc +6 -6
  55. data/doc/virtual_rows.rdoc +2 -2
  56. data/lib/sequel/adapters/ado/access.rb +1 -1
  57. data/lib/sequel/adapters/ado.rb +17 -17
  58. data/lib/sequel/adapters/amalgalite.rb +3 -5
  59. data/lib/sequel/adapters/ibmdb.rb +2 -2
  60. data/lib/sequel/adapters/jdbc/derby.rb +8 -0
  61. data/lib/sequel/adapters/jdbc/h2.rb +60 -10
  62. data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +7 -4
  64. data/lib/sequel/adapters/jdbc.rb +16 -18
  65. data/lib/sequel/adapters/mysql.rb +92 -67
  66. data/lib/sequel/adapters/mysql2.rb +54 -49
  67. data/lib/sequel/adapters/odbc.rb +6 -2
  68. data/lib/sequel/adapters/oracle.rb +4 -3
  69. data/lib/sequel/adapters/postgres.rb +83 -40
  70. data/lib/sequel/adapters/shared/access.rb +11 -1
  71. data/lib/sequel/adapters/shared/db2.rb +30 -0
  72. data/lib/sequel/adapters/shared/mssql.rb +90 -9
  73. data/lib/sequel/adapters/shared/mysql.rb +47 -2
  74. data/lib/sequel/adapters/shared/oracle.rb +82 -1
  75. data/lib/sequel/adapters/shared/postgres.rb +496 -178
  76. data/lib/sequel/adapters/shared/sqlanywhere.rb +11 -1
  77. data/lib/sequel/adapters/shared/sqlite.rb +116 -11
  78. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  79. data/lib/sequel/adapters/sqlite.rb +60 -18
  80. data/lib/sequel/adapters/tinytds.rb +1 -1
  81. data/lib/sequel/adapters/trilogy.rb +117 -0
  82. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  83. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  84. data/lib/sequel/ast_transformer.rb +6 -0
  85. data/lib/sequel/connection_pool/sharded_single.rb +5 -7
  86. data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
  87. data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
  88. data/lib/sequel/connection_pool/single.rb +6 -8
  89. data/lib/sequel/connection_pool/threaded.rb +14 -8
  90. data/lib/sequel/connection_pool/timed_queue.rb +270 -0
  91. data/lib/sequel/connection_pool.rb +55 -31
  92. data/lib/sequel/core.rb +28 -18
  93. data/lib/sequel/database/connecting.rb +27 -3
  94. data/lib/sequel/database/dataset.rb +16 -6
  95. data/lib/sequel/database/misc.rb +69 -14
  96. data/lib/sequel/database/query.rb +73 -2
  97. data/lib/sequel/database/schema_generator.rb +46 -53
  98. data/lib/sequel/database/schema_methods.rb +18 -2
  99. data/lib/sequel/dataset/actions.rb +108 -14
  100. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
  101. data/lib/sequel/dataset/features.rb +20 -0
  102. data/lib/sequel/dataset/misc.rb +12 -2
  103. data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
  104. data/lib/sequel/dataset/prepared_statements.rb +2 -0
  105. data/lib/sequel/dataset/query.rb +171 -44
  106. data/lib/sequel/dataset/sql.rb +182 -47
  107. data/lib/sequel/dataset.rb +4 -0
  108. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  109. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  110. data/lib/sequel/extensions/any_not_empty.rb +1 -1
  111. data/lib/sequel/extensions/async_thread_pool.rb +439 -0
  112. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  113. data/lib/sequel/extensions/blank.rb +8 -0
  114. data/lib/sequel/extensions/connection_expiration.rb +15 -9
  115. data/lib/sequel/extensions/connection_validator.rb +16 -11
  116. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  117. data/lib/sequel/extensions/core_refinements.rb +36 -11
  118. data/lib/sequel/extensions/date_arithmetic.rb +71 -31
  119. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  120. data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
  121. data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
  122. data/lib/sequel/extensions/eval_inspect.rb +2 -0
  123. data/lib/sequel/extensions/index_caching.rb +5 -1
  124. data/lib/sequel/extensions/inflector.rb +9 -1
  125. data/lib/sequel/extensions/is_distinct_from.rb +141 -0
  126. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  127. data/lib/sequel/extensions/migration.rb +11 -2
  128. data/lib/sequel/extensions/named_timezones.rb +26 -6
  129. data/lib/sequel/extensions/pagination.rb +1 -1
  130. data/lib/sequel/extensions/pg_array.rb +32 -4
  131. data/lib/sequel/extensions/pg_array_ops.rb +2 -2
  132. data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
  133. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
  134. data/lib/sequel/extensions/pg_enum.rb +2 -3
  135. data/lib/sequel/extensions/pg_extended_date_support.rb +38 -27
  136. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  137. data/lib/sequel/extensions/pg_hstore.rb +6 -1
  138. data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
  139. data/lib/sequel/extensions/pg_inet.rb +10 -11
  140. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  141. data/lib/sequel/extensions/pg_interval.rb +45 -19
  142. data/lib/sequel/extensions/pg_json.rb +13 -15
  143. data/lib/sequel/extensions/pg_json_ops.rb +73 -2
  144. data/lib/sequel/extensions/pg_loose_count.rb +3 -1
  145. data/lib/sequel/extensions/pg_multirange.rb +367 -0
  146. data/lib/sequel/extensions/pg_range.rb +11 -24
  147. data/lib/sequel/extensions/pg_range_ops.rb +37 -9
  148. data/lib/sequel/extensions/pg_row.rb +21 -19
  149. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  150. data/lib/sequel/extensions/query.rb +2 -0
  151. data/lib/sequel/extensions/s.rb +2 -1
  152. data/lib/sequel/extensions/schema_caching.rb +1 -1
  153. data/lib/sequel/extensions/schema_dumper.rb +45 -11
  154. data/lib/sequel/extensions/server_block.rb +10 -13
  155. data/lib/sequel/extensions/set_literalizer.rb +58 -0
  156. data/lib/sequel/extensions/sql_comments.rb +110 -3
  157. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  158. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  159. data/lib/sequel/extensions/string_agg.rb +1 -1
  160. data/lib/sequel/extensions/string_date_time.rb +19 -23
  161. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  162. data/lib/sequel/model/associations.rb +345 -101
  163. data/lib/sequel/model/base.rb +51 -27
  164. data/lib/sequel/model/dataset_module.rb +3 -0
  165. data/lib/sequel/model/errors.rb +10 -1
  166. data/lib/sequel/model/inflections.rb +1 -1
  167. data/lib/sequel/model/plugins.rb +5 -0
  168. data/lib/sequel/plugins/association_proxies.rb +2 -0
  169. data/lib/sequel/plugins/async_thread_pool.rb +39 -0
  170. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  171. data/lib/sequel/plugins/auto_validations.rb +87 -15
  172. data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
  173. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  174. data/lib/sequel/plugins/column_encryption.rb +728 -0
  175. data/lib/sequel/plugins/composition.rb +10 -4
  176. data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
  177. data/lib/sequel/plugins/constraint_validations.rb +10 -6
  178. data/lib/sequel/plugins/dataset_associations.rb +4 -1
  179. data/lib/sequel/plugins/defaults_setter.rb +16 -0
  180. data/lib/sequel/plugins/dirty.rb +1 -1
  181. data/lib/sequel/plugins/enum.rb +124 -0
  182. data/lib/sequel/plugins/finder.rb +4 -2
  183. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  184. data/lib/sequel/plugins/instance_specific_default.rb +1 -1
  185. data/lib/sequel/plugins/json_serializer.rb +39 -24
  186. data/lib/sequel/plugins/lazy_attributes.rb +3 -0
  187. data/lib/sequel/plugins/list.rb +3 -1
  188. data/lib/sequel/plugins/many_through_many.rb +109 -10
  189. data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
  190. data/lib/sequel/plugins/nested_attributes.rb +12 -7
  191. data/lib/sequel/plugins/optimistic_locking.rb +9 -42
  192. data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
  193. data/lib/sequel/plugins/pg_array_associations.rb +56 -38
  194. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +11 -3
  195. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
  196. data/lib/sequel/plugins/prepared_statements.rb +12 -2
  197. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
  198. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  199. data/lib/sequel/plugins/rcte_tree.rb +27 -19
  200. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  201. data/lib/sequel/plugins/serialization.rb +9 -3
  202. data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
  203. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  204. data/lib/sequel/plugins/sql_comments.rb +189 -0
  205. data/lib/sequel/plugins/static_cache.rb +39 -1
  206. data/lib/sequel/plugins/static_cache_cache.rb +5 -1
  207. data/lib/sequel/plugins/subclasses.rb +28 -11
  208. data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
  209. data/lib/sequel/plugins/timestamps.rb +1 -1
  210. data/lib/sequel/plugins/unused_associations.rb +521 -0
  211. data/lib/sequel/plugins/update_or_create.rb +1 -1
  212. data/lib/sequel/plugins/validate_associated.rb +22 -12
  213. data/lib/sequel/plugins/validation_helpers.rb +46 -12
  214. data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
  215. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  216. data/lib/sequel/sql.rb +1 -1
  217. data/lib/sequel/timezones.rb +12 -14
  218. data/lib/sequel/version.rb +1 -1
  219. metadata +132 -38
@@ -6,6 +6,17 @@
6
6
  # the current database). The main interface is through
7
7
  # Sequel::Database#dump_schema_migration.
8
8
  #
9
+ # The schema_dumper extension is quite limited in what types of
10
+ # database objects it supports. In general, it only supports
11
+ # dumping tables, columns, primary key and foreign key constraints,
12
+ # and some indexes. It does not support most table options, CHECK
13
+ # constraints, partial indexes, database functions, triggers,
14
+ # security grants/revokes, and a wide variety of other useful
15
+ # database properties. Be aware of the limitations when using the
16
+ # schema_dumper extension. If you are dumping the schema to restore
17
+ # to the same database type, it is recommended to use your database's
18
+ # dump and restore programs instead of the schema_dumper extension.
19
+ #
9
20
  # To load the extension:
10
21
  #
11
22
  # DB.extension :schema_dumper
@@ -77,11 +88,11 @@ module Sequel
77
88
  # Note that the migration this produces does not have a down
78
89
  # block, so you cannot reverse it.
79
90
  def dump_foreign_key_migration(options=OPTS)
80
- ts = tables(options)
91
+ ts = _dump_tables(options)
81
92
  <<END_MIG
82
93
  Sequel.migration do
83
94
  change do
84
- #{ts.sort.map{|t| dump_table_foreign_keys(t)}.reject{|x| x == ''}.join("\n\n").gsub(/^/, ' ')}
95
+ #{ts.map{|t| dump_table_foreign_keys(t)}.reject{|x| x == ''}.join("\n\n").gsub(/^/, ' ')}
85
96
  end
86
97
  end
87
98
  END_MIG
@@ -95,11 +106,11 @@ END_MIG
95
106
  # set to :namespace, prepend the table name to the index name if the
96
107
  # database does not use a global index namespace.
97
108
  def dump_indexes_migration(options=OPTS)
98
- ts = tables(options)
109
+ ts = _dump_tables(options)
99
110
  <<END_MIG
100
111
  Sequel.migration do
101
112
  change do
102
- #{ts.sort.map{|t| dump_table_indexes(t, :add_index, options)}.reject{|x| x == ''}.join("\n\n").gsub(/^/, ' ')}
113
+ #{ts.map{|t| dump_table_indexes(t, :add_index, options)}.reject{|x| x == ''}.join("\n\n").gsub(/^/, ' ')}
103
114
  end
104
115
  end
105
116
  END_MIG
@@ -127,7 +138,7 @@ END_MIG
127
138
  options[:foreign_keys] = false
128
139
  end
129
140
 
130
- ts = sort_dumped_tables(tables(options), options)
141
+ ts = sort_dumped_tables(_dump_tables(options), options)
131
142
  skipped_fks = if sfk = options[:skipped_foreign_keys]
132
143
  # Handle skipped foreign keys by adding them at the end via
133
144
  # alter_table/add_foreign_key. Note that skipped foreign keys
@@ -155,6 +166,21 @@ END_MIG
155
166
 
156
167
  private
157
168
 
169
+ # Handle schema option to dump tables in a different schema. Such
170
+ # tables must be schema qualified for this to work correctly.
171
+ def _dump_tables(opts)
172
+ if opts[:schema]
173
+ _literal_table_sort(tables(opts.merge(:qualify=>true)))
174
+ else
175
+ tables(opts).sort
176
+ end
177
+ end
178
+
179
+ # Sort the given table by the literalized value.
180
+ def _literal_table_sort(tables)
181
+ tables.sort_by{|s| literal(s)}
182
+ end
183
+
158
184
  # If a database default exists and can't be converted, and we are dumping with :same_db,
159
185
  # return a string with the inspect method modified a literal string is created if the code is evaled.
160
186
  def column_schema_to_ruby_default_fallback(default, options)
@@ -172,7 +198,7 @@ END_MIG
172
198
  if options[:single_pk] && schema_autoincrementing_primary_key?(schema)
173
199
  type_hash = options[:same_db] ? {:type=>schema[:db_type]} : column_schema_to_ruby_type(schema)
174
200
  [:table, :key, :on_delete, :on_update, :deferrable].each{|f| type_hash[f] = schema[f] if schema[f]}
175
- if type_hash == {:type=>Integer} || type_hash == {:type=>"integer"}
201
+ if type_hash == {:type=>Integer} || type_hash == {:type=>"integer"} || type_hash == {:type=>"INTEGER"}
176
202
  type_hash.delete(:type)
177
203
  elsif options[:same_db] && type_hash == {:type=>type_literal_generic_bignum_symbol(type_hash).to_s}
178
204
  type_hash[:type] = :Bignum
@@ -193,12 +219,20 @@ END_MIG
193
219
  if database_type == :mysql && h[:type] =~ /\Atimestamp/
194
220
  h[:null] = true
195
221
  end
222
+ if database_type == :mssql && schema[:max_length]
223
+ h[:size] = schema[:max_length]
224
+ end
196
225
  h
197
226
  else
198
227
  column_schema_to_ruby_type(schema)
199
228
  end
200
229
  type = col_opts.delete(:type)
201
- col_opts.delete(:size) if col_opts[:size].nil?
230
+ if col_opts.key?(:size) && col_opts[:size].nil?
231
+ col_opts.delete(:size)
232
+ if max_length = schema[:max_length]
233
+ col_opts[:size] = max_length
234
+ end
235
+ end
202
236
  if schema[:generated]
203
237
  if options[:same_db] && database_type == :postgres
204
238
  col_opts[:generated_always_as] = column_schema_to_ruby_default_fallback(schema[:default], options)
@@ -214,7 +248,7 @@ END_MIG
214
248
  col_opts[:null] = false if schema[:allow_null] == false
215
249
  if table = schema[:table]
216
250
  [:key, :on_delete, :on_update, :deferrable].each{|f| col_opts[f] = schema[f] if schema[f]}
217
- col_opts[:type] = type unless type == Integer || type == 'integer'
251
+ col_opts[:type] = type unless type == Integer || type == 'integer' || type == 'INTEGER'
218
252
  gen.foreign_key(name, table, col_opts)
219
253
  else
220
254
  gen.column(name, type, col_opts)
@@ -341,7 +375,7 @@ END_MIG
341
375
  options[:skipped_foreign_keys] = skipped_foreign_keys
342
376
  tables
343
377
  else
344
- tables.sort
378
+ tables
345
379
  end
346
380
  end
347
381
 
@@ -366,14 +400,14 @@ END_MIG
366
400
  # outstanding foreign keys and skipping those foreign keys.
367
401
  # The skipped foreign keys will be added at the end of the
368
402
  # migration.
369
- skip_table, skip_fks = table_fks.sort_by{|table, fks| [fks.length, table]}.first
403
+ skip_table, skip_fks = table_fks.sort_by{|table, fks| [fks.length, literal(table)]}.first
370
404
  skip_fks_hash = skipped_foreign_keys[skip_table] = {}
371
405
  skip_fks.each{|fk| skip_fks_hash[fk[:columns]] = fk}
372
406
  this_loop << skip_table
373
407
  end
374
408
 
375
409
  # Add sorted tables from this loop to the final list
376
- sorted_tables.concat(this_loop.sort)
410
+ sorted_tables.concat(_literal_table_sort(this_loop))
377
411
 
378
412
  # Remove tables that were handled this loop
379
413
  this_loop.each{|t| table_fks.delete(t)}
@@ -69,7 +69,8 @@ module Sequel
69
69
  # Also defines the with_server method on the receiver for easy use.
70
70
  def self.extended(db)
71
71
  pool = db.pool
72
- if defined?(ShardedThreadedConnectionPool) && pool.is_a?(ShardedThreadedConnectionPool)
72
+ case pool.pool_type
73
+ when :sharded_threaded, :sharded_timed_queue
73
74
  pool.extend(ThreadedServerBlock)
74
75
  pool.instance_variable_set(:@default_servers, {})
75
76
  else
@@ -88,12 +89,10 @@ module Sequel
88
89
  module UnthreadedServerBlock
89
90
  # Set a default server/shard to use inside the block.
90
91
  def with_server(default_server, read_only_server=default_server)
91
- begin
92
- set_default_server(default_server, read_only_server)
93
- yield
94
- ensure
95
- clear_default_server
96
- end
92
+ set_default_server(default_server, read_only_server)
93
+ yield
94
+ ensure
95
+ clear_default_server
97
96
  end
98
97
 
99
98
  private
@@ -131,12 +130,10 @@ module Sequel
131
130
  # Set a default server/shard to use inside the block for the current
132
131
  # thread.
133
132
  def with_server(default_server, read_only_server=default_server)
134
- begin
135
- set_default_server(default_server, read_only_server)
136
- yield
137
- ensure
138
- clear_default_server
139
- end
133
+ set_default_server(default_server, read_only_server)
134
+ yield
135
+ ensure
136
+ clear_default_server
140
137
  end
141
138
 
142
139
  private
@@ -0,0 +1,58 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The set_literalizer extension allows for using Set instances in many of the
4
+ # same places that you would use Array instances:
5
+ #
6
+ # DB[:table].where(column: Set.new([1, 2, 3]))
7
+ # # SELECT FROM table WHERE (column IN (1, 2, 3))
8
+ #
9
+ # To load the extension into all datasets created from a given Database:
10
+ #
11
+ # DB.extension :set_literalizer
12
+ #
13
+ # Related module: Sequel::Dataset::SetLiteralizer
14
+
15
+ require 'set'
16
+
17
+ module Sequel
18
+ class Dataset
19
+ module SetLiteralizer
20
+ # Try to generate the same SQL for Set instances used in datasets
21
+ # that would be used for equivalent Array instances.
22
+ def complex_expression_sql_append(sql, op, args)
23
+ # Array instances are treated specially by
24
+ # Sequel::SQL::BooleanExpression.from_value_pairs. That cannot
25
+ # be modified by a dataset extension, so this tries to convert
26
+ # the complex expression values generated by default to what would
27
+ # be the complex expression values used for the equivalent array.
28
+ case op
29
+ when :'=', :'!='
30
+ if (set = args[1]).is_a?(Set)
31
+ op = op == :'=' ? :IN : :'NOT IN'
32
+ col = args[0]
33
+ array = set.to_a
34
+ if Sequel.condition_specifier?(array) && col.is_a?(Array)
35
+ array = Sequel.value_list(array)
36
+ end
37
+ args = [col, array]
38
+ end
39
+ end
40
+
41
+ super
42
+ end
43
+
44
+ private
45
+
46
+ # Literalize Set instances by converting the set to array.
47
+ def literal_other_append(sql, v)
48
+ if Set === v
49
+ literal_append(sql, v.to_a)
50
+ else
51
+ super
52
+ end
53
+ end
54
+ end
55
+
56
+ register_extension(:set_literalizer, SetLiteralizer)
57
+ end
58
+ end
@@ -44,11 +44,51 @@
44
44
  #
45
45
  # DB.extension(:sql_comments)
46
46
  #
47
+ # Loading the sql_comments extension into the database also adds
48
+ # support for block-level comment support via Database#with_comments.
49
+ # You call #with_comments with a hash. Queries inside the hash will
50
+ # include a comment based on the hash (assuming they are inside the
51
+ # same thread):
52
+ #
53
+ # DB.with_comments(model: Album, action: :all) do
54
+ # DB[:albums].all
55
+ # # SELECT * FROM albums -- model:Album,action:all
56
+ # end
57
+ #
58
+ # You can nest calls to #with_comments, which will combine the
59
+ # entries from both calls:
60
+ #
61
+ # DB.with_comments(application: App, path: :scrubbed_path) do
62
+ # DB.with_comments(model: Album, action: :all) do
63
+ # ds = DB[:albums].all
64
+ # # SELECT * FROM albums
65
+ # # -- application:App,path:scrubbed_path,model:Album,action:all
66
+ # end
67
+ # end
68
+ #
69
+ # You can override comment entries specified in earlier blocks, or
70
+ # remove entries specified earlier using a nil value:
71
+ #
72
+ # DB.with_comments(application: App, path: :scrubbed_path) do
73
+ # DB.with_comments(application: Foo, path: nil) do
74
+ # ds = DB[:albums].all
75
+ # # SELECT * FROM albums # -- application:Foo
76
+ # end
77
+ # end
78
+ #
79
+ # You can combine block-level comments with dataset-specific
80
+ # comments:
81
+ #
82
+ # DB.with_comments(model: Album, action: :all) do
83
+ # DB[:table].comment("Some Comment").all
84
+ # # SELECT * FROM albums -- model:Album,action:all -- Some Comment
85
+ # end
86
+ #
47
87
  # Note that Microsoft Access does not support inline comments,
48
88
  # and attempting to use comments on it will result in SQL syntax
49
89
  # errors.
50
90
  #
51
- # Related module: Sequel::SQLComments
91
+ # Related modules: Sequel::SQLComments, Sequel::Database::SQLComments
52
92
 
53
93
  #
54
94
  module Sequel
@@ -62,7 +102,7 @@ module Sequel
62
102
  %w'select insert update delete'.each do |type|
63
103
  define_method(:"#{type}_sql") do |*a|
64
104
  sql = super(*a)
65
- if comment = @opts[:comment]
105
+ if comment = _sql_comment
66
106
  # This assumes that the comment stored in the dataset has
67
107
  # already been formatted. If not, this could result in SQL
68
108
  # injection.
@@ -74,8 +114,10 @@ module Sequel
74
114
  if sql.frozen?
75
115
  sql += comment
76
116
  sql.freeze
77
- else
117
+ elsif @opts[:append_sql] || @opts[:placeholder_literalizer]
78
118
  sql << comment
119
+ else
120
+ sql += comment
79
121
  end
80
122
  end
81
123
  sql
@@ -84,6 +126,11 @@ module Sequel
84
126
 
85
127
  private
86
128
 
129
+ # The comment to include in the SQL query, if any.
130
+ def _sql_comment
131
+ @opts[:comment]
132
+ end
133
+
87
134
  # Format the comment. For maximum compatibility, this uses a
88
135
  # single line SQL comment, and converts all consecutive whitespace
89
136
  # in the comment to a single space.
@@ -92,5 +139,65 @@ module Sequel
92
139
  end
93
140
  end
94
141
 
142
+ module Database::SQLComments
143
+ def self.extended(db)
144
+ db.instance_variable_set(:@comment_hashes, {})
145
+ db.extend_datasets DatasetSQLComments
146
+ end
147
+
148
+ # A map of threads to comment hashes, used for correctly setting
149
+ # comments for all queries inside #with_comments blocks.
150
+ attr_reader :comment_hashes
151
+
152
+ # Store the comment hash and use it to create comments inside the block
153
+ def with_comments(comment_hash)
154
+ hashes = @comment_hashes
155
+ t = Sequel.current
156
+ new_hash = if hash = Sequel.synchronize{hashes[t]}
157
+ hash.merge(comment_hash)
158
+ else
159
+ comment_hash.dup
160
+ end
161
+ yield Sequel.synchronize{hashes[t] = new_hash}
162
+ ensure
163
+ if hash
164
+ Sequel.synchronize{hashes[t] = hash}
165
+ else
166
+ t && Sequel.synchronize{hashes.delete(t)}
167
+ end
168
+ end
169
+
170
+ module DatasetSQLComments
171
+ include Sequel::SQLComments
172
+
173
+ private
174
+
175
+ # Include comments added via Database#with_comments in the output SQL.
176
+ def _sql_comment
177
+ specific_comment = super
178
+ return specific_comment if @opts[:append_sql]
179
+
180
+ t = Sequel.current
181
+ hashes = db.comment_hashes
182
+ block_comment = if comment_hash = Sequel.synchronize{hashes[t]}
183
+ comment_array = comment_hash.map{|k,v| "#{k}:#{v}" unless v.nil?}
184
+ comment_array.compact!
185
+ comment_array.join(",")
186
+ end
187
+
188
+ if block_comment
189
+ if specific_comment
190
+ format_sql_comment(block_comment + specific_comment)
191
+ else
192
+ format_sql_comment(block_comment)
193
+ end
194
+ else
195
+ specific_comment
196
+ end
197
+ end
198
+ end
199
+ end
200
+
95
201
  Dataset.register_extension(:sql_comments, SQLComments)
202
+ Database.register_extension(:sql_comments, Database::SQLComments)
96
203
  end
@@ -0,0 +1,108 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The sql_log_normalizer extension normalizes the SQL that is logged,
4
+ # removing the literal strings and numbers in the SQL, and removing the
5
+ # logging of any bound variables:
6
+ #
7
+ # ds = DB[:table].first(a: 1, b: 'something')
8
+ # # Without sql_log_normalizer extension
9
+ # # SELECT * FROM "table" WHERE (("a" = 1) AND ("b" = 'something')) LIMIT 1
10
+ #
11
+ # # With sql_log_normalizer_extension
12
+ # # SELECT * FROM "table" WHERE (("a" = ?) AND ("b" = ?)) LIMIT ?
13
+ #
14
+ # The normalization is done by scanning the SQL string being executed
15
+ # for literal strings and numbers, and replacing them with question
16
+ # marks. While this should work for all or almost all production queries,
17
+ # there are pathlogical queries that will not be handled correctly, such as
18
+ # the use of apostrophes in identifiers:
19
+ #
20
+ # DB[:"asf'bar"].where(a: 1, b: 'something').first
21
+ # # Logged as:
22
+ # # SELECT * FROM "asf?something')) LIMIT ?
23
+ #
24
+ # The expected use case for this extension is when you want to normalize
25
+ # logs to group similar queries, or when you want to protect sensitive
26
+ # data from being stored in the logs.
27
+ #
28
+ # Related module: Sequel::SQLLogNormalizer
29
+
30
+ #
31
+ module Sequel
32
+ module SQLLogNormalizer
33
+ def self.extended(db)
34
+ type = case db.literal("'")
35
+ when "''''"
36
+ :standard
37
+ when "'\\''"
38
+ :backslash
39
+ when "N''''"
40
+ :n_standard
41
+ else
42
+ raise Error, "SQL log normalization is not supported on this database (' literalized as #{db.literal("'").inspect})"
43
+ end
44
+ db.instance_variable_set(:@sql_string_escape_type, type)
45
+ end
46
+
47
+ # Normalize the SQL before calling super.
48
+ def log_connection_yield(sql, conn, args=nil)
49
+ unless skip_logging?
50
+ sql = normalize_logged_sql(sql)
51
+ args = nil
52
+ end
53
+ super
54
+ end
55
+
56
+ # Replace literal strings and numbers in SQL with question mark placeholders.
57
+ def normalize_logged_sql(sql)
58
+ sql = sql.dup
59
+ sql.force_encoding('BINARY')
60
+ start_index = 0
61
+ check_n = @sql_string_escape_type == :n_standard
62
+ outside_string = true
63
+
64
+ if @sql_string_escape_type == :backslash
65
+ search_char = /[\\']/
66
+ escape_char_offset = 0
67
+ escape_char_value = 92 # backslash
68
+ else
69
+ search_char = "'"
70
+ escape_char_offset = 1
71
+ escape_char_value = 39 # apostrophe
72
+ end
73
+
74
+ # The approach used here goes against Sequel's philosophy of never attempting
75
+ # to parse SQL. However, parsing the SQL is basically the only way to implement
76
+ # this support with Sequel's design, and it's better to be pragmatic and accept
77
+ # this than not be able to support this.
78
+
79
+ # Replace literal strings
80
+ while outside_string && (index = start_index = sql.index("'", start_index))
81
+ if check_n && index != 0 && sql.getbyte(index-1) == 78 # N' start
82
+ start_index -= 1
83
+ end
84
+ index += 1
85
+ outside_string = false
86
+
87
+ while (index = sql.index(search_char, index)) && (sql.getbyte(index + escape_char_offset) == escape_char_value)
88
+ # skip escaped characters inside string literal
89
+ index += 2
90
+ end
91
+
92
+ if index
93
+ # Found end of string
94
+ sql[start_index..index] = '?'
95
+ start_index += 1
96
+ outside_string = true
97
+ end
98
+ end
99
+
100
+ # Replace integer and decimal floating point numbers
101
+ sql.gsub!(/\b-?\d+(?:\.\d+)?\b/, '?')
102
+
103
+ sql
104
+ end
105
+ end
106
+
107
+ Database.register_extension(:sql_log_normalizer, SQLLogNormalizer)
108
+ end