sequel 4.9.0 → 4.10.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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +79 -1
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/Rakefile +2 -12
  6. data/bin/sequel +1 -0
  7. data/doc/advanced_associations.rdoc +82 -25
  8. data/doc/association_basics.rdoc +21 -22
  9. data/doc/core_extensions.rdoc +1 -1
  10. data/doc/opening_databases.rdoc +7 -0
  11. data/doc/release_notes/4.10.0.txt +226 -0
  12. data/doc/security.rdoc +1 -0
  13. data/doc/testing.rdoc +7 -7
  14. data/doc/transactions.rdoc +8 -0
  15. data/lib/sequel/adapters/jdbc.rb +160 -168
  16. data/lib/sequel/adapters/jdbc/db2.rb +17 -18
  17. data/lib/sequel/adapters/jdbc/derby.rb +5 -28
  18. data/lib/sequel/adapters/jdbc/h2.rb +11 -22
  19. data/lib/sequel/adapters/jdbc/hsqldb.rb +31 -18
  20. data/lib/sequel/adapters/jdbc/jtds.rb +0 -15
  21. data/lib/sequel/adapters/jdbc/oracle.rb +36 -35
  22. data/lib/sequel/adapters/jdbc/postgresql.rb +72 -90
  23. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +18 -16
  24. data/lib/sequel/adapters/jdbc/sqlite.rb +7 -0
  25. data/lib/sequel/adapters/jdbc/sqlserver.rb +10 -30
  26. data/lib/sequel/adapters/jdbc/transactions.rb +5 -6
  27. data/lib/sequel/adapters/openbase.rb +1 -7
  28. data/lib/sequel/adapters/postgres.rb +1 -1
  29. data/lib/sequel/adapters/shared/access.rb +3 -6
  30. data/lib/sequel/adapters/shared/cubrid.rb +24 -9
  31. data/lib/sequel/adapters/shared/db2.rb +13 -5
  32. data/lib/sequel/adapters/shared/firebird.rb +16 -16
  33. data/lib/sequel/adapters/shared/informix.rb +2 -5
  34. data/lib/sequel/adapters/shared/mssql.rb +72 -63
  35. data/lib/sequel/adapters/shared/mysql.rb +72 -40
  36. data/lib/sequel/adapters/shared/oracle.rb +27 -15
  37. data/lib/sequel/adapters/shared/postgres.rb +24 -44
  38. data/lib/sequel/adapters/shared/progress.rb +1 -5
  39. data/lib/sequel/adapters/shared/sqlanywhere.rb +26 -18
  40. data/lib/sequel/adapters/shared/sqlite.rb +21 -6
  41. data/lib/sequel/adapters/utils/emulate_offset_with_reverse_and_count.rb +8 -1
  42. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +8 -2
  43. data/lib/sequel/adapters/utils/split_alter_table.rb +8 -0
  44. data/lib/sequel/core.rb +14 -9
  45. data/lib/sequel/database/dataset_defaults.rb +1 -0
  46. data/lib/sequel/database/misc.rb +12 -0
  47. data/lib/sequel/database/query.rb +4 -1
  48. data/lib/sequel/database/schema_methods.rb +3 -2
  49. data/lib/sequel/database/transactions.rb +47 -17
  50. data/lib/sequel/dataset/features.rb +12 -2
  51. data/lib/sequel/dataset/mutation.rb +2 -0
  52. data/lib/sequel/dataset/placeholder_literalizer.rb +12 -4
  53. data/lib/sequel/dataset/prepared_statements.rb +6 -0
  54. data/lib/sequel/dataset/query.rb +1 -1
  55. data/lib/sequel/dataset/sql.rb +132 -70
  56. data/lib/sequel/extensions/columns_introspection.rb +1 -1
  57. data/lib/sequel/extensions/null_dataset.rb +8 -4
  58. data/lib/sequel/extensions/pg_array.rb +4 -4
  59. data/lib/sequel/extensions/pg_row.rb +1 -0
  60. data/lib/sequel/model/associations.rb +468 -188
  61. data/lib/sequel/model/base.rb +88 -13
  62. data/lib/sequel/plugins/association_pks.rb +23 -64
  63. data/lib/sequel/plugins/auto_validations.rb +3 -2
  64. data/lib/sequel/plugins/dataset_associations.rb +1 -3
  65. data/lib/sequel/plugins/many_through_many.rb +18 -65
  66. data/lib/sequel/plugins/pg_array_associations.rb +97 -86
  67. data/lib/sequel/plugins/prepared_statements.rb +2 -1
  68. data/lib/sequel/plugins/prepared_statements_associations.rb +36 -27
  69. data/lib/sequel/plugins/rcte_tree.rb +12 -16
  70. data/lib/sequel/plugins/sharding.rb +21 -3
  71. data/lib/sequel/plugins/single_table_inheritance.rb +2 -1
  72. data/lib/sequel/plugins/subclasses.rb +1 -9
  73. data/lib/sequel/plugins/tactical_eager_loading.rb +9 -0
  74. data/lib/sequel/plugins/tree.rb +2 -2
  75. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  76. data/lib/sequel/version.rb +1 -1
  77. data/spec/adapters/mssql_spec.rb +57 -15
  78. data/spec/adapters/mysql_spec.rb +11 -0
  79. data/spec/bin_spec.rb +2 -2
  80. data/spec/core/database_spec.rb +38 -4
  81. data/spec/core/dataset_spec.rb +45 -7
  82. data/spec/core/placeholder_literalizer_spec.rb +17 -0
  83. data/spec/core/schema_spec.rb +6 -1
  84. data/spec/extensions/active_model_spec.rb +18 -9
  85. data/spec/extensions/association_pks_spec.rb +20 -18
  86. data/spec/extensions/association_proxies_spec.rb +9 -9
  87. data/spec/extensions/auto_validations_spec.rb +6 -0
  88. data/spec/extensions/columns_introspection_spec.rb +1 -0
  89. data/spec/extensions/constraint_validations_spec.rb +3 -1
  90. data/spec/extensions/many_through_many_spec.rb +191 -111
  91. data/spec/extensions/pg_array_associations_spec.rb +133 -103
  92. data/spec/extensions/prepared_statements_associations_spec.rb +23 -4
  93. data/spec/extensions/rcte_tree_spec.rb +35 -27
  94. data/spec/extensions/sequel_3_dataset_methods_spec.rb +0 -1
  95. data/spec/extensions/sharding_spec.rb +2 -2
  96. data/spec/extensions/tactical_eager_loading_spec.rb +4 -0
  97. data/spec/extensions/to_dot_spec.rb +1 -0
  98. data/spec/extensions/touch_spec.rb +2 -2
  99. data/spec/integration/associations_test.rb +130 -37
  100. data/spec/integration/dataset_test.rb +17 -0
  101. data/spec/integration/model_test.rb +17 -0
  102. data/spec/integration/schema_test.rb +14 -0
  103. data/spec/integration/transaction_test.rb +25 -1
  104. data/spec/model/association_reflection_spec.rb +63 -24
  105. data/spec/model/associations_spec.rb +104 -57
  106. data/spec/model/base_spec.rb +14 -1
  107. data/spec/model/class_dataset_methods_spec.rb +1 -0
  108. data/spec/model/eager_loading_spec.rb +221 -74
  109. data/spec/model/model_spec.rb +119 -1
  110. metadata +4 -2
