activerecord-jdbc-adapter 1.2.1 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.travis.yml +3 -0
  2. data/Gemfile.lock +13 -15
  3. data/History.txt +19 -0
  4. data/README.rdoc +2 -0
  5. data/Rakefile +2 -1
  6. data/lib/arel/visitors/derby.rb +9 -2
  7. data/lib/arel/visitors/sql_server.rb +2 -0
  8. data/lib/arjdbc/db2/adapter.rb +3 -1
  9. data/lib/arjdbc/derby/adapter.rb +10 -3
  10. data/lib/arjdbc/jdbc/adapter.rb +5 -2
  11. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  12. data/lib/arjdbc/jdbc/base_ext.rb +15 -0
  13. data/lib/arjdbc/jdbc/connection.rb +5 -1
  14. data/lib/arjdbc/jdbc/missing_functionality_helper.rb +1 -0
  15. data/lib/arjdbc/mssql/adapter.rb +31 -28
  16. data/lib/arjdbc/mssql/lock_helpers.rb +72 -0
  17. data/lib/arjdbc/mysql/adapter.rb +110 -45
  18. data/lib/arjdbc/oracle/adapter.rb +7 -0
  19. data/lib/arjdbc/postgresql/adapter.rb +327 -153
  20. data/lib/arjdbc/sqlite3/adapter.rb +9 -4
  21. data/lib/arjdbc/version.rb +1 -1
  22. data/rakelib/db.rake +17 -5
  23. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +14 -4
  24. data/src/java/arjdbc/postgresql/PostgresqlRubyJdbcConnection.java +25 -0
  25. data/test/db/jdbc.rb +4 -3
  26. data/test/db2_reset_column_information_test.rb +8 -0
  27. data/test/derby_reset_column_information_test.rb +8 -0
  28. data/test/derby_row_locking_test.rb +9 -0
  29. data/test/derby_simple_test.rb +40 -0
  30. data/test/h2_simple_test.rb +2 -2
  31. data/test/helper.rb +15 -2
  32. data/test/jdbc_common.rb +1 -0
  33. data/test/jndi_callbacks_test.rb +5 -9
  34. data/test/manualTestDatabase.rb +31 -31
  35. data/test/models/validates_uniqueness_of_string.rb +1 -1
  36. data/test/mssql_ignore_system_views_test.rb +27 -0
  37. data/test/mssql_null_test.rb +14 -0
  38. data/test/mssql_reset_column_information_test.rb +8 -0
  39. data/test/mssql_row_locking_sql_test.rb +159 -0
  40. data/test/mssql_row_locking_test.rb +9 -0
  41. data/test/mysql_reset_column_information_test.rb +8 -0
  42. data/test/mysql_simple_test.rb +69 -5
  43. data/test/oracle_reset_column_information_test.rb +8 -0
  44. data/test/oracle_specific_test.rb +1 -1
  45. data/test/postgres_nonseq_pkey_test.rb +1 -1
  46. data/test/postgres_reset_column_information_test.rb +8 -0
  47. data/test/postgres_simple_test.rb +72 -1
  48. data/test/row_locking.rb +90 -0
  49. data/test/simple.rb +82 -2
  50. data/test/sqlite3_reset_column_information_test.rb +8 -0
  51. data/test/sqlite3_simple_test.rb +47 -0
  52. data/test/sybase_reset_column_information_test.rb +8 -0
  53. metadata +33 -3
@@ -135,7 +135,7 @@ module ::ArJdbc
135
135
  def quote_column_name(name)
136
136
  "`#{name.to_s.gsub('`', '``')}`"
137
137
  end
138
-
138
+
139
139
  def quoted_true
140
140
  "1"
141
141
  end
@@ -192,13 +192,54 @@ module ::ArJdbc
192
192
  end
193
193
  end
194
194
 
