sequel 3.37.0 → 3.38.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 (129) hide show
  1. data/CHANGELOG +56 -0
  2. data/README.rdoc +82 -58
  3. data/Rakefile +6 -5
  4. data/bin/sequel +1 -1
  5. data/doc/active_record.rdoc +67 -52
  6. data/doc/advanced_associations.rdoc +33 -48
  7. data/doc/association_basics.rdoc +41 -51
  8. data/doc/cheat_sheet.rdoc +21 -21
  9. data/doc/core_extensions.rdoc +374 -0
  10. data/doc/dataset_basics.rdoc +5 -5
  11. data/doc/dataset_filtering.rdoc +47 -43
  12. data/doc/mass_assignment.rdoc +1 -1
  13. data/doc/migration.rdoc +4 -5
  14. data/doc/model_hooks.rdoc +3 -3
  15. data/doc/object_model.rdoc +31 -25
  16. data/doc/opening_databases.rdoc +19 -5
  17. data/doc/prepared_statements.rdoc +2 -2
  18. data/doc/querying.rdoc +109 -52
  19. data/doc/reflection.rdoc +6 -6
  20. data/doc/release_notes/3.38.0.txt +234 -0
  21. data/doc/schema_modification.rdoc +22 -13
  22. data/doc/sharding.rdoc +8 -9
  23. data/doc/sql.rdoc +154 -112
  24. data/doc/testing.rdoc +47 -7
  25. data/doc/thread_safety.rdoc +1 -1
  26. data/doc/transactions.rdoc +1 -1
  27. data/doc/validations.rdoc +1 -1
  28. data/doc/virtual_rows.rdoc +29 -43
  29. data/lib/sequel/adapters/do/postgres.rb +1 -4
  30. data/lib/sequel/adapters/jdbc.rb +14 -3
  31. data/lib/sequel/adapters/jdbc/db2.rb +9 -0
  32. data/lib/sequel/adapters/jdbc/derby.rb +41 -4
  33. data/lib/sequel/adapters/jdbc/jtds.rb +11 -0
  34. data/lib/sequel/adapters/jdbc/postgresql.rb +3 -6
  35. data/lib/sequel/adapters/mock.rb +10 -4
  36. data/lib/sequel/adapters/postgres.rb +1 -28
  37. data/lib/sequel/adapters/shared/mssql.rb +23 -13
  38. data/lib/sequel/adapters/shared/postgres.rb +46 -0
  39. data/lib/sequel/adapters/swift.rb +21 -13
  40. data/lib/sequel/adapters/swift/mysql.rb +1 -0
  41. data/lib/sequel/adapters/swift/postgres.rb +4 -5
  42. data/lib/sequel/adapters/swift/sqlite.rb +2 -1
  43. data/lib/sequel/adapters/tinytds.rb +14 -2
  44. data/lib/sequel/adapters/utils/pg_types.rb +5 -0
  45. data/lib/sequel/core.rb +29 -17
  46. data/lib/sequel/database/query.rb +1 -1
  47. data/lib/sequel/database/schema_generator.rb +3 -0
  48. data/lib/sequel/dataset/actions.rb +5 -6
  49. data/lib/sequel/dataset/query.rb +7 -7
  50. data/lib/sequel/dataset/sql.rb +5 -18
  51. data/lib/sequel/extensions/core_extensions.rb +8 -12
  52. data/lib/sequel/extensions/pg_array.rb +59 -33
  53. data/lib/sequel/extensions/pg_array_ops.rb +32 -4
  54. data/lib/sequel/extensions/pg_auto_parameterize.rb +1 -1
  55. data/lib/sequel/extensions/pg_hstore.rb +32 -17
  56. data/lib/sequel/extensions/pg_hstore_ops.rb +32 -3
  57. data/lib/sequel/extensions/pg_inet.rb +1 -2
  58. data/lib/sequel/extensions/pg_interval.rb +0 -1
  59. data/lib/sequel/extensions/pg_json.rb +41 -23
  60. data/lib/sequel/extensions/pg_range.rb +36 -11
  61. data/lib/sequel/extensions/pg_range_ops.rb +32 -4
  62. data/lib/sequel/extensions/pg_row.rb +572 -0
  63. data/lib/sequel/extensions/pg_row_ops.rb +164 -0
  64. data/lib/sequel/extensions/query.rb +3 -3
  65. data/lib/sequel/extensions/schema_dumper.rb +7 -8
  66. data/lib/sequel/extensions/select_remove.rb +1 -1
  67. data/lib/sequel/model/base.rb +1 -0
  68. data/lib/sequel/no_core_ext.rb +1 -1
  69. data/lib/sequel/plugins/pg_row.rb +121 -0
  70. data/lib/sequel/plugins/pg_typecast_on_load.rb +65 -0
  71. data/lib/sequel/plugins/validation_helpers.rb +31 -0
  72. data/lib/sequel/sql.rb +64 -44
  73. data/lib/sequel/version.rb +1 -1
  74. data/spec/adapters/mssql_spec.rb +37 -12
  75. data/spec/adapters/mysql_spec.rb +39 -75
  76. data/spec/adapters/oracle_spec.rb +11 -11
  77. data/spec/adapters/postgres_spec.rb +414 -237
  78. data/spec/adapters/spec_helper.rb +1 -1
  79. data/spec/adapters/sqlite_spec.rb +14 -14
  80. data/spec/core/database_spec.rb +6 -6
  81. data/spec/core/dataset_spec.rb +169 -205
  82. data/spec/core/expression_filters_spec.rb +182 -295
  83. data/spec/core/object_graph_spec.rb +6 -6
  84. data/spec/core/schema_spec.rb +14 -14
  85. data/spec/core/spec_helper.rb +1 -0
  86. data/spec/{extensions/core_extensions_spec.rb → core_extensions_spec.rb} +208 -14
  87. data/spec/extensions/columns_introspection_spec.rb +5 -5
  88. data/spec/extensions/hook_class_methods_spec.rb +28 -36
  89. data/spec/extensions/many_through_many_spec.rb +4 -4
  90. data/spec/extensions/pg_array_ops_spec.rb +15 -7
  91. data/spec/extensions/pg_array_spec.rb +81 -48
  92. data/spec/extensions/pg_auto_parameterize_spec.rb +2 -2
  93. data/spec/extensions/pg_hstore_ops_spec.rb +13 -9
  94. data/spec/extensions/pg_hstore_spec.rb +66 -65
  95. data/spec/extensions/pg_inet_spec.rb +2 -4
  96. data/spec/extensions/pg_interval_spec.rb +2 -3
  97. data/spec/extensions/pg_json_spec.rb +20 -18
  98. data/spec/extensions/pg_range_ops_spec.rb +11 -4
  99. data/spec/extensions/pg_range_spec.rb +30 -7
  100. data/spec/extensions/pg_row_ops_spec.rb +48 -0
  101. data/spec/extensions/pg_row_plugin_spec.rb +45 -0
  102. data/spec/extensions/pg_row_spec.rb +323 -0
  103. data/spec/extensions/pg_typecast_on_load_spec.rb +58 -0
  104. data/spec/extensions/query_literals_spec.rb +11 -11
  105. data/spec/extensions/query_spec.rb +3 -3
  106. data/spec/extensions/schema_dumper_spec.rb +20 -4
  107. data/spec/extensions/schema_spec.rb +18 -41
  108. data/spec/extensions/select_remove_spec.rb +4 -4
  109. data/spec/extensions/spec_helper.rb +4 -8
  110. data/spec/extensions/to_dot_spec.rb +5 -5
  111. data/spec/extensions/validation_class_methods_spec.rb +28 -16
  112. data/spec/integration/associations_test.rb +20 -20
  113. data/spec/integration/dataset_test.rb +98 -98
  114. data/spec/integration/eager_loader_test.rb +13 -27
  115. data/spec/integration/plugin_test.rb +5 -5
  116. data/spec/integration/prepared_statement_test.rb +22 -13
  117. data/spec/integration/schema_test.rb +28 -18
  118. data/spec/integration/spec_helper.rb +1 -1
  119. data/spec/integration/timezone_test.rb +2 -2
  120. data/spec/integration/type_test.rb +15 -6
  121. data/spec/model/association_reflection_spec.rb +1 -1
  122. data/spec/model/associations_spec.rb +4 -4
  123. data/spec/model/base_spec.rb +5 -5
  124. data/spec/model/eager_loading_spec.rb +15 -15
  125. data/spec/model/model_spec.rb +32 -32
  126. data/spec/model/record_spec.rb +16 -0
  127. data/spec/model/spec_helper.rb +2 -6
  128. data/spec/model/validations_spec.rb +1 -1
  129. metadata +16 -4