@@ -3,6 +3,16 @@ Sequel.require 'adapters/jdbc/transactions'
3
3
 
4
4
  module Sequel
5
5
  module JDBC
6
+ class TypeConvertor
7
+ def DB2Clob(r, i)
8
+ if v = r.getClob(i)
9
+ v = v.getSubString(1, v.length)
10
+ v = Sequel::SQL::Blob.new(v) if ::Sequel::DB2::use_clob_as_blob
11
+ v
12
+ end
13
+ end
14
+ end
15
+
6
16
  class Database
7
17
  # Alias the generic JDBC versions so they can be called directly later
8
18
  alias jdbc_schema_parse_table schema_parse_table
@@ -55,28 +65,17 @@ module Sequel
55
65
  def primary_key_index_re
56
66
  PRIMARY_KEY_INDEX_RE
57
67
  end
68
+
69
+ def setup_type_convertor_map
70
+ super
71
+ map = @type_convertor_map
72
+ types = Java::JavaSQL::Types
73
+ map[types::NCLOB] = map[types::CLOB] = TypeConvertor::INSTANCE.method(:DB2Clob)
74
+ end
58
75
  end
59
76
 
60
77
  class Dataset < JDBC::Dataset
61
78
  include Sequel::DB2::DatasetMethods
62
-
63
- class ::Sequel::JDBC::Dataset::TYPE_TRANSLATOR
64
- def db2_clob(v) Sequel::SQL::Blob.new(v.getSubString(1, v.length)) end
65
- end
66
-
67
- DB2_CLOB_METHOD = TYPE_TRANSLATOR_INSTANCE.method(:db2_clob)
68
-
69
- private
70
-
71
- # Return clob as blob if use_clob_as_blob is true
72
- def convert_type_proc(v, ctn=nil)
73
- case v
74
- when JAVA_SQL_CLOB
75
- ::Sequel::DB2::use_clob_as_blob ? DB2_CLOB_METHOD : super
76
- else
77
- super
78
- end
79
- end
80
79
  end
