sequel 5.58.0 → 5.78.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 (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