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.
- data/.travis.yml +3 -0
- data/Gemfile.lock +13 -15
- data/History.txt +19 -0
- data/README.rdoc +2 -0
- data/Rakefile +2 -1
- data/lib/arel/visitors/derby.rb +9 -2
- data/lib/arel/visitors/sql_server.rb +2 -0
- data/lib/arjdbc/db2/adapter.rb +3 -1
- data/lib/arjdbc/derby/adapter.rb +10 -3
- data/lib/arjdbc/jdbc/adapter.rb +5 -2
- data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
- data/lib/arjdbc/jdbc/base_ext.rb +15 -0
- data/lib/arjdbc/jdbc/connection.rb +5 -1
- data/lib/arjdbc/jdbc/missing_functionality_helper.rb +1 -0
- data/lib/arjdbc/mssql/adapter.rb +31 -28
- data/lib/arjdbc/mssql/lock_helpers.rb +72 -0
- data/lib/arjdbc/mysql/adapter.rb +110 -45
- data/lib/arjdbc/oracle/adapter.rb +7 -0
- data/lib/arjdbc/postgresql/adapter.rb +327 -153
- data/lib/arjdbc/sqlite3/adapter.rb +9 -4
- data/lib/arjdbc/version.rb +1 -1
- data/rakelib/db.rake +17 -5
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +14 -4
- data/src/java/arjdbc/postgresql/PostgresqlRubyJdbcConnection.java +25 -0
- data/test/db/jdbc.rb +4 -3
- data/test/db2_reset_column_information_test.rb +8 -0
- data/test/derby_reset_column_information_test.rb +8 -0
- data/test/derby_row_locking_test.rb +9 -0
- data/test/derby_simple_test.rb +40 -0
- data/test/h2_simple_test.rb +2 -2
- data/test/helper.rb +15 -2
- data/test/jdbc_common.rb +1 -0
- data/test/jndi_callbacks_test.rb +5 -9
- data/test/manualTestDatabase.rb +31 -31
- data/test/models/validates_uniqueness_of_string.rb +1 -1
- data/test/mssql_ignore_system_views_test.rb +27 -0
- data/test/mssql_null_test.rb +14 -0
- data/test/mssql_reset_column_information_test.rb +8 -0
- data/test/mssql_row_locking_sql_test.rb +159 -0
- data/test/mssql_row_locking_test.rb +9 -0
- data/test/mysql_reset_column_information_test.rb +8 -0
- data/test/mysql_simple_test.rb +69 -5
- data/test/oracle_reset_column_information_test.rb +8 -0
- data/test/oracle_specific_test.rb +1 -1
- data/test/postgres_nonseq_pkey_test.rb +1 -1
- data/test/postgres_reset_column_information_test.rb +8 -0
- data/test/postgres_simple_test.rb +72 -1
- data/test/row_locking.rb +90 -0
- data/test/simple.rb +82 -2
- data/test/sqlite3_reset_column_information_test.rb +8 -0
- data/test/sqlite3_simple_test.rb +47 -0
- data/test/sybase_reset_column_information_test.rb +8 -0
- metadata +33 -3
data/lib/arjdbc/mysql/adapter.rb
CHANGED
@@ -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,
|
198
|
-
::ActiveRecord::ConnectionAdapters::
|
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
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
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
|
377
|
-
|
378
|
-
|
379
|
-
|
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
|
-
|
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
|
-
|
420
|
-
|
421
|
-
|
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
|
24
|
-
|
25
|
-
|
26
|
-
|
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 /^
|
33
|
-
when /^smallint/i
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
57
|
-
|
58
|
-
if
|
59
|
-
|
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
|
-
|
118
|
+
super
|
62
119
|
end
|
63
120
|
end
|
64
121
|
|
65
|
-
#
|
66
|
-
def
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
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
|
251
|
-
|
252
|
-
|
253
|
-
|
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
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
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
|
-
#
|
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
|
-
|
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
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
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
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
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,
|
429
|
-
return "DISTINCT #{columns}" if
|
492
|
+
def distinct(columns, orders) #:nodoc:
|
493
|
+
return "DISTINCT #{columns}" if orders.empty?
|
430
494
|
|
431
|
-
#
|
432
|
-
# any
|
433
|
-
order_columns =
|
434
|
-
|
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
|
-
|
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
|
-
|
460
|
-
|
461
|
-
|
462
|
-
"
|
463
|
-
|
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
|
-
|
467
|
-
case
|
468
|
-
when
|
469
|
-
|
470
|
-
when /^
|
471
|
-
|
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,
|
576
|
-
execute "DROP INDEX #{index_name
|
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
|
-
|
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
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
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
|
-
|
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
|