81
80
  end
82
81
  end
@@ -185,7 +185,6 @@ module Sequel
185
185
  BOOL_FALSE_OLD = '(1 = 0)'.freeze
186
186
  BOOL_TRUE = 'TRUE'.freeze
187
187
  BOOL_FALSE = 'FALSE'.freeze
188
- SELECT_CLAUSE_METHODS = clause_methods(:select, %w'select distinct columns from join where group having compounds order limit lock')
189
188
  EMULATED_FUNCTION_MAP = {:char_length=>'length'.freeze}
190
189
 
191
190
  # Derby doesn't support an expression between CASE and WHEN,
@@ -240,23 +239,10 @@ module Sequel
240
239
 
241
240
  private
242
241
 
243
- JAVA_SQL_CLOB = Java::JavaSQL::Clob
244
-
245
- class ::Sequel::JDBC::Dataset::TYPE_TRANSLATOR
246
- def derby_clob(v) v.getSubString(1, v.length) end
242
+ def empty_from_sql
243
+ DEFAULT_FROM
247
244
  end
248
245
 
249
- DERBY_CLOB_METHOD = TYPE_TRANSLATOR_INSTANCE.method(:derby_clob)
250
-
251
- # Handle clobs on Derby as strings.
252
- def convert_type_proc(v, ctn=nil)
253
- if v.is_a?(JAVA_SQL_CLOB)
254
- DERBY_CLOB_METHOD
255
- else
256
- super
257
- end
258
- end
259
-
260
246
  # Derby needs a hex string casted to BLOB for blobs.
261
247
  def literal_blob_append(sql, v)
262
248
  sql << BLOB_OPEN << v.unpack(HSTAR).first << BLOB_CLOSE
@@ -293,18 +279,9 @@ module Sequel
293
279
  end
294
280
  end
295
281
 
296
- # Derby doesn't support common table expressions.
297
- def select_clause_methods
298
- SELECT_CLAUSE_METHODS
299
- end
300
-
301
- # Use a default FROM table if the dataset does not contain a FROM table.
302
- def select_from_sql(sql)
303
- if @opts[:from]
304
- super
305
- else
306
- sql << DEFAULT_FROM
307
- end
282
+ # Derby supports multiple rows in INSERT.
283
+ def multi_insert_sql_strategy
284
+ :values
308
285
  end
309
286
 
310
287
  # Offset comes before limit in Derby
@@ -49,7 +49,7 @@ module Sequel
49
49
  # If the :prepare option is given and we aren't in a savepoint,
50
50
  # prepare the transaction for a two-phase commit.
51
51
  def commit_transaction(conn, opts=OPTS)
52
- if (s = opts[:prepare]) && _trans(conn)[:savepoint_level] <= 1
52
+ if (s = opts[:prepare]) && savepoint_level(conn) <= 1
53
53
  log_connection_execute(conn, "PREPARE COMMIT #{s}")