@@ -39,6 +39,11 @@ module Sequel
39
39
  end
40
40
  end.new
41
41
 
42
+ # Type OIDs for string types used by PostgreSQL. These types don't
43
+ # have conversion procs associated with them (since the data is
44
+ # already in the form of a string).
45
+ STRING_TYPES = [18, 19, 25, 1042, 1043]
46
+
42
47
  # Hash with type name strings/symbols and callable values for converting PostgreSQL types.
43
48
  # Non-builtin types that don't have fixed numbers should use this to register
44
49
  # conversion procs.
data/lib/sequel/core.rb CHANGED
@@ -147,23 +147,11 @@ module Sequel
147
147
  Database.connect(*args, &block)
148
148
  end
149
149
 
150
- if !defined?(::SEQUEL_NO_CORE_EXTENSIONS) && !ENV.has_key?('SEQUEL_NO_CORE_EXTENSIONS')
151
- # Whether the core extensions are enabled. The core extensions are enabled by
152
- # default for backwards compatibility, but can be disabled using the SEQUEL_NO_CORE_EXTENSIONS
153
- # constant or environment variable.
154
- def self.core_extensions?
155
- # We override this method to return true inside the core_extensions.rb file,
156
- # but we also set it here because that file is not loaded until most of Sequel
157
- # is finished loading, and parts of Sequel check whether the core extensions
158
- # are loaded.
159
- true
160
- end
161
- else
162
- def self.core_extensions?
163
- false
164
- end
150
+ # Assume the core extensions are not loaded by default, if the core_extensions
151
+ # extension is loaded, this will be overridden.
152
+ def self.core_extensions?
153
+ false
165
154
  end
