sequel 4.9.0 → 4.10.0

Sign up to get free protection for your applications and to get access to all the features.
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