sequel 5.58.0 → 5.78.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (161) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +288 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +24 -23
  5. data/bin/sequel +11 -3
  6. data/doc/advanced_associations.rdoc +16 -14
  7. data/doc/association_basics.rdoc +53 -17
  8. data/doc/cheat_sheet.rdoc +3 -3
  9. data/doc/mass_assignment.rdoc +1 -1
  10. data/doc/migration.rdoc +15 -0
  11. data/doc/model_hooks.rdoc +1 -1
  12. data/doc/object_model.rdoc +8 -8
  13. data/doc/opening_databases.rdoc +20 -12
  14. data/doc/postgresql.rdoc +8 -8
  15. data/doc/querying.rdoc +1 -1
  16. data/doc/release_notes/5.59.0.txt +73 -0
  17. data/doc/release_notes/5.60.0.txt +22 -0
  18. data/doc/release_notes/5.61.0.txt +43 -0
  19. data/doc/release_notes/5.62.0.txt +132 -0
  20. data/doc/release_notes/5.63.0.txt +33 -0
  21. data/doc/release_notes/5.64.0.txt +50 -0
  22. data/doc/release_notes/5.65.0.txt +21 -0
  23. data/doc/release_notes/5.66.0.txt +24 -0
  24. data/doc/release_notes/5.67.0.txt +32 -0
  25. data/doc/release_notes/5.68.0.txt +61 -0
  26. data/doc/release_notes/5.69.0.txt +26 -0
  27. data/doc/release_notes/5.70.0.txt +35 -0
  28. data/doc/release_notes/5.71.0.txt +21 -0
  29. data/doc/release_notes/5.72.0.txt +33 -0
  30. data/doc/release_notes/5.73.0.txt +66 -0
  31. data/doc/release_notes/5.74.0.txt +45 -0
  32. data/doc/release_notes/5.75.0.txt +35 -0
  33. data/doc/release_notes/5.76.0.txt +86 -0
  34. data/doc/release_notes/5.77.0.txt +63 -0
  35. data/doc/release_notes/5.78.0.txt +67 -0
  36. data/doc/schema_modification.rdoc +3 -3
  37. data/doc/security.rdoc +9 -9
  38. data/doc/sharding.rdoc +3 -1
  39. data/doc/sql.rdoc +14 -14
  40. data/doc/testing.rdoc +16 -12
  41. data/doc/transactions.rdoc +6 -6
  42. data/doc/virtual_rows.rdoc +1 -1
  43. data/lib/sequel/adapters/ibmdb.rb +1 -1
  44. data/lib/sequel/adapters/jdbc/h2.rb +3 -0
  45. data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -0
  46. data/lib/sequel/adapters/jdbc/postgresql.rb +3 -0
  47. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +15 -0
  48. data/lib/sequel/adapters/jdbc/sqlserver.rb +4 -0
  49. data/lib/sequel/adapters/jdbc.rb +10 -6
  50. data/lib/sequel/adapters/mysql.rb +19 -7
  51. data/lib/sequel/adapters/mysql2.rb +2 -2
  52. data/lib/sequel/adapters/odbc/mssql.rb +1 -1
  53. data/lib/sequel/adapters/oracle.rb +1 -0
  54. data/lib/sequel/adapters/postgres.rb +62 -16
  55. data/lib/sequel/adapters/shared/access.rb +9 -1
  56. data/lib/sequel/adapters/shared/db2.rb +12 -0
  57. data/lib/sequel/adapters/shared/mssql.rb +71 -9
  58. data/lib/sequel/adapters/shared/mysql.rb +80 -1
  59. data/lib/sequel/adapters/shared/oracle.rb +17 -7
  60. data/lib/sequel/adapters/shared/postgres.rb +494 -164
  61. data/lib/sequel/adapters/shared/sqlanywhere.rb +18 -5
  62. data/lib/sequel/adapters/shared/sqlite.rb +40 -4
  63. data/lib/sequel/adapters/sqlite.rb +42 -3
  64. data/lib/sequel/adapters/trilogy.rb +117 -0
  65. data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
  66. data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
  67. data/lib/sequel/connection_pool/threaded.rb +14 -8
  68. data/lib/sequel/connection_pool/timed_queue.rb +270 -0
  69. data/lib/sequel/connection_pool.rb +57 -31
  70. data/lib/sequel/database/connecting.rb +25 -1
  71. data/lib/sequel/database/dataset.rb +16 -6
  72. data/lib/sequel/database/misc.rb +65 -14
  73. data/lib/sequel/database/query.rb +72 -1
  74. data/lib/sequel/database/schema_generator.rb +2 -1
  75. data/lib/sequel/database/schema_methods.rb +13 -3
  76. data/lib/sequel/database/transactions.rb +6 -0
  77. data/lib/sequel/dataset/actions.rb +60 -13
  78. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
  79. data/lib/sequel/dataset/features.rb +15 -1
  80. data/lib/sequel/dataset/misc.rb +12 -2
  81. data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
  82. data/lib/sequel/dataset/query.rb +62 -37
  83. data/lib/sequel/dataset/sql.rb +58 -36
  84. data/lib/sequel/dataset.rb +4 -0
  85. data/lib/sequel/exceptions.rb +5 -0
  86. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  87. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  88. data/lib/sequel/extensions/any_not_empty.rb +2 -2
  89. data/lib/sequel/extensions/async_thread_pool.rb +21 -13
  90. data/lib/sequel/extensions/auto_cast_date_and_time.rb +94 -0
  91. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  92. data/lib/sequel/extensions/connection_expiration.rb +15 -9
  93. data/lib/sequel/extensions/connection_validator.rb +16 -11
  94. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  95. data/lib/sequel/extensions/date_arithmetic.rb +36 -8
  96. data/lib/sequel/extensions/duplicate_columns_handler.rb +10 -9
  97. data/lib/sequel/extensions/index_caching.rb +5 -1
  98. data/lib/sequel/extensions/is_distinct_from.rb +3 -1
  99. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  100. data/lib/sequel/extensions/migration.rb +65 -15
  101. data/lib/sequel/extensions/named_timezones.rb +22 -6
  102. data/lib/sequel/extensions/pg_array.rb +33 -4
  103. data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
  104. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
  105. data/lib/sequel/extensions/pg_enum.rb +1 -2
  106. data/lib/sequel/extensions/pg_extended_date_support.rb +38 -27
  107. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  108. data/lib/sequel/extensions/pg_hstore.rb +5 -0
  109. data/lib/sequel/extensions/pg_inet.rb +10 -11
  110. data/lib/sequel/extensions/pg_interval.rb +10 -11
  111. data/lib/sequel/extensions/pg_json.rb +10 -10
  112. data/lib/sequel/extensions/pg_json_ops.rb +52 -0
  113. data/lib/sequel/extensions/pg_multirange.rb +6 -11
  114. data/lib/sequel/extensions/pg_range.rb +9 -14
  115. data/lib/sequel/extensions/pg_row.rb +20 -19
  116. data/lib/sequel/extensions/pg_timestamptz.rb +27 -3
  117. data/lib/sequel/extensions/round_timestamps.rb +1 -1
  118. data/lib/sequel/extensions/schema_caching.rb +1 -1
  119. data/lib/sequel/extensions/schema_dumper.rb +32 -9
  120. data/lib/sequel/extensions/server_block.rb +2 -1
  121. data/lib/sequel/extensions/set_literalizer.rb +58 -0
  122. data/lib/sequel/extensions/sqlite_json_ops.rb +76 -18
  123. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  124. data/lib/sequel/extensions/transaction_connection_validator.rb +78 -0
  125. data/lib/sequel/model/associations.rb +50 -11
  126. data/lib/sequel/model/base.rb +45 -21
  127. data/lib/sequel/model/dataset_module.rb +3 -0
  128. data/lib/sequel/model/exceptions.rb +15 -3
  129. data/lib/sequel/plugins/auto_validations.rb +53 -15
  130. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  131. data/lib/sequel/plugins/column_encryption.rb +27 -6
  132. data/lib/sequel/plugins/composition.rb +2 -2
  133. data/lib/sequel/plugins/concurrent_eager_loading.rb +4 -4
  134. data/lib/sequel/plugins/constraint_validations.rb +8 -5
  135. data/lib/sequel/plugins/defaults_setter.rb +16 -0
  136. data/lib/sequel/plugins/dirty.rb +1 -1
  137. data/lib/sequel/plugins/finder.rb +4 -2
  138. data/lib/sequel/plugins/list.rb +8 -3
  139. data/lib/sequel/plugins/many_through_many.rb +1 -1
  140. data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
  141. data/lib/sequel/plugins/nested_attributes.rb +4 -4
  142. data/lib/sequel/plugins/optimistic_locking.rb +9 -42
  143. data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
  144. data/lib/sequel/plugins/paged_operations.rb +181 -0
  145. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +9 -3
  146. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
  147. data/lib/sequel/plugins/prepared_statements.rb +2 -1
  148. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
  149. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  150. data/lib/sequel/plugins/rcte_tree.rb +7 -4
  151. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  152. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  153. data/lib/sequel/plugins/sql_comments.rb +5 -5
  154. data/lib/sequel/plugins/static_cache.rb +38 -0
  155. data/lib/sequel/plugins/static_cache_cache.rb +5 -1
  156. data/lib/sequel/plugins/tactical_eager_loading.rb +21 -14
  157. data/lib/sequel/plugins/validate_associated.rb +22 -12
  158. data/lib/sequel/plugins/validation_helpers.rb +29 -2
  159. data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
  160. data/lib/sequel/version.rb +1 -1
  161. metadata +76 -6