54
54
  else
55
55
  super
@@ -142,12 +142,12 @@ module Sequel
142
142
 
143
143
  # Dataset class for H2 datasets accessed via JDBC.
144
144
  class Dataset < JDBC::Dataset
145
- SELECT_CLAUSE_METHODS = clause_methods(:select, %w'select distinct columns from join where group having compounds order limit')
146
145
  APOS = Dataset::APOS
147
146
  HSTAR = "H*".freeze
148
147
  ILIKE_PLACEHOLDER = ["CAST(".freeze, " AS VARCHAR_IGNORECASE)".freeze].freeze
149
148
  TIME_FORMAT = "'%H:%M:%S'".freeze
150
-
149
+ ONLY_OFFSET = " LIMIT -1 OFFSET ".freeze
150
+
151
151
  # Emulate the case insensitive LIKE operator and the bitwise operators.
152
152
  def complex_expression_sql_append(sql, op, args)
153
153
  case op
@@ -182,23 +182,6 @@ module Sequel
182
182
 
183
183
  private
184
184
 
185
- #JAVA_H2_CLOB = Java::OrgH2Jdbc::JdbcClob
186
-
187
- class ::Sequel::JDBC::Dataset::TYPE_TRANSLATOR
188
- def h2_clob(v) v.getSubString(1, v.length) end
189
- end
190
-
191
- H2_CLOB_METHOD = TYPE_TRANSLATOR_INSTANCE.method(:h2_clob)
192
-
193
- # Handle H2 specific clobs as strings.
194
- def convert_type_proc(v, ctn=nil)
195
- if v.is_a?(Java::OrgH2Jdbc::JdbcClob)
196
- H2_CLOB_METHOD
197
- else
198
- super
199
- end
200
- end
201
-
202
185
  # H2 expects hexadecimal strings for blob values
203
186
  def literal_blob_append(sql, v)
204
187
  sql << APOS << v.unpack(HSTAR).first << APOS
@@ -209,8 +192,14 @@ module Sequel
209
192
  v.strftime(TIME_FORMAT)
210
193
  end
211
194
 
212
- def select_clause_methods
213
- SELECT_CLAUSE_METHODS
195
+ # H2 supports multiple rows in INSERT.
196
+ def multi_insert_sql_strategy
197
+ :values
198
+ end
199
+
200
+ def select_only_offset_sql(sql)
201
+ sql << ONLY_OFFSET
202
+ literal_append(sql, @opts[:offset])
214
203
  end
215
204
 
216
205
  # H2 supports quoted function names.
@@ -32,6 +32,11 @@ module Sequel
32
32
  end
33
33
  end
34
34
 
35
+ # HSQLDB supports DROP TABLE IF EXISTS
36
+ def supports_drop_table_if_exists?
37
+ true
38
+ end
39
+
35
40
  private
36
41
 
37
42
  # HSQLDB specific SQL for renaming columns, and changing column types and/or nullity.
@@ -71,6 +76,16 @@ module Sequel
71
76
  DATABASE_ERROR_REGEXPS
72
77
  end
73
78
 
79
+ # IF EXISTS comes after table name on HSQLDB
80
+ def drop_table_sql(name, options)
81
+ "DROP TABLE #{quote_schema_table(name)}#{' IF EXISTS' if options[:if_exists]}#{' CASCADE' if options[:cascade]}"
82
+ end
83
+
84
+ # IF EXISTS comes after view name on HSQLDB
85
+ def drop_view_sql(name, options)
86
+ "DROP VIEW #{quote_schema_table(name)}#{' IF EXISTS' if options[:if_exists]}#{' CASCADE' if options[:cascade]}"
87
+ end
88
+
74
89
  # Use IDENTITY() to get the last inserted id.
75
90
  def last_insert_id(conn, opts=OPTS)
76
91
  statement(conn) do |stmt|
@@ -113,12 +128,6 @@ module Sequel
113
128
  class Dataset < JDBC::Dataset
114
129
  BOOL_TRUE = 'TRUE'.freeze