195
+ # based on:
196
+ # https://github.com/rails/rails/blob/3-1-stable/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb#L756
197
+ # Required for passing rails column caching tests
198
+ # Returns a table's primary key and belonging sequence.
199
+ def pk_and_sequence_for(table) #:nodoc:
200
+ keys = []
201
+ result = execute("SHOW INDEX FROM #{quote_table_name(table)} WHERE Key_name = 'PRIMARY'", 'SCHEMA')
202
+ result.each do |h|
203
+ keys << h["Column_name"]
204
+ end
205
+ keys.length == 1 ? [keys.first, nil] : nil
206
+ end
207
+
208
+ # based on:
209
+ # https://github.com/rails/rails/blob/3-1-stable/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb#L647
210
+ # Returns an array of indexes for the given table.
211
+ def indexes(table_name, name = nil)#:nodoc:
212
+ indexes = []
213
+ current_index = nil
214
+ result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
215
+ result.each do |row|
216
+ key_name = row["Key_name"]
217
+ if current_index != key_name
218
+ next if key_name == "PRIMARY" # skip the primary key
219
+ current_index = key_name
220
+ indexes << ::ActiveRecord::ConnectionAdapters::IndexDefinition.new(
221
+ row["Table"], key_name, row["Non_unique"] == 0, [], [])
222
+ end
223
+
224
+ indexes.last.columns << row["Column_name"]
225
+ indexes.last.lengths << row["Sub_part"]
226
+ end
227
+ indexes
228
+ end
229
+
195
230
  def jdbc_columns(table_name, name = nil)#:nodoc:
196
231
  sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
197
- execute(sql, :skip_logging).map do |field|
198
- ::ActiveRecord::ConnectionAdapters::MysqlAdapter::Column.new(field["Field"], field["Default"], field["Type"], field["Null"] == "YES")
232
+ execute(sql, 'SCHEMA').map do |field|
233
+ ::ActiveRecord::ConnectionAdapters::MysqlColumn.new(field["Field"], field["Default"], field["Type"], field["Null"] == "YES")
199
234
  end
200
235
  end
201
236
 
237
+ # Returns just a table's primary key
238
+ def primary_key(table)
239
+ pk_and_sequence = pk_and_sequence_for(table)
240
+ pk_and_sequence && pk_and_sequence.first
241
+ end
242
+
202
243
  def recreate_database(name, options = {}) #:nodoc:
203
244
  drop_database(name)
204
245
  create_database(name, options)
@@ -221,7 +262,7 @@ module ::ArJdbc
221
262
  end
222
263
 
223
264
  def create_table(name, options = {}) #:nodoc:
224
- super(name, {:options => "ENGINE=InnoDB"}.merge(options))
265
+ super(name, {:options => "ENGINE=InnoDB DEFAULT CHARSET=utf8"}.merge(options))
225
266
  end
226
267
 
227
268
  def rename_table(name, new_name)
@@ -293,6 +334,29 @@ module ::ArJdbc
293
334
  sql
294
335
  end
295
336
 
337
+ # Taken from: https://github.com/gfmurphy/rails/blob/3-1-stable/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb#L540
338
+ #
339
+ # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
340
+ # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
341
+ # these, we must use a subquery. However, MySQL is too stupid to create a
342
+ # temporary table for this automatically, so we have to give it some prompting
343
+ # in the form of a subsubquery. Ugh!
344
+ def join_to_update(update, select) #:nodoc:
345
+ if select.limit || select.offset || select.orders.any?
346
+ subsubselect = select.clone
347
+ subsubselect.projections = [update.key]
348
+
349
+ subselect = Arel::SelectManager.new(select.engine)
350
+ subselect.project Arel.sql(update.key.name)
351
+ subselect.from subsubselect.as('__active_record_temp')
352
+
353
+ update.where update.key.in(subselect)
354
+ else
355
+ update.table select.source
356
+ update.wheres = select.constraints
357
+ end
358
+ end
359
+
296
360
  def show_variable(var)
297
361
  res = execute("show variables like '#{var}'")
298
362
  result_row = res.detect {|row| row["Variable_name"] == var }
@@ -333,12 +397,12 @@ module ::ArJdbc
333
397
  length = options[:length] if options.is_a?(Hash)
334
398
 
335
399
  case length