@@ -56,7 +56,11 @@ module Sequel
56
56
 
57
57
  # Dump the index cache to the filename given in Marshal format.
58
58
  def dump_index_cache(file)
59
- File.open(file, 'wb'){|f| f.write(Marshal.dump(@indexes))}
59
+ indexes = {}
60
+ @indexes.sort.each do |k, v|
61
+ indexes[k] = v
62
+ end
63
+ File.open(file, 'wb'){|f| f.write(Marshal.dump(indexes))}
60
64
  nil
61
65
  end
62
66
 
@@ -3,7 +3,7 @@
3
3
  # The is_distinct_from extension adds the ability to use the
4
4
  # SQL standard IS DISTINCT FROM operator, which is similar to the
5
5
  # not equals operator, except that NULL values are considered
6
- # equal. Only PostgreSQL and H2 currently support this operator. On
6
+ # equal. PostgreSQL, SQLite 3.39+, and H2 currently support this operator. On
7
7
  # other databases, support is emulated.
8
8
  #
9
9
  # First, you need to load the extension into the database:
@@ -90,6 +90,8 @@ module Sequel
90
90
  case db.database_type
91
91
  when :postgres, :h2
92
92
  true
93
+ when :sqlite
94
+ db.sqlite_version >= 33900
93
95
  else