166
-
167
155
 
168
156
  # Convert the +exception+ to the given class. The given class should be
169
157
  # <tt>Sequel::Error</tt> or a subclass. Returns an instance of +klass+ with
@@ -262,6 +250,28 @@ module Sequel
262
250
  Database.single_threaded = value
263
251
  end
264
252
 
253
+ COLUMN_REF_RE1 = /\A((?:(?!__).)+)__((?:(?!___).)+)___(.+)\z/.freeze
254
+ COLUMN_REF_RE2 = /\A((?:(?!___).)+)___(.+)\z/.freeze
255
+ COLUMN_REF_RE3 = /\A((?:(?!__).)+)__(.+)\z/.freeze
256
+
257
+ # Splits the symbol into three parts. Each part will
258
+ # either be a string or nil.
259
+ #
260
+ # For columns, these parts are the table, column, and alias.
261
+ # For tables, these parts are the schema, table, and alias.
262
+ def self.split_symbol(sym)
263
+ case s = sym.to_s
264
+ when COLUMN_REF_RE1
265
+ [$1, $2, $3]
266
+ when COLUMN_REF_RE2
267
+ [nil, $1, $2]
268
+ when COLUMN_REF_RE3
269
+ [$1, $2, nil]
270
+ else
271
+ [nil, s, nil]
272
+ end
273
+ end
274
+
265
275
  # Converts the given +string+ into a +Date+ object.
266
276
  #
267
277
  # Sequel.string_to_date('2010-09-10') # Date.civil(2010, 09, 10)
@@ -413,7 +423,9 @@ module Sequel
413
423
  private_class_method :adapter_method, :def_adapter_method
414
424
 
415
425
  require(%w"metaprogramming sql connection_pool exceptions dataset database timezones ast_transformer version")
416
- extension(:core_extensions) if Sequel.core_extensions?
426
+ if !defined?(::SEQUEL_NO_CORE_EXTENSIONS) && !ENV.has_key?('SEQUEL_NO_CORE_EXTENSIONS')
427
+ extension(:core_extensions)
428
+ end
417
429
 
418
430
  # Add the database adapter class methods to Sequel via metaprogramming
419
431
  def_adapter_method(*Database::ADAPTERS)
@@ -582,7 +582,7 @@ module Sequel
582
582
  :time
583
583
  when /\A(bool(ean)?)\z/io
584
584
  :boolean