336
- when Hash
337
- column_names.map { |name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
338
- when Fixnum
339
- column_names.map { |name| "#{quote_column_name(name)}(#{length})" }
340
- else
341
- column_names.map { |name| quote_column_name(name) }
400
+ when Hash
401
+ column_names.map { |name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
402
+ when Fixnum
403
+ column_names.map { |name| "#{quote_column_name(name)}(#{length})" }
404
+ else
405
+ column_names.map { |name| quote_column_name(name) }
342
406
  end
343
407
  end
344
408
 
@@ -373,31 +437,13 @@ module ::ArJdbc
373
437
  end
374
438
  end
375
439
 
376
- module ActiveRecord::ConnectionAdapters
377
- # Remove any vestiges of core/Ruby MySQL adapter
378
- remove_const(:MysqlColumn) if const_defined?(:MysqlColumn)
379
- remove_const(:MysqlAdapter) if const_defined?(:MysqlAdapter)
380
-
381
- class MysqlAdapter < JdbcAdapter
382
- include ArJdbc::MySQL
383
-
384
- def initialize(*args)
385
- super
386
- configure_connection
387
- end
440
+ module ActiveRecord
441
+ module ConnectionAdapters
442
+ # Remove any vestiges of core/Ruby MySQL adapter
443
+ remove_const(:MysqlColumn) if const_defined?(:MysqlColumn)
444
+ remove_const(:MysqlAdapter) if const_defined?(:MysqlAdapter)
388
445
 
389
- def jdbc_connection_class(spec)
390
- ::ArJdbc::MySQL.jdbc_connection_class
391
- end
392
-
393
- def jdbc_column_class
394
- ActiveRecord::ConnectionAdapters::MysqlAdapter::Column
395
- end
396
-
397
- alias_chained_method :columns, :query_cache, :jdbc_columns
398
-
399
- remove_const(:Column) if const_defined?(:Column)
400
- class Column < JdbcColumn
446
+ class MysqlColumn < JdbcColumn
401
447
  include ArJdbc::MySQL::ColumnExtensions
402
448
 
403
449
  def initialize(name, *args)
@@ -411,20 +457,39 @@ module ActiveRecord::ConnectionAdapters
411
457
  def call_discovered_column_callbacks(*)
412
458
  end
413
459
  end
414
-
415
- protected
416
- def exec_insert(sql, name, binds)
417
- binds = binds.dup
418
460
 
419
- # Pretend to support bind parameters
420
- unless binds.empty?
421
- sql = sql.gsub('?') { quote(*binds.shift.reverse) }
461
+ class MysqlAdapter < JdbcAdapter
462
+ include ArJdbc::MySQL
463
+
464
+ def initialize(*args)
465
+ super
466
+ configure_connection
422
467
  end
423
- execute sql, name
424
- end
425
- alias :exec_update :exec_insert
426
- alias :exec_delete :exec_insert
427
468
 
469
+ def jdbc_connection_class(spec)
470
+ ::ArJdbc::MySQL.jdbc_connection_class
471
+ end
472
+
473
+ def jdbc_column_class
474
+ ActiveRecord::ConnectionAdapters::MysqlColumn
475
+ end
476
+
477
+ alias_chained_method :columns, :query_cache, :jdbc_columns
478
+
479
+ protected
480
+ def exec_insert(sql, name, binds)
481
+ binds = binds.dup
482
+
483
+ # Pretend to support bind parameters
484
+ unless binds.empty?
485
+ sql = sql.gsub('?') { quote(*binds.shift.reverse) }
486
+ end
487
+ execute sql, name
488
+ end
489
+ alias :exec_update :exec_insert
490
+ alias :exec_delete :exec_insert
491
+
492
+ end
428
493
  end
429
494
  end
430
495
 
@@ -58,6 +58,13 @@ module ::ArJdbc
58
58
  end
59
59
  end
60
60
 
61
+ def extract_limit(sql_type)
62
+ case sql_type
63
+ when /^(clob|date)/i; nil
64
+ else super
65
+ end
66
+ end
67
+
61
68
  def type_cast_code(var_name)
62
69
  case type
63
70
  when :datetime then "ArJdbc::Oracle::Column.string_to_time(#{var_name}, self.class)"
@@ -6,7 +6,6 @@ module ::ArJdbc
6
6
  module PostgreSQL
7
7
  def self.extended(mod)
8
8
  (class << mod; self; end).class_eval do
9
- alias_chained_method :insert, :query_dirty, :pg_insert
10
9
  alias_chained_method :columns, :query_cache, :pg_columns
11
10
  end
12
11
  end
@@ -19,90 +18,170 @@ module ::ArJdbc
19
18
  ::ActiveRecord::ConnectionAdapters::PostgresJdbcConnection
20
19
  end
21
20
 
21
+ # column behavior based on postgresql_adapter in rails project
22
+ # https://github.com/rails/rails/blob/3-1-stable/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L41
22
23
  module Column
23
- def type_cast(value)
24
- case type
25
- when :boolean then cast_to_boolean(value)
26
- else super
24
+ def self.included(base)
25
+ class << base
26
+ attr_accessor :money_precision
27
+ def string_to_time(string)
28
+ return string unless String === string
29
+
30
+ case string
31
+ when 'infinity' then 1.0 / 0.0
32
+ when '-infinity' then -1.0 / 0.0
33
+ else
34
+ super
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ private
41
+ # Extracts the value from a Postgresql column default definition
42
+ def default_value(default)
43
+ case default
44
+ # This is a performance optimization for Ruby 1.9.2 in development.
45
+ # If the value is nil, we return nil straight away without checking
46
+ # the regular expressions. If we check each regular expression,
47
+ # Regexp#=== will call NilClass#to_str, which will trigger
48
+ # method_missing (defined by whiny nil in ActiveSupport) which
49
+ # makes this method very very slow.
50
+ when NilClass
51
+ nil
52
+ # Numeric types
53
+ when /\A\(?(-?\d+(\.\d*)?\)?)\z/
54
+ $1
55
+ # Character types
56
+ when /\A'(.*)'::(?:character varying|bpchar|text)\z/m
57
+ $1
58
+ # Character types (8.1 formatting)
59
+ when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m
60
+ $1.gsub(/\\(\d\d\d)/) { $1.oct.chr }
61
+ # Binary data types
62
+ when /\A'(.*)'::bytea\z/m
63
+ $1
64
+ # Date/time types
65
+ when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
66
+ $1
67
+ when /\A'(.*)'::interval\z/
68
+ $1
69
+ # Boolean type
70
+ when 'true'
71
+ true
72
+ when 'false'
73
+ false
74
+ # Geometric types
75
+ when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
76
+ $1
77
+ # Network address types
78
+ when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
79
+ $1
80
+ # Bit string types
81
+ when /\AB'(.*)'::"?bit(?: varying)?"?\z/
82
+ $1
83
+ # XML type
84
+ when /\A'(.*)'::xml\z/m
85
+ $1
86
+ # Arrays
87
+ when /\A'(.*)'::"?\D+"?\[\]\z/
88
+ $1
89
+ # Object identifier types
90
+ when /\A-?\d+\z/
91
+ $1
92
+ else
93
+ # Anything else is blank, some user type, or some function
94
+ # and we can't know the value of that, so return nil.
95
+ nil
27
96
  end
28
97
  end
29
98
 
30
99
  def extract_limit(sql_type)
31
100
  case sql_type
32
- when /^int2/i; 2
33
- when /^smallint/i; 2
34
- when /^int4/i; nil
35
- when /^integer/i; nil
36
- when /^int8/i; 8
37
- when /^bigint/i; 8
38
- when /^(bool|text|date|time|bytea)/i; nil # ACTIVERECORD_JDBC-135,139
101
+ when /^bigint/i then 8
102
+ when /^smallint/i then 2
39
103
  else super
40
104
  end
41
105
  end
42
106
 
43
- def simplified_type(field_type)
44
- return :integer if field_type =~ /^(big|)serial/i
45
- return :string if field_type =~ /\[\]$/i || field_type =~ /^interval/i
46
- return :string if field_type =~ /^(?:point|lseg|box|"?path"?|polygon|circle)/i
47
- return :string if field_type =~ /^uuid/i
48
- return :datetime if field_type =~ /^timestamp/i
49
- return :float if field_type =~ /^(?:real|double precision)$/i
50
- return :binary if field_type =~ /^bytea/i
51
- return :boolean if field_type =~ /^bool/i
52
- return :decimal if field_type == 'numeric(131089)'
53
- super
107
+ # Extracts the scale from PostgreSQL-specific data types.
108
+ def extract_scale(sql_type)
109
+ # Money type has a fixed scale of 2.
110
+ sql_type =~ /^money/ ? 2 : super
54
111
  end
55
112
 
56
- def cast_to_boolean(value)
57
- return nil if value.nil?
58
- if value == true || value == false
59
- value
113
+ # Extracts the precision from PostgreSQL-specific data types.
114
+ def extract_precision(sql_type)
115
+ if sql_type == 'money'
116
+ self.class.money_precision
60
117
  else
61
- %w(true t 1).include?(value.to_s.downcase)
118
+ super
62
119
  end
63
120
  end
64
121
 
65
- # Post process default value from JDBC into a Rails-friendly format (columns{-internal})
66
- def default_value(value)
67
- # Boolean types
68
- return "t" if value =~ /true/i
69
- return "f" if value =~ /false/i
70
-
71
- # Char/String/Bytea type values
72
- return $1 if value =~ /^'(.*)'::(bpchar|text|character varying|bytea)$/
73
-
74
- # Numeric values
75
- return value.delete("()") if value =~ /^\(?-?[0-9]+(\.[0-9]*)?\)?/
76
-
77
- # Fixed dates / timestamp
78
- return $1 if value =~ /^'(.+)'::(date|timestamp)/
79
-
80
- # Anything else is blank, some user type, or some function
81
- # and we can't know the value of that, so return nil.
82
- return nil
122
+ # Maps PostgreSQL-specific data types to logical Rails types.
123
+ def simplified_type(field_type)
124
+ case field_type
125
+ # Numeric and monetary types
126
+ when /^(?:real|double precision)$/ then :float
127
+ # Monetary types
128
+ when 'money' then :decimal
129
+ # Character types
130
+ when /^(?:character varying|bpchar)(?:\(\d+\))?$/ then :string
131
+ # Binary data types
132
+ when 'bytea' then :binary
133
+ # Date/time types
134
+ when /^timestamp with(?:out)? time zone$/ then :datetime
135
+ when 'interval' then :string
136
+ # Geometric types
137
+ when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/ then :string
138
+ # Network address types
139
+ when /^(?:cidr|inet|macaddr)$/ then :string
140
+ # Bit strings
141
+ when /^bit(?: varying)?(?:\(\d+\))?$/ then :string
142
+ # XML type
143
+ when 'xml' then :xml
144
+ # tsvector type
145
+ when 'tsvector' then :tsvector
146
+ # Arrays
147
+ when /^\D+\[\]$/ then :string
148
+ # Object identifier types
149
+ when 'oid' then :integer
150
+ # UUID type
151
+ when 'uuid' then :string
152
+ # Small and big integer types
153
+ when /^(?:small|big)int$/ then :integer
154
+ # Pass through all types that are not specific to PostgreSQL.
155
+ else
156
+ super
157
+ end
83
158
  end
84
159
  end
85
160
 
86
- def modify_types(tp)
87
- tp[:primary_key] = "serial primary key"
88
- tp[:string][:limit] = 255
89
- tp[:integer][:limit] = nil
90
- tp[:boolean] = { :name => "boolean" }
91
- tp[:float] = { :name => "float" }
92
- tp[:text] = { :name => "text" }
93
- tp[:datetime] = { :name => "timestamp" }
94
- tp[:timestamp] = { :name => "timestamp" }
95
- tp[:time] = { :name => "time" }
96
- tp[:date] = { :name => "date" }
97
- tp[:decimal] = { :name => "decimal" }
98
- tp
99
- end
161
+ # constants taken from postgresql_adapter in rails project
162
+ ADAPTER_NAME = 'PostgreSQL'
163
+
164
+ NATIVE_DATABASE_TYPES = {
165
+ :primary_key => "serial primary key",
166
+ :string => { :name => "character varying", :limit => 255 },
167
+ :text => { :name => "text" },
168
+ :integer => { :name => "integer" },
169
+ :float => { :name => "float" },
170
+ :decimal => { :name => "decimal" },
171
+ :datetime => { :name => "timestamp" },
172
+ :timestamp => { :name => "timestamp" },
173
+ :time => { :name => "time" },
174
+ :date => { :name => "date" },
175
+ :binary => { :name => "bytea" },
176
+ :boolean => { :name => "boolean" },
177
+ :xml => { :name => "xml" },
178
+ :tsvector => { :name => "tsvector" }
179
+ }
100
180
 
101
181
  def adapter_name #:nodoc:
102
- 'PostgreSQL'
182
+ ADAPTER_NAME
103
183
  end
104
184
 
105
-
106
185
  def self.arel2_visitors(config)
107
186
  {}.tap {|v| %w(postgresql pg jdbcpostgresql).each {|a| v[a] = ::Arel::Visitors::PostgreSQL } }
108
187
  end
@@ -119,6 +198,10 @@ module ::ArJdbc
119
198
  end
120
199
  end
121
200
 
201
+ def native_database_types
202
+ NATIVE_DATABASE_TYPES
203
+ end
204
+
122
205
  # Does PostgreSQL support migrations?
123
206
  def supports_migrations?
124
207
  true
@@ -247,64 +330,45 @@ module ::ArJdbc
247
330
  nil
248
331
  end
249
332
 
250
- def pg_insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
251
- sql = substitute_binds(sql, binds)
252
-
253
- # Extract the table from the insert sql. Yuck.
254
- table = sql.split(" ", 4)[2].gsub('"', '')
333
+ def primary_key(table)
334
+ pk_and_sequence = pk_and_sequence_for(table)
335
+ pk_and_sequence && pk_and_sequence.first
336
+ end
255
337
 
256
- # Try an insert with 'returning id' if available (PG >= 8.2)
257
- if supports_insert_with_returning? && id_value.nil?
258
- pk, sequence_name = *pk_and_sequence_for(table) unless pk
259
- if pk
260
- sql = substitute_binds(sql, binds)
261
- id_value = select_value("#{sql} RETURNING #{quote_column_name(pk)}")
262
- clear_query_cache #FIXME: Why now?
263
- return id_value
264
- end
338
+ # taken from rails postgresql adapter
339
+ # https://github.com/gfmurphy/rails/blob/master/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L611
340
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
341
+ unless pk
342
+ table_ref = extract_table_ref_from_insert_sql(sql)
343
+ pk = primary_key(table_ref) if table_ref
265
344
  end
266
345
 
267
- # Otherwise, plain insert
268
- execute(sql, name, binds)
269
-
270
- # Don't need to look up id_value if we already have it.
271
- # (and can't in case of non-sequence PK)
272
- unless id_value
273
- # If neither pk nor sequence name is given, look them up.
274
- unless pk || sequence_name
275
- pk, sequence_name = *pk_and_sequence_for(table)
276
- end
346
+ sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk
277
347
 
278
- # If a pk is given, fallback to default sequence name.
279
- # Don't fetch last insert id for a table without a pk.
280
- if pk && sequence_name ||= default_sequence_name(table, pk)
281
- id_value = last_insert_id(table, sequence_name)
282
- end
283
- end
284
- id_value
348
+ [sql, binds]
285
349
  end
286
350
 
287
351
  def pg_columns(table_name, name=nil)
288
- schema_name = @config[:schema_search_path]
289
- if table_name =~ /\./
290
- parts = table_name.split(/\./)
291
- table_name = parts.pop
292
- schema_name = parts.join(".")
352
+ column_definitions(table_name).map do |row|
353
+ ::ActiveRecord::ConnectionAdapters::PostgreSQLColumn.new(
354
+ row["column_name"], row["column_default"], row["column_type"],
355
+ row["column_not_null"] == "f")
293
356
  end
294
- schema_list = if schema_name.nil?
295
- []
296
- else
297
- schema_name.split(/\s*,\s*/)
298
- end
299
- while schema_list.size > 1
300
- s = schema_list.shift
301
- begin
302
- return @connection.columns_internal(table_name, name, s)
303
- rescue ActiveRecord::JDBCError=>ignored_for_next_schema
304
- end
305
- end
306
- s = schema_list.shift
307
- return @connection.columns_internal(table_name, name, s)
357
+ end
358
+
359
+ # current database name
360
+ def current_database
361
+ exec_query("select current_database() as database").
362
+ first["database"]
363
+ end
364
+
365
+ # current database encoding
366
+ def encoding
367
+ exec_query(<<-end_sql).first["encoding"]
368
+ SELECT pg_encoding_to_char(pg_database.encoding) as encoding
369
+ FROM pg_database
370
+ WHERE pg_database.datname LIKE '#{current_database}'
371
+ end_sql
308
372
  end
309
373
 
310
374
  # Sets the maximum number columns postgres has, default 32
@@ -341,10 +405,10 @@ module ::ArJdbc
341
405
 
342
406
  insertion_order = []
343
407
  index_order = nil
344
-
408
+
345
409
  result.each do |row|
346
410
  if current_index != row[0]
347
-
411
+
348
412
  (index_order = row[4].split(' ')).each_with_index{ |v, i| index_order[i] = v.to_i }
349
413
  indexes << ::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, row[0], row[1] == "t", [])
350
414
  current_index = row[0]
@@ -357,6 +421,11 @@ module ::ArJdbc
357
421
  indexes
358
422
  end
359
423
 
424
+ # take id from result of insert query
425
+ def last_inserted_id(result)
426
+ Hash[Array(*result)].fetch("id") { result }
427
+ end
428
+
360
429
  def last_insert_id(table, sequence_name)
361
430
  Integer(select_value("SELECT currval('#{sequence_name}')"))
362
431
  end
@@ -386,11 +455,6 @@ module ::ArJdbc
386
455
  select('select nspname from pg_namespace').map {|r| r["nspname"] }
387
456
  end
388
457
 
389
- def primary_key(table)
390
- pk_and_sequence = pk_and_sequence_for(table)
391
- pk_and_sequence && pk_and_sequence.first
392
- end
393
-
394
458
  def structure_dump
395
459
  database = @config[:database]
396
460
  if database.nil?
@@ -425,18 +489,17 @@ module ::ArJdbc
425
489
  # requires that the ORDER BY include the distinct column.
426
490
  #
427
491
  # distinct("posts.id", "posts.created_at desc")
428
- def distinct(columns, order_by)
429
- return "DISTINCT #{columns}" if order_by.blank?
492
+ def distinct(columns, orders) #:nodoc:
493
+ return "DISTINCT #{columns}" if orders.empty?
430
494
 
431
- # construct a clean list of column names from the ORDER BY clause, removing
432
- # any asc/desc modifiers
433
- order_columns = [order_by].flatten.map{|o| o.split(',').collect { |s| s.split.first } }.flatten.reject(&:blank?)
434
- order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
495
+ # Construct a clean list of column names from the ORDER BY clause, removing
496
+ # any ASC/DESC modifiers
497
+ order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*/i, '') }.
498
+ reject(&:blank?)
499
+ order_columns = order_columns.
500
+ zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
435
501
 