94
96
  false
95
97
  end
@@ -8,6 +8,9 @@
8
8
  # :decimal :: use 0.0 for unsupported strings
9
9
  # :string :: silently allow hash and array conversion to string
10
10
  #
11
+ # This also removes bytesize checks for string inputs for float, integer
12
+ # and decimal conversions.
13
+ #
11
14
  # To load the extension into the database:
12
15
  #
13
16
  # DB.extension :looser_typecasting
@@ -159,6 +159,19 @@ module Sequel
159
159
  migration.up = block
160
160
  migration.down = MigrationReverser.new.reverse(&block)
161
161
  end
162
+
163
+ # Creates a revert migration. This is the same as creating
164
+ # the same block with +down+, but it also calls the block and attempts
165
+ # to create a +up+ block that will reverse the changes made by
166
+ # the block. This is designed to revert the changes in the
167
+ # provided block.
168
+ #
169
+ # There are no guarantees that this will work perfectly
170
+ # in all cases, but it works for some simple cases.
171
+ def revert(&block)
172
+ migration.down = block
173
+ migration.up = MigrationReverser.new.reverse(&block)
174
+ end
162
175
  end
163
176
 
164
177
  # Handles the reversing of reversible migrations. Basically records
@@ -270,6 +283,10 @@ module Sequel
270
283
  def rename_column(name, new_name)
