activerecord-jdbc-adapter 1.2.1 → 1.2.2

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 (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