436
- # return a DISTINCT ON() clause that's distinct on the columns we want but includes
437
- # all the required columns for the ORDER BY to work properly
438
- sql = "DISTINCT ON (#{columns}) #{columns}, "
439
- sql << order_columns * ', '
502
+ "DISTINCT #{columns}, #{order_columns * ', '}"
440
503
  end
441
504
 
442
505
  # ORDER BY clause for the passed order option.
@@ -453,22 +516,31 @@ module ::ArJdbc
453
516
  sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}"
454
517
  end
455
518
 
519
+ # from postgres_adapter.rb in rails project
520
+ # https://github.com/rails/rails/blob/3-1-stable/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L412
521
+ # Quotes PostgreSQL-specific data types for SQL input.
456
522
  def quote(value, column = nil) #:nodoc:
457
523
  return super unless column
458
524
 
459
- if value.kind_of?(String) && column.type == :binary
460
- "E'#{escape_bytea(value)}'"
461
- elsif value.kind_of?(String) && column.sql_type == 'xml'
462
- "xml '#{quote_string(value)}'"
463
- elsif value.kind_of?(Numeric) && column.sql_type == 'money'
525
+ case value
526
+ when Float
527
+ return super unless value.infinite? && column.type == :datetime
528
+ "'#{value.to_s.downcase}'"
529
+ when Numeric
530
+ return super unless column.sql_type == 'money'
464
531
  # Not truly string input, so doesn't require (or allow) escape string syntax.