585
- when /\A(real|float|double( precision)?)\z/io
585
+ when /\A(real|float|double( precision)?|double\(\d+,\d+\)( unsigned)?)\z/io
586
586
  :float
587
587
  when /\A(?:(?:(?:num(?:ber|eric)?|decimal)(?:\(\d+,\s*(\d+|false|true)\))?)|(?:small)?money)\z/io
588
588
  $1 && ['0', 'false'].include?($1) ? :integer : :decimal
@@ -98,6 +98,8 @@ module Sequel
98
98
  # (:restrict, :cascade, :set_null, :set_default, :no_action).
99
99
  # :primary_key :: Make the column as a single primary key column. This should only
100
100
  # be used if you have a single, nonautoincrementing primary key column.
101
+ # :type :: Overrides the type given as the argument. Generally not used by column
102
+ # itself, but can be passed as an option to other methods that call column.
101
103
  # :unique :: Mark the column as unique, generally has the same effect as
102
104
  # creating a unique index on the column.
103
105
  def column(name, type, opts = {})
@@ -122,6 +124,7 @@ module Sequel
122
124
  # foreign_key(:artist_id) # artist_id INTEGER
123
125
  # foreign_key(:artist_id, :artists) # artist_id INTEGER REFERENCES artists
124
126
  # foreign_key(:artist_id, :artists, :key=>:id) # artist_id INTEGER REFERENCES artists(id)
127
+ # foreign_key(:artist_id, :artists, :type=>String) # artist_id varchar(255) REFERENCES artists(id)
125
128
  #
126
129
  # If you want a foreign key constraint without adding a column (usually because it is a
127
130
  # composite foreign key), you can provide an array of columns as the first argument, and
@@ -340,7 +340,7 @@ module Sequel
340
340
  # DB[:table].order(:id).last # SELECT * FROM table ORDER BY id DESC LIMIT 1
341
341
  # # => {:id=>10}
342
342
  #
343
- # DB[:table].order(:id.desc).last(2) # SELECT * FROM table ORDER BY id ASC LIMIT 2
343
+ # DB[:table].order(Sequel.desc(:id)).last(2) # SELECT * FROM table ORDER BY id ASC LIMIT 2
344
344
  # # => [{:id=>1}, {:id=>2}]
345
345
  def last(*args, &block)
346
346
  raise(Error, 'No order specified') unless @opts[:order]
@@ -768,7 +768,7 @@ module Sequel
768
768
  when Symbol
769
769
  _, c, a = split_symbol(s)
770
770
  (a || c).to_sym
771
- when SQL::Identifier
771
+ when SQL::Identifier, SQL::Wrapper
772
772
  hash_key_symbol(s.value)
773
773
  when SQL::QualifiedIdentifier
774
774
  hash_key_symbol(s.column)
@@ -820,10 +820,9 @@ module Sequel
820
820
  when SQL::AliasedExpression
821
821
  c.expression
822
822
  when SQL::OrderedExpression
823
- expr = c.expression
824
- if expr.is_a?(Symbol)
825
- expr = unaliased_identifier(expr)
826
- SQL::OrderedExpression.new(unaliased_identifier(c.expression), c.descending, :nulls=>c.nulls)
823
+ case expr = c.expression
824
+ when Symbol, SQL::AliasedExpression
825
+ SQL::OrderedExpression.new(unaliased_identifier(expr), c.descending, :nulls=>c.nulls)
827
826
  else
828
827
  c
829
828
  end
@@ -500,7 +500,7 @@ module Sequel
500
500
  # # SELECT * FROM a LEFT JOIN b AS c USING (d)
501
501
  #
502
502
  # DB[:a].natural_join(:b).join_table(:inner, :c) do |ta, jta, js|
503
- # (:d.qualify(ta) > :e.qualify(jta)) & {:f.qualify(ta)=>DB.from(js.first.table).select(:g)}
503
+ # (Sequel.qualify(ta, :d) > Sequel.qualify(jta, :e)) & {Sequel.qualify(ta, :f)=>DB.from(js.first.table).select(:g)}
504
504
  # end
505
505
  # # SELECT * FROM a NATURAL JOIN b INNER JOIN c
506
506
  # # ON ((c.d > b.e) AND (c.f IN (SELECT g FROM b)))
@@ -657,10 +657,10 @@ module Sequel
657
657
  #
658
658
  # DB[:items].order(:name) # SELECT * FROM items ORDER BY name
659
659
  # DB[:items].order(:a, :b) # SELECT * FROM items ORDER BY a, b
660
- # DB[:items].order('a + b'.lit) # SELECT * FROM items ORDER BY a + b
660
+ # DB[:items].order(Sequel.lit('a + b')) # SELECT * FROM items ORDER BY a + b
661
661
  # DB[:items].order(:a + :b) # SELECT * FROM items ORDER BY (a + b)
662
- # DB[:items].order(:name.desc) # SELECT * FROM items ORDER BY name DESC
663
- # DB[:items].order(:name.asc(:nulls=>:last)) # SELECT * FROM items ORDER BY name ASC NULLS LAST
662
+ # DB[:items].order(Sequel.desc(:name)) # SELECT * FROM items ORDER BY name DESC
663
+ # DB[:items].order(Sequel.asc(:name, :nulls=>:last)) # SELECT * FROM items ORDER BY name ASC NULLS LAST
664
664
  # DB[:items].order{sum(name).desc} # SELECT * FROM items ORDER BY sum(name) DESC
665
665
  # DB[:items].order(nil) # SELECT * FROM items
666
666
  def order(*columns, &block)
@@ -756,7 +756,7 @@ module Sequel
756
756
  #
757
757
  # DB[:items].reverse(:id) # SELECT * FROM items ORDER BY id DESC
758
758
  # DB[:items].order(:id).reverse # SELECT * FROM items ORDER BY id DESC
759
- # DB[:items].order(:id).reverse(:name.asc) # SELECT * FROM items ORDER BY name ASC
759
+ # DB[:items].order(:id).reverse(Sequel.desc(:name)) # SELECT * FROM items ORDER BY name ASC
760
760
  def reverse(*order)
761
761
  order(*invert_order(order.empty? ? @opts[:order] : order))
762
762
  end
@@ -1119,8 +1119,8 @@ module Sequel
1119
1119
  # Inverts the given order by breaking it into a list of column references
1120
1120
  # and inverting them.
1121
1121
  #
1122
- # DB[:items].invert_order([:id.desc]]) #=> [:id]
1123
- # DB[:items].invert_order(:category, :price.desc]) #=> [:category.desc, :price]
1122
+ # DB[:items].invert_order([Sequel.desc(:id)]]) #=> [Sequel.asc(:id)]
1123
+ # DB[:items].invert_order([:category, Sequel.desc(:price)]) #=> [Sequel.desc(:category), Sequel.asc(:price)]
1124
1124
  def invert_order(order)
1125
1125
  return nil unless order
1126
1126
  new_order = []
@@ -200,9 +200,9 @@ module Sequel
200
200
  CASE_THEN = " THEN ".freeze
201
201
  CASE_WHEN = " WHEN ".freeze
202
202
  CAST_OPEN = 'CAST('.freeze
203
- COLUMN_REF_RE1 = /\A((?:(?!__).)+)__((?:(?!___).)+)___(.+)\z/.freeze
204
- COLUMN_REF_RE2 = /\A((?:(?!___).)+)___(.+)\z/.freeze
205
- COLUMN_REF_RE3 = /\A((?:(?!__).)+)__(.+)\z/.freeze
203
+ COLUMN_REF_RE1 = Sequel::COLUMN_REF_RE1
204
+ COLUMN_REF_RE2 = Sequel::COLUMN_REF_RE2
205
+ COLUMN_REF_RE3 = Sequel::COLUMN_REF_RE3
206
206
  COMMA = ', '.freeze
207
207
  COMMA_SEPARATOR = COMMA
208
208
  CONDITION_FALSE = '(1 = 0)'.freeze
@@ -1320,22 +1320,9 @@ module Sequel
1320
1320
  end
1321
1321
  end
1322
1322
 
1323
- # Splits the symbol into three parts. Each part will
1324
- # either be a string or nil.
1325
- #
1326
- # For columns, these parts are the table, column, and alias.
1327
- # For tables, these parts are the schema, table, and alias.
1323
+ # Delegate to Sequel.split_symbol.
1328
1324
  def split_symbol(sym)
1329
- case s = sym.to_s
1330
- when COLUMN_REF_RE1
1331
- [$1, $2, $3]
1332
- when COLUMN_REF_RE2
1333
- [nil, $1, $2]
1334
- when COLUMN_REF_RE3
1335
- [$1, $2, nil]
1336
- else
1337
- [nil, s, nil]
1338
- end
1325
+ Sequel.split_symbol(sym)
1339
1326
  end
1340
1327
 
1341
1328
  # The string that is appended to to create the SQL query, the empty
@@ -199,28 +199,24 @@ end
199
199
 
200
200
  # Sequel extends +Symbol+ to add methods to implement the SQL DSL.
201
201
  class Symbol
202
- include Sequel::SQL::QualifyingMethods
203
- include Sequel::SQL::IdentifierMethods
204
202
  include Sequel::SQL::AliasMethods
205
203
  include Sequel::SQL::CastMethods
206
204
  include Sequel::SQL::OrderMethods
207
205
  include Sequel::SQL::BooleanMethods
208
206
  include Sequel::SQL::NumericMethods
207
+ include Sequel::SQL::QualifyingMethods
209
208
  include Sequel::SQL::StringMethods
210
209
  include Sequel::SQL::SubscriptMethods
211
210
  include Sequel::SQL::ComplexExpressionMethods
212
211
  include Sequel::SQL::InequalityMethods if RUBY_VERSION < '1.9.0'
213
212
 
214
- # If no argument is given, returns a <tt>Sequel::SQL::ColumnAll</tt> object specifying all
215
- # columns for this table.
216
- # If an argument is given, returns a <tt>Sequel::SQL::NumericExpression</tt> using the *
217
- # (multiplication) operator with this and the given argument.
218
- #
219
- # :table.* # SQL: table.*
220
- # :column * 2 # SQL: column * 2
221
- def *(ce=(arg=false;nil))
222
- return super(ce) unless arg == false
223
- Sequel::SQL::ColumnAll.new(self);
213
+ # Returns receiver wrapped in an <tt>Sequel::SQL::Identifier</tt>. Usually used to
214
+ # prevent splitting the symbol.
215
+ #
216
+ # :a__b # SQL: "a"."b"
217
+ # :a__b.identifier # SQL: "a__b"
218
+ def identifier
219
+ Sequel::SQL::Identifier.new(self)
224
220
  end
225
221
 
226
222
  # Returns a <tt>Sequel::SQL::Function</tt> with this as the function name,
@@ -15,15 +15,21 @@
15
15
  #
16
16
  # To turn an existing Array into a PGArray:
17
17
  #
18
+ # Sequel.pg_array(array)
19
+ #
20
+ # If you have loaded the {core_extensions extension}[link:files/doc/core_extensions_rdoc.html]),
21
+ # you can also use Array#pg_array:
22
+ #
18
23
  # array.pg_array
19
24
  #
20
25
  # You can also provide a type, though it many cases it isn't necessary:
21
26
  #
27
+ # Sequel.pg_array(array, :varchar) # or :integer, :"double precision", etc.
22
28
  # array.pg_array(:varchar) # or :integer, :"double precision", etc.
23
29
  #
24
30
  # So if you want to insert an array into an integer[] database column:
25
31
  #
26
- # DB[:table].insert(:column=>[1, 2, 3].pg_array)
32
+ # DB[:table].insert(:column=>Sequel.pg_array([1, 2, 3]))
27
33
  #
28
34
  # If you would like to use PostgreSQL arrays in your model objects, you
29
35
  # probably want to modify the schema parsing/typecasting so that it
@@ -135,6 +141,8 @@ module Sequel
135
141
  # typecast method. For example, for an array of integers, this could be set to
136
142
  # :integer, so that the typecast_value_integer method is called on all of the
137
143
  # array elements. Defaults to :type_symbol option.
144
+ # :type_procs :: A hash mapping oids to conversion procs, used for looking up the :scalar_oid and
145
+ # value and setting the :oid value. Defaults to the global Sequel::Postgres::PG_TYPES.
138
146
  # :type_symbol :: The base of the schema type symbol for this type. For example, if you provide
139
147
  # :integer, Sequel will recognize this type as :integer_array during schema parsing.
140
148
  # Defaults to the db_type argument.
@@ -142,12 +150,16 @@ module Sequel
142
150
  # typecasting method to be created in the database. This should only be used
143
151
  # to alias existing array types. For example, if there is an array type that can be
144
152
  # treated just like an integer array, you can do :typecast_method=>:integer.
153
+ # :typecast_methods_module :: If given, a module object to add the typecasting method to. Defaults
154
+ # to DatabaseMethods.
145
155
  #
146
156
  # If a block is given, it is treated as the :converter option.
147
157
  def self.register(db_type, opts={}, &block)
148
158
  db_type = db_type.to_s
149
159
  typecast_method = opts[:typecast_method]
150
160
  type = (typecast_method || opts[:type_symbol] || db_type).to_sym
161
+ type_procs = opts[:type_procs] || PG_TYPES
162
+ mod = opts[:typecast_methods_module] || DatabaseMethods
151
163
 
152
164
  if converter = opts[:converter]
153
165
  raise Error, "can't provide both a block and :converter option to register" if block
@@ -157,7 +169,7 @@ module Sequel
157
169
 
158
170
  if soid = opts[:scalar_oid]
159
171
  raise Error, "can't provide both a converter and :scalar_oid option to register" if converter
160
- raise Error, "no conversion proc for :scalar_oid=>#{soid.inspect} in PG_TYPES" unless converter = PG_TYPES[soid]
172
+ raise Error, "no conversion proc for :scalar_oid=>#{soid.inspect}" unless converter = type_procs[soid]
161
173
  end
162
174
 
163
175
  array_type = (opts[:array_type] || db_type).to_s.dup.freeze
@@ -165,15 +177,27 @@ module Sequel
165
177
 
166
178
  ARRAY_TYPES[db_type] = :"#{type}_array"
167
179
 
168
- DatabaseMethods.define_array_typecast_method(type, creator, opts.fetch(:scalar_typecast, type)) unless typecast_method
180
+ define_array_typecast_method(mod, type, creator, opts.fetch(:scalar_typecast, type)) unless typecast_method
169
181
 
170
182
  if oid = opts[:oid]
171
- Sequel::Postgres::PG_TYPES[oid] = creator
183
+ type_procs[oid] = creator
172
184
  end
173
185
 
174
186
  nil
175
187
  end
176
188
 
189
+ # Define a private array typecasting method in the given module for the given type that uses
190
+ # the creator argument to do the type conversion.
191
+ def self.define_array_typecast_method(mod, type, creator, scalar_typecast)
192
+ mod.class_eval do
193
+ meth = :"typecast_value_#{type}_array"
194
+ scalar_typecast_method = :"typecast_value_#{scalar_typecast}"
195
+ define_method(meth){|v| typecast_value_pg_array(v, creator, scalar_typecast_method)}
196
+ private meth
197
+ end
198
+ end
199
+ private_class_method :define_array_typecast_method
200
+
177
201
  module DatabaseMethods
178
202
  APOS = "'".freeze
179
203
  DOUBLE_APOS = "''".freeze
@@ -181,22 +205,6 @@ module Sequel
181
205
  ESCAPE_REPLACEMENT = '\\\\\1'.freeze
182
206
  BLOB_RANGE = 1...-1
183
207
 
184
- # Reset the conversion procs when extending the Database object, so
185
- # it will pick up the array convertors. This is only done for the native
186
- # postgres adapter.
187
- def self.extended(db)
188
- db.reset_conversion_procs if db.respond_to?(:reset_conversion_procs)
189
- end
190
-
191
- # Define a private array typecasting method for the given type that uses
192
- # the creator argument to do the type conversion.
193
- def self.define_array_typecast_method(type, creator, scalar_typecast)
194
- meth = :"typecast_value_#{type}_array"
195
- scalar_typecast_method = :"typecast_value_#{scalar_typecast}"
196
- define_method(meth){|v| typecast_value_pg_array(v, creator, scalar_typecast_method)}
197
- private meth
198
- end
199
-
200
208
  # Handle arrays in bound variables
201
209
  def bound_variable_arg(arg, conn)
202
210
  case arg
@@ -239,12 +247,11 @@ module Sequel
239
247
  # Manually override the typecasting for timestamp array types so that
240
248
  # they use the database's timezone instead of the global Sequel
241
249
  # timezone.
242
- def get_conversion_procs(conn)
250
+ def get_conversion_procs
243
251
  procs = super
244
252
 
245
- converter = method(:to_application_timestamp)
246
- procs[1115] = Creator.new("timestamp without time zone", converter)
247
- procs[1185] = Creator.new("timestamp with time zone", converter)
253
+ procs[1115] = Creator.new("timestamp without time zone", procs[1114])
254
+ procs[1185] = Creator.new("timestamp with time zone", procs[1184])
248
255
 
249
256
  procs
250
257
  end
@@ -271,8 +278,6 @@ module Sequel
271
278
  value = Sequel.recursive_map(value, method(scalar_typecast_method))
272
279
  end
273
280
  PGArray.new(value, creator.type)
274
- when String
275
- creator.call(value)
276
281
  else
277
282
  raise Sequel::InvalidValue, "invalid value for array type: #{value.inspect}"
278
283
  end
@@ -497,15 +502,36 @@ module Sequel
497
502
  end
498
503
  end
499
504
 
505
+ module SQL::Builders
506
+ # Return a Postgres::PGArray proxy for the given array and database array type.
507
+ def pg_array(v, array_type=nil)
508
+ case v
509
+ when Postgres::PGArray
510
+ if array_type.nil? || v.array_type == array_type
511
+ v
512
+ else
513
+ Postgres::PGArray.new(v.to_a, array_type)
514
+ end
515
+ when Array
516
+ Postgres::PGArray.new(v, array_type)
517
+ else
518
+ # May not be defined unless the pg_array_ops extension is used
519
+ pg_array_op(v)
520
+ end
521
+ end
522
+ end
523
+
500
524
  Database.register_extension(:pg_array, Postgres::PGArray::DatabaseMethods)
501
525
  end
502
526
 
503
- class Array
504
- # Return a PGArray proxy to the receiver, using a
505
- # specific database type if given. This is mostly useful
506
- # as a short cut for creating PGArray objects that didn't
507
- # come from the database.
508
- def pg_array(type=nil)
509
- Sequel::Postgres::PGArray.new(self, type)
527
+ if Sequel.core_extensions?
528
+ class Array
529
+ # Return a PGArray proxy to the receiver, using a
530
+ # specific database type if given. This is mostly useful
531
+ # as a short cut for creating PGArray objects that didn't
532
+ # come from the database.
533
+ def pg_array(type=nil)
534
+ Sequel::Postgres::PGArray.new(self, type)
535
+ end
510
536
  end
511
537
  end
@@ -5,8 +5,22 @@
5
5
  #
6
6
  # Sequel.extension :pg_array_ops
7
7
  #
8
- # The most common usage is taking an object that represents an SQL
9
- # identifier (such as a :symbol), and calling #pg_array on it:
8
+ # The most common usage is passing an expression to Sequel.pg_array_op:
9
+ #
10
+ # ia = Sequel.pg_array_op(:int_array_column)
11
+ #
12
+ # If you have also loaded the pg_array extension, you can use
13
+ # Sequel.pg_array as well:
14
+ #
15
+ # ia = Sequel.pg_array(:int_array_column)
16
+ #
17
+ # Also, on most Sequel expression objects, you can call the pg_array
18
+ # method:
19
+ #
20
+ # ia = Sequel.expr(:int_array_column).pg_array
21
+ #
22
+ # If you have loaded the {core_extensions extension}[link:files/doc/core_extensions_rdoc.html]),
23
+ # you can also call Symbol#pg_array:
10
24
  #
11
25
  # ia = :int_array_column.pg_array
12
26
  #
@@ -211,6 +225,18 @@ module Sequel
211
225
  end
212
226
  end
213
227
 
228
+ module SQL::Builders
229
+ # Return the object wrapped in an Postgres::ArrayOp.
230
+ def pg_array_op(v)
231
+ case v
232
+ when Postgres::ArrayOp
233
+ v
234
+ else
235
+ Postgres::ArrayOp.new(v)
236
+ end
237
+ end
238
+ end
239
+
214
240
  class SQL::GenericExpression
215
241
  include Sequel::Postgres::ArrayOpMethods
216
242
  end
@@ -220,6 +246,8 @@ module Sequel
220
246
  end
221
247
  end
222
248
 
223
- class Symbol
224
- include Sequel::Postgres::ArrayOpMethods
249
+ if Sequel.core_extensions?
250
+ class Symbol
251
+ include Sequel::Postgres::ArrayOpMethods
252
+ end
225
253
  end