271
284
  @actions << [:rename_column, new_name, name]
272
285
  end
286
+
287
+ def set_column_allow_null(name, allow_null=true)
288
+ @actions << [:set_column_allow_null, name, !allow_null]
289
+ end
273
290
  end
274
291
 
275
292
  # The preferred method for writing Sequel migrations, using a DSL:
@@ -377,7 +394,7 @@ module Sequel
377
394
  # Raise a NotCurrentError unless the migrator is current, takes the same
378
395
  # arguments as #run.
379
396
  def self.check_current(*args)
380
- raise(NotCurrentError, 'migrator is not current') unless is_current?(*args)
397
+ raise(NotCurrentError, 'current migration version does not match latest available version') unless is_current?(*args)
381
398
  end
382
399
 
383
400
  # Return whether the migrator is current (i.e. it does not need to make
@@ -386,6 +403,11 @@ module Sequel
386
403
  migrator_class(directory).new(db, directory, opts).is_current?
387
404
  end
388
405
 
406
+ # Lock ID to use for advisory locks when running migrations
407
+ # "sequel-migration".codepoints.reduce(:*) % (2**63)
408
+ MIGRATION_ADVISORY_LOCK_ID = 4966325471869609408
409
+ private_constant :MIGRATION_ADVISORY_LOCK_ID
410
+
389
411
  # Migrates the supplied database using the migration files in the specified directory. Options:
390
412
  # :allow_missing_migration_files :: Don't raise an error if there are missing migration files.
391
413
  # It is very risky to use this option, since it can result in
@@ -399,6 +421,8 @@ module Sequel
399
421
  # :table :: The table containing the schema version (default: :schema_info for integer migrations and
400
422
  # :schema_migrations for timestamped migrations).
401
423
  # :target :: The target version to which to migrate. If not given, migrates to the maximum version.
424
+ # :use_advisory_lock :: Use advisory locks in migrations (only use this if Sequel supports advisory
425
+ # locks for the database).
402
426
  #
403
427
  # Examples:
404
428
  # Sequel::Migrator.run(DB, "migrations")
@@ -406,7 +430,11 @@ module Sequel
406
430
  # Sequel::Migrator.run(DB, "app1/migrations", column: :app2_version)
407
431
  # Sequel::Migrator.run(DB, "app2/migrations", column: :app2_version, table: :schema_info2)
408
432
  def self.run(db, directory, opts=OPTS)
409
- migrator_class(directory).new(db, directory, opts).run
433
+ if opts[:use_advisory_lock]
434
+ db.with_advisory_lock(MIGRATION_ADVISORY_LOCK_ID){run(db, directory, opts.merge(:use_advisory_lock=>false))}
435
+ else
436
+ migrator_class(directory).new(db, directory, opts).run
437
+ end
410
438
  end
411
439
 
412
440
  # Choose the Migrator subclass to use. Uses the TimestampMigrator
@@ -478,11 +506,7 @@ module Sequel
478
506
  @use_transactions
479
507
  end
480
508
 
481
- if use_trans
482
- db.transaction(&block)
483
- else
484
- yield
485
- end
509
+ db.transaction(:skip_transaction=>use_trans == false, &block)
486
510
  end
487
511
 
488
512
  # Load the migration file, raising an exception if the file does not define
@@ -680,6 +704,13 @@ module Sequel
680
704
  @migration_tuples = get_migration_tuples
681
705
  end
682
706
 
707
+ # Apply the migration in the given file path. See Migrator.run for the
708
+ # available options. Additionally, this method supports the :direction
709
+ # option for whether to run the migration up (default) or down.
710
+ def self.run_single(db, path, opts=OPTS)
711
+ new(db, File.dirname(path), opts).run_single(path, opts[:direction] || :up)
712
+ end
713
+
683
714
  # The timestamp migrator is current if there are no migrations to apply
684
715
  # in either direction.
685
716
  def is_current?
@@ -689,20 +720,39 @@ module Sequel
689
720
  # Apply all migration tuples on the database
690
721
  def run
691
722
  migration_tuples.each do |m, f, direction|
692
- t = Time.now
693
- db.log_info("Begin applying migration #{f}, direction: #{direction}")
694
- checked_transaction(m) do
695
- m.apply(db, direction)
696
- fi = f.downcase
697
- direction == :up ? ds.insert(column=>fi) : ds.where(column=>fi).delete
698
- end
699
- db.log_info("Finished applying migration #{f}, direction: #{direction}, took #{sprintf('%0.6f', Time.now - t)} seconds")
723
+ apply_migration(m, f, direction)
700
724
  end
701
725
  nil
702
726
  end
703
727
 
728
+ # Apply single migration tuple at the given path with the given direction
729
+ # on the database.
730
+ def run_single(path, direction)
731
+ migration = load_migration_file(path)
732
+ file_name = File.basename(path)
733
+ already_applied = applied_migrations.include?(file_name.downcase)
734
+
735
+ return if direction == :up ? already_applied : !already_applied
736
+
737
+ apply_migration(migration, file_name, direction)
738
+ nil
739
+ end
740
+
704
741
  private
705
742
 
743
+ # Apply a single migration with the given filename in the given direction.
744
+ def apply_migration(migration, file_name, direction)
745
+ fi = file_name.downcase
746
+ t = Time.now
747
+
748
+ db.log_info("Begin applying migration #{file_name}, direction: #{direction}")
749
+ checked_transaction(migration) do
750
+ migration.apply(db, direction)
751
+ direction == :up ? ds.insert(column=>fi) : ds.where(column=>fi).delete
752
+ end
753
+ db.log_info("Finished applying migration #{file_name}, direction: #{direction}, took #{sprintf('%0.6f', Time.now - t)} seconds")
754
+ end
755
+
706
756
  # Convert the schema_info table to the new schema_migrations table format,
707
757
  # using the version of the schema_info table and the current migration files.
708
758
  def convert_from_schema_info
@@ -68,6 +68,10 @@ module Sequel
68
68
  private
69
69
 
70
70
  if RUBY_VERSION >= '2.6'
71
+ # Whether Time.at with :nsec and :in is broken. True on JRuby < 9.3.9.0.
72
+ BROKEN_TIME_AT_WITH_NSEC = defined?(JRUBY_VERSION) && (JRUBY_VERSION < '9.3' || (JRUBY_VERSION < '9.4' && JRUBY_VERSION.split('.')[2].to_i < 9))
73
+ private_constant :BROKEN_TIME_AT_WITH_NSEC
74
+
71
75
  # Convert the given input Time (which must be in UTC) to the given input timezone,
72
76
  # which should be a TZInfo::Timezone instance.
73
77
  def convert_input_time_other(v, input_timezone)
@@ -76,33 +80,45 @@ module Sequel
76
80
  raise unless disamb = tzinfo_disambiguator_for(v)
77
81
  period = input_timezone.period_for_local(v, &disamb)
78
82
  offset = period.utc_total_offset
79
- Time.at(v.to_i - offset, :in => input_timezone)
83
+ # :nocov:
84
+ if BROKEN_TIME_AT_WITH_NSEC
85
+ Time.at(v.to_i - offset, :in => input_timezone) + v.nsec/1000000000.0
86
+ # :nocov:
87
+ else
88
+ Time.at(v.to_i - offset, v.nsec, :nsec, :in => input_timezone)
89
+ end
80
90
  end
81
91
 
82
92
  # Convert the given input Time to the given output timezone,
83
93
  # which should be a TZInfo::Timezone instance.
84
94
  def convert_output_time_other(v, output_timezone)
85
- Time.at(v.to_i, :in => output_timezone)
95
+ # :nocov:
96
+ if BROKEN_TIME_AT_WITH_NSEC
97
+ Time.at(v.to_i, :in => output_timezone) + v.nsec/1000000000.0
98
+ # :nocov:
99
+ else
100
+ Time.at(v.to_i, v.nsec, :nsec, :in => output_timezone)
101
+ end
86
102
  end
87
103
  # :nodoc:
88
104
  # :nocov:
89
105
  else
90
106
  def convert_input_time_other(v, input_timezone)
91
107
  local_offset = input_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset
92
- Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i
108
+ Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i + v.nsec/1000000000.0
93
109
  end
94
110
 
95
111
  if defined?(TZInfo::VERSION) && TZInfo::VERSION > '2'
96
112
  def convert_output_time_other(v, output_timezone)
97
113
  v = output_timezone.utc_to_local(v.getutc)
98
114
  local_offset = output_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset
99
- Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i + local_offset
115
+ Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i + v.nsec/1000000000.0 + local_offset
100
116
  end
101
117
  else
102
118
  def convert_output_time_other(v, output_timezone)
103
119
  v = output_timezone.utc_to_local(v.getutc)
104
120
  local_offset = output_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset
105
- Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i
121
+ Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i + v.nsec/1000000000.0
106
122
  end
107
123
  end
108
124
  # :nodoc:
@@ -120,7 +136,7 @@ module Sequel
120
136
  v = output_timezone.utc_to_local(v.new_offset(0))
121
137
 
122
138
  # Force DateTime output instead of TZInfo::DateTimeWithOffset
123
- DateTime.jd(v.jd, v.hour, v.minute, v.second + v.sec_fraction, v.offset, v.start)
139
+ DateTime.civil(v.year, v.month, v.day, v.hour, v.minute, v.second + v.sec_fraction, v.offset, v.start)
124
140
  end
125
141
  # :nodoc:
126
142
  # :nocov:
@@ -228,16 +228,37 @@ module Sequel
228
228
  when Array
229
229
  "{#{a.map{|i| bound_variable_array(i)}.join(',')}}"
230
230
  when Sequel::SQL::Blob
231
- "\"#{literal(a)[BLOB_RANGE].gsub("''", "'").gsub(/("|\\)/, '\\\\\1')}\""
231
+ bound_variable_array_string(literal(a)[BLOB_RANGE].gsub("''", "'"))
232
232
  when Sequel::LiteralString
233
233
  a
234
234
  when String
235
- "\"#{a.gsub(/("|\\)/, '\\\\\1')}\""
235
+ bound_variable_array_string(a)
236
+ when Float
237
+ if a.infinite?
238
+ a > 0 ? '"Infinity"' : '"-Infinity"'
239
+ elsif a.nan?
240
+ '"NaN"'
241
+ else
242
+ literal(a)
243
+ end
244
+ when Time, Date
245
+ @default_dataset.literal_date_or_time(a)
236
246
  else
237
- literal(a)
247
+ if (s = bound_variable_arg(a, nil)).is_a?(String)
248
+ bound_variable_array_string(s)
249
+ else
250
+ literal(a)
251
+ end
238
252
  end
239
253
  end
240
254
 
255
+ # Escape strings used as array members in bound variables. Most complex
256
+ # will create a regular string with bound_variable_arg, and then use this
257
+ # escaping to format it as an array member.
258
+ def bound_variable_array_string(s)
259
+ "\"#{s.gsub(/("|\\)/, '\\\\\1')}\""
260
+ end
261
+
241
262
  # Look into both the current database's array schema types and the global
242
263
  # array schema types to get the type symbol for the given database type
243
264
  # string.
@@ -246,7 +267,7 @@ module Sequel
246
267
  end
247
268
 
248
269
  # Make the column type detection handle registered array types.
249
- def schema_column_type(db_type)
270
+ def schema_array_type(db_type)
250
271
  if (db_type =~ /\A([^(]+)(?:\([^(]+\))?\[\]\z/io) && (type = pg_array_schema_type($1))
251
272
  type
252
273
  else
@@ -457,6 +478,14 @@ module Sequel
457
478
  end
458
479
  end
459
480
 
481
+ # Allow automatic parameterization of the receiver if all elements can be
482
+ # can be automatically parameterized.
483
+ def sequel_auto_param_type(ds)
484
+ if array_type && all?{|x| nil == x || ds.send(:auto_param_type, x)}
485
+ "::#{array_type}[]"
486
+ end
487
+ end
488
+
460
489
  private
461
490
 
462
491
  # Recursive method that handles multi-dimensional