465
532
  "'#{value}'"
466
- elsif value.kind_of?(String) && column.sql_type =~ /^bit/
467
- case value
468
- when /^[01]*$/
469
- "B'#{value}'" # Bit-string notation
470
- when /^[0-9A-F]*$/i
471
- "X'#{value}'" # Hexadecimal notation
533
+ when String
534
+ case column.sql_type
535
+ when 'bytea' then "'#{escape_bytea(value)}'"
536
+ when 'xml' then "xml '#{quote_string(value)}'"
537
+ when /^bit/
538
+ case value
539
+ when /^[01]*$/ then "B'#{value}'" # Bit-string notation
540
+ when /^[0-9A-F]*$/i then "X'#{value}'" # Hexadecimal notation
541
+ end
542
+ else
543
+ super
472
544
  end
473
545
  else
474
546
  super
@@ -572,24 +644,64 @@ module ::ArJdbc
572
644
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
573
645
  end
574
646
 
575
- def remove_index(table_name, options) #:nodoc:
576
- execute "DROP INDEX #{index_name(table_name, options)}"
647
+ def remove_index!(table_name, index_name) #:nodoc:
648
+ execute "DROP INDEX #{quote_table_name(index_name)}"
649
+ end
650
+
651
+ def index_name_length
652
+ 63
577
653
  end
578
654
 
579
- def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
655
+ # Maps logical Rails types to PostgreSQL-specific data types.
656
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
580
657
  return super unless type.to_s == 'integer'
658
+ return 'integer' unless limit
581
659
 
582
- if limit.nil? || limit == 4
583
- 'integer'
584
- elsif limit < 4
585
- 'smallint'
586
- else
587
- 'bigint'
660
+ case limit
661
+ when 1, 2; 'smallint'
662
+ when 3, 4; 'integer'
663
+ when 5..8; 'bigint'
664
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
588
665
  end
589
666
  end
590
667
 
591
- def tables
592
- @connection.tables(database_name, nil, nil, ["TABLE"])
668
+ def tables(name = nil)
669
+ exec_query(<<-SQL, 'SCHEMA').map { |row| row["tablename"] }
670
+ SELECT tablename
671
+ FROM pg_tables
672
+ WHERE schemaname = ANY (current_schemas(false))
673
+ SQL
674
+ end
675
+
676
+ def table_exists?(name)
677
+ schema, table = extract_schema_and_table(name.to_s)
678
+ return false unless table # Abstract classes is having nil table name
679
+
680
+ binds = [[nil, table.gsub(/(^"|"$)/,'')]]
681
+ binds << [nil, schema] if schema
682
+
683
+ exec_query(<<-SQL, 'SCHEMA', binds).first["table_count"] > 0
684
+ SELECT COUNT(*) as table_count
685
+ FROM pg_tables
686
+ WHERE tablename = ?
687
+ AND schemaname = #{schema ? "?" : "ANY (current_schemas(false))"}
688
+ SQL
689
+ end
690
+
691
+ # Extracts the table and schema name from +name+
692
+ def extract_schema_and_table(name)
693
+ schema, table = name.split('.', 2)
694
+
695
+ unless table # A table was provided without a schema
696
+ table = schema
697
+ schema = nil
698
+ end
699
+
700
+ if name =~ /^"/ # Handle quoted table names
701
+ table = name
702
+ schema = nil
703
+ end
704
+ [schema, table]
593
705
  end
594
706
 
595
707
  private
@@ -604,6 +716,35 @@ module ::ArJdbc
604
716
  end
605
717
  end
606
718
 
719
+ # Returns the list of a table's column names, data types, and default values.
720
+ #
721
+ # The underlying query is roughly:
722
+ # SELECT column.name, column.type, default.value
723
+ # FROM column LEFT JOIN default
724
+ # ON column.table_id = default.table_id
725
+ # AND column.num = default.column_num
726
+ # WHERE column.table_id = get_table_id('table_name')
727
+ # AND column.num > 0
728
+ # AND NOT column.is_dropped
729
+ # ORDER BY column.num
730
+ #
731
+ # If the table name is not prefixed with a schema, the database will
732
+ # take the first match from the schema search path.
733
+ #
734
+ # Query implementation notes:
735
+ # - format_type includes the column size constraint, e.g. varchar(50)
736
+ # - ::regclass is a function that gives the id for a table name
737
+ def column_definitions(table_name) #:nodoc:
738
+ exec_query(<<-end_sql, 'SCHEMA')
739
+ SELECT a.attname as column_name, format_type(a.atttypid, a.atttypmod) as column_type, d.adsrc as column_default, a.attnotnull as column_not_null
740
+ FROM pg_attribute a LEFT JOIN pg_attrdef d
741
+ ON a.attrelid = d.adrelid AND a.attnum = d.adnum
742
+ WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
743
+ AND a.attnum > 0 AND NOT a.attisdropped
744
+ ORDER BY a.attnum
745
+ end_sql
746
+ end
747
+
607
748
  def extract_pg_identifier_from_name(name)
608
749
  match_data = name[0,1] == '"' ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
609
750
 
@@ -613,6 +754,12 @@ module ::ArJdbc
613
754
  [match_data[1], (rest.length > 0 ? rest : nil)]
614
755
  end
615
756
  end
757
+
758
+ # from rails postgresl_adapter
759
+ def extract_table_ref_from_insert_sql(sql)
760
+ sql[/into\s+([^\(]*).*values\s*\(/i]
761
+ $1.strip if $1
762
+ end
616
763
  end
617
764
  end
618
765
 
@@ -634,9 +781,37 @@ module ActiveRecord::ConnectionAdapters
634
781
  end
635
782
  end
636
783
 
784
+ class PostgresJdbcConnection < JdbcConnection
785
+ alias :java_native_database_types :set_native_database_types
786
+
787
+ # override to prevent connection from loading hash from jdbc
788
+ # metadata, which can be expensive. We can do this since
789
+ # native_database_types is defined in the adapter to use a static hash
790
+ # not relying on the driver's metadata
791
+ def set_native_database_types
792
+ @native_types = {}
793
+ end
794
+ end
795
+
637
796
  class PostgreSQLAdapter < JdbcAdapter
638
797
  include ArJdbc::PostgreSQL
639
798
 
799
+ class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
800
+ def xml(*args)
801
+ options = args.extract_options!
802
+ column(args[0], "xml", options)
803
+ end
804
+
805
+ def tsvector(*args)
806
+ options = args.extract_options!
807
+ column(args[0], "tsvector", options)
808
+ end
809
+ end
810
+
811
+ def table_definition
812
+ TableDefinition.new(self)
813
+ end
814
+
640
815
  def jdbc_connection_class(spec)
641
816
  ::ArJdbc::PostgreSQL.jdbc_connection_class
642
817
  end
@@ -644,8 +819,7 @@ module ActiveRecord::ConnectionAdapters
644
819
  def jdbc_column_class
645
820
  ActiveRecord::ConnectionAdapters::PostgreSQLColumn
646
821
  end
647
-
648
- alias_chained_method :insert, :query_dirty, :pg_insert
822
+
649
823
  alias_chained_method :columns, :query_cache, :pg_columns
650
824
  end
651
825
  end