115
130
  BOOL_FALSE = 'FALSE'.freeze
116
- # HSQLDB does support common table expressions, but the support is broken.
117
- # CTEs operate more like temprorary tables or views, lasting longer than the duration of the expression.
118
- # CTEs in earlier queries might take precedence over CTEs with the same name in later queries.
119
- # Also, if any CTE is recursive, all CTEs must be recursive.
120
- # If you want to use CTEs with HSQLDB, you'll have to manually modify the dataset to allow it.
121
- SELECT_CLAUSE_METHODS = clause_methods(:select, %w'select distinct columns from join where group having compounds order limit lock')
122
131
  SQL_WITH_RECURSIVE = "WITH RECURSIVE ".freeze
123
132
  APOS = Dataset::APOS
124
133
  HSTAR = "H*".freeze
@@ -148,6 +157,15 @@ module Sequel
148
157
  true
149
158
  end
150
159
 
160
+ # HSQLDB does support common table expressions, but the support is broken.
161
+ # CTEs operate more like temprorary tables or views, lasting longer than the duration of the expression.
162
+ # CTEs in earlier queries might take precedence over CTEs with the same name in later queries.
163
+ # Also, if any CTE is recursive, all CTEs must be recursive.
164
+ # If you want to use CTEs with HSQLDB, you'll have to manually modify the dataset to allow it.
165
+ def supports_cte?(type=:select)
166
+ false
167
+ end
168
+
151
169
  # HSQLDB does not support IS TRUE.
152
170
  def supports_is_true?
153
171
  false
@@ -160,6 +178,10 @@ module Sequel
160
178
 
161
179
  private
162
180
 
181
+ def empty_from_sql
182
+ DEFAULT_FROM
183
+ end
184
+
163
185
  # Use string in hex format for blob data.
164
186
  def literal_blob_append(sql, v)
165
187
  sql << BLOB_OPEN << v.unpack(HSTAR).first << APOS
@@ -180,20 +202,11 @@ module Sequel
180
202
  BOOL_TRUE
181
203
  end
182
204
 
183
- # HSQLDB does not support CTEs well enough for Sequel to enable support for them.
184
- def select_clause_methods
185
- SELECT_CLAUSE_METHODS
205
+ # HSQLDB supports multiple rows in INSERT.
206
+ def multi_insert_sql_strategy
207
+ :values
186
208
  end
187
209
 
188
- # Use a default FROM table if the dataset does not contain a FROM table.
189
- def select_from_sql(sql)
190
- if @opts[:from]
191
- super
192
- else
193
- sql << DEFAULT_FROM
194
- end
195
- end
196
-
197
210
  # Use WITH RECURSIVE instead of WITH if any of the CTEs is recursive
198
211
  def select_with_sql_base
199
212
  opts[:with].any?{|w| w[:recursive]} ? SQL_WITH_RECURSIVE : super
@@ -25,21 +25,6 @@ module Sequel
25
25
  # Dataset class for JTDS datasets accessed via JDBC.
26
26
  class Dataset < JDBC::Dataset
27
27
  include Sequel::MSSQL::DatasetMethods
28
-
29
- class ::Sequel::JDBC::Dataset::TYPE_TRANSLATOR
30
- def jtds_clob(v) v.getSubString(1, v.length) end
31
- end
32
-
33
- JTDS_CLOB_METHOD = TYPE_TRANSLATOR_INSTANCE.method(:jtds_clob)
34
-
35
- # Handle CLOB types retrieved via JTDS.
36
- def convert_type_proc(v, ctn=nil)
37
- if v.is_a?(Java::NetSourceforgeJtdsJdbc::ClobImpl)
38
- JTDS_CLOB_METHOD
39
- else
40
- super
41
- end
42
- end
43
28
  end
44
29
  end
45
30
  end
@@ -3,6 +3,21 @@ Sequel.require 'adapters/jdbc/transactions'
3
3
 
4
4
  module Sequel
5
5
  module JDBC
6
+ class TypeConvertor
7
+ JAVA_BIG_DECIMAL_CONSTRUCTOR = java.math.BigDecimal.java_class.constructor(Java::long).method(:new_instance)
8
+
9
+ def OracleDecimal(r, i)
10
+ if v = r.getBigDecimal(i)
11
+ i = v.long_value
12
+ if v == JAVA_BIG_DECIMAL_CONSTRUCTOR.call(i)
13
+ i
14
+ else
15
+ BigDecimal.new(v.to_string)
16
+ end
17
+ end
18
+ end
19
+ end
20
+
6
21
  # Database and Dataset support for Oracle databases accessed via JDBC.
7
22
  module Oracle
8
23
  # Instance methods for Oracle Database objects accessed via JDBC.
@@ -31,6 +46,11 @@ module Sequel
31
46
  super || exception.message =~ /\AClosed Connection/
32
47
  end
33
48
 
49
+ # Default the fetch size for statements to 100, similar to the oci8-based oracle adapter.
50
+ def default_fetch_size
51
+ 100
52
+ end
53
+
34
54
  def last_insert_id(conn, opts)
35
55
  unless sequence = opts[:sequence]
36
56
  if t = opts[:table]
@@ -76,50 +96,31 @@ module Sequel
76
96
  def supports_releasing_savepoints?
77
97
  false
78
98
  end
99
+
100
+ def setup_type_convertor_map
101
+ super
102
+ @type_convertor_map[:OracleDecimal] = TypeConvertor::INSTANCE.method(:OracleDecimal)
103
+ end
79
104
  end
80
105
 
81
106
  # Dataset class for Oracle datasets accessed via JDBC.
82
107
  class Dataset < JDBC::Dataset
83
108
  include Sequel::Oracle::DatasetMethods
84
109
 
85
- private
110
+ NUMERIC_TYPE = Java::JavaSQL::Types::NUMERIC
111
+ TIMESTAMP_TYPE = Java::JavaSQL::Types::TIMESTAMP
112
+ TIMESTAMPTZ_TYPES = [Java::oracle.jdbc.OracleTypes::TIMESTAMPTZ, Java::oracle.jdbc.OracleTypes::TIMESTAMPLTZ]
86
113
 
87
- JAVA_BIG_DECIMAL = ::Sequel::JDBC::Dataset::JAVA_BIG_DECIMAL
88
- JAVA_BIG_DECIMAL_CONSTRUCTOR = java.math.BigDecimal.java_class.constructor(Java::long).method(:new_instance)
89
-
90
- class ::Sequel::JDBC::Dataset::TYPE_TRANSLATOR
91
- def oracle_decimal(v)
92
- if v.scale == 0
93
- i = v.long_value
94
- if v.equals(JAVA_BIG_DECIMAL_CONSTRUCTOR.call(i))
95
- i
96
- else
97
- decimal(v)
98
- end
114
+ def type_convertor(map, meta, type, i)
115
+ case type
116
+ when NUMERIC_TYPE
117
+ if meta.getScale(i) == 0
118
+ map[:OracleDecimal]
99
119
  else
100
- decimal(v)
120
+ super
101
121
  end
102
- end
103
- end
104
-
105
- ORACLE_DECIMAL_METHOD = TYPE_TRANSLATOR_INSTANCE.method(:oracle_decimal)
106
-
107
- def convert_type_oracle_timestamp(v)
108
- db.to_application_timestamp(v.to_string)
109
- end
110
-
111
- def convert_type_oracle_timestamptz(v)
112
- convert_type_oracle_timestamp(db.synchronize(@opts[:server]){|c| v.timestampValue(c)})
113
- end
114
-
115
- def convert_type_proc(v, ctn=nil)
116
- case v
117
- when JAVA_BIG_DECIMAL
118
- ORACLE_DECIMAL_METHOD
119
- when Java::OracleSql::TIMESTAMPTZ
120
- method(:convert_type_oracle_timestamptz)
121
- when Java::OracleSql::TIMESTAMP
122
- method(:convert_type_oracle_timestamp)
122
+ when *TIMESTAMPTZ_TYPES
123
+ map[TIMESTAMP_TYPE]
123
124
  else
124
125
  super
125
126
  end
@@ -4,6 +4,26 @@ module Sequel
4
4
  Postgres::CONVERTED_EXCEPTIONS << NativeException
5
5
 
6
6
  module JDBC
7
+ class TypeConvertor
8
+ # Return PostgreSQL array types as ruby Arrays instead of
9
+ # JDBC PostgreSQL driver-specific array type. Only used if the
10
+ # database does not have a conversion proc for the type.
11
+ def RubyPGArray(r, i)
12
+ if v = r.getArray(i)
13
+ v.array.to_ary
14
+ end
15
+ end
16
+
17
+ # Return PostgreSQL hstore types as ruby Hashes instead of
18
+ # Java HashMaps. Only used if the database does not have a
19
+ # conversion proc for the type.
20
+ def RubyPGHstore(r, i)
21
+ if v = r.getObject(i)
22
+ v.to_hash
23
+ end
24
+ end
25
+ end
26
+
7
27
  # Adapter, Database, and Dataset support for accessing a PostgreSQL
8
28
  # database via JDBC.
9
29
  module Postgres
@@ -12,7 +32,7 @@ module Sequel
12
32
  module DatabaseMethods
13
33
  extend Sequel::Database::ResetIdentifierMangling
14
34
  include Sequel::Postgres::DatabaseMethods
15
-
35
+
16
36
  # Add the primary_keys and primary_key_sequences instance variables,
17
37
  # so we can get the correct return values for inserted rows.
18
38
  def self.extended(db)
@@ -81,8 +101,30 @@ module Sequel
81
101
  end
82
102
  end
83
103
 
104
+ def oid_convertor_proc(oid)
105
+ if (conv = Sequel.synchronize{@oid_convertor_map[oid]}).nil?
106
+ conv = if pr = conversion_procs[oid]
107
+ lambda do |r, i|
108
+ if v = r.getString(i)
109
+ pr.call(v)
110
+ end
111
+ end
112
+ else
113
+ false
114
+ end
115
+ Sequel.synchronize{@oid_convertor_map[oid] = conv}
116
+ end
117
+ conv
118
+ end
119
+
84
120
  private
85
121
 
122
+ # Clear oid convertor map cache when conversion procs are updated.
123
+ def conversion_procs_updated
124
+ super
125
+ Sequel.synchronize{@oid_convertor_map = {}}
126
+ end
127
+
86
128
  def disconnect_error?(exception, opts)
87
129
  super || exception.message =~ /\AThis connection has been closed\.\z|\AFATAL: terminating connection due to administrator command\z/
88
130
  end
@@ -101,6 +143,13 @@ module Sequel
101
143
  end
102
144
  conn
103
145
  end
146
+
147
+ def setup_type_convertor_map
148
+ super
149
+ @oid_convertor_map = {}
150
+ @type_convertor_map[:RubyPGArray] = TypeConvertor::INSTANCE.method(:RubyPGArray)
151
+ @type_convertor_map[:RubyPGHstore] = TypeConvertor::INSTANCE.method(:RubyPGHstore)
152
+ end
104
153
  end
105
154
 
106
155
  # Dataset subclass used for datasets that connect to PostgreSQL via JDBC.
@@ -108,53 +157,6 @@ module Sequel
108
157
  include Sequel::Postgres::DatasetMethods
109
158
  APOS = Dataset::APOS
110
159
 
111
- class ::Sequel::JDBC::Dataset::TYPE_TRANSLATOR
112
- # Convert Java::OrgPostgresqlUtil::PGobject to ruby strings
113
- def pg_object(v)
114
- v.to_string
115
- end
116
- end
117
-
118
- # Handle conversions of PostgreSQL array instances
119
- class PGArrayConverter
120
- # Set the method that will return the correct conversion
121
- # proc for elements of this array.
122
- def initialize(meth)
123
- @conversion_proc_method = meth
124
- @conversion_proc = nil
125
- end
126
-
127
- # Convert Java::OrgPostgresqlJdbc4::Jdbc4Array to ruby arrays
128
- def call(v)
129
- _pg_array(v.array)
130
- end
131
-
132
- private
133
-
134
- # Handle multi-dimensional Java arrays by recursively mapping them
135
- # to ruby arrays of ruby values.
136
- def _pg_array(v)
137
- v.to_ary.map do |i|
138
- if i.respond_to?(:to_ary)
139
- _pg_array(i)
140
- elsif i
141
- if @conversion_proc.nil?
142
- @conversion_proc = @conversion_proc_method.call(i)
143
- end
144
- if @conversion_proc
145
- @conversion_proc.call(i)
146
- else
147
- i
148
- end
149
- else
150
- i
151
- end
152
- end
153
- end
154
- end
155
-
156
- PG_OBJECT_METHOD = TYPE_TRANSLATOR_INSTANCE.method(:pg_object)
157
-
158
160
  # Add the shared PostgreSQL prepared statement methods
159
161
  def prepare(type, name=nil, *values)
160
162
  ps = to_prepared_statement(type, values)
@@ -169,55 +171,35 @@ module Sequel
169
171
 
170
172
  private
171
173
 
172
- # Handle PostgreSQL array and object types. Object types are just
173
- # turned into strings, similarly to how the native adapter treats
174
- # the types.
175
- def convert_type_proc(v, ctn=nil)
176
- case v
177
- when Java::OrgPostgresqlJdbc4::Jdbc4Array
178
- if pr = db.conversion_procs[ctn]
179
- lambda{|x| pr.call(PG_OBJECT_METHOD.call(x))}
180
- else
181
- PGArrayConverter.new(method(:convert_type_proc))
182
- end
183
- when Java::OrgPostgresqlUtil::PGobject
184
- if pr = db.conversion_procs[ctn]
185
- lambda{|x| pr.call(PG_OBJECT_METHOD.call(x))}
186
- else
187
- PG_OBJECT_METHOD
188
- end
189
- when String
190
- if pr = db.conversion_procs[ctn]
174
+ # Literalize strings similar to the native postgres adapter
175
+ def literal_string_append(sql, v)
176
+ sql << APOS << db.synchronize(@opts[:server]){|c| c.escape_string(v)} << APOS
177
+ end
178
+
179
+ STRING_TYPE = Java::JavaSQL::Types::VARCHAR
180
+ ARRAY_TYPE = Java::JavaSQL::Types::ARRAY
181
+ PG_SPECIFIC_TYPES = [ARRAY_TYPE, Java::JavaSQL::Types::OTHER, Java::JavaSQL::Types::STRUCT]
182
+ HSTORE_TYPE = 'hstore'.freeze
183
+
184
+ def type_convertor(map, meta, type, i)
185
+ case type
186
+ when *PG_SPECIFIC_TYPES
187
+ oid = meta.field(i).oid
188
+ if pr = db.oid_convertor_proc(oid)
191
189
  pr
190
+ elsif type == ARRAY_TYPE
191
+ map[:RubyPGArray]
192
+ elsif oid == 2950 # UUID
193
+ map[STRING_TYPE]
194
+ elsif meta.getPGType(i) == HSTORE_TYPE
195
+ map[:RubyPGHstore]
192
196
  else
193
- false
194
- end
195
- when JAVA_HASH_MAP
196
- if Sequel.respond_to?(:hstore)
197
- lambda{|x| Sequel.hstore(HASH_MAP_METHOD.call(x))}
198
- else
199
- HASH_MAP_METHOD
197
+ super
200
198
  end
201
199
  else
202
200
  super
203
201
  end
204
202
  end
205
-
206
- # The jdbc/postgresql adapter uses column type oids when determining
207
- # conversion procs.
208
- def convert_type_proc_uses_column_info?
209
- true
210
- end
211
-
212
- # Use the column type oid as the database specific column info value.
213
- def convert_type_proc_column_info(meta, i)
214
- meta.field(i).oid
215
- end
216
-
217
- # Literalize strings similar to the native postgres adapter
218
- def literal_string_append(sql, v)
219
- sql << APOS << db.synchronize(@opts[:server]){|c| c.escape_string(v)} << APOS
220
- end
221
203
  end
222
204
  end
223
205
  end