activerecord-jdbc-adapter-onsite 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/.gitignore +22 -0
- data/.travis.yml +14 -0
- data/Appraisals +16 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +45 -0
- data/History.txt +488 -0
- data/LICENSE.txt +21 -0
- data/README.rdoc +214 -0
- data/Rakefile +62 -0
- data/activerecord-jdbc-adapter.gemspec +23 -0
- data/bench/bench_attributes.rb +13 -0
- data/bench/bench_attributes_new.rb +14 -0
- data/bench/bench_create.rb +12 -0
- data/bench/bench_find_all.rb +12 -0
- data/bench/bench_find_all_mt.rb +25 -0
- data/bench/bench_model.rb +85 -0
- data/bench/bench_new.rb +12 -0
- data/bench/bench_new_valid.rb +12 -0
- data/bench/bench_valid.rb +13 -0
- data/gemfiles/rails23.gemfile +10 -0
- data/gemfiles/rails23.gemfile.lock +38 -0
- data/gemfiles/rails30.gemfile +9 -0
- data/gemfiles/rails30.gemfile.lock +33 -0
- data/gemfiles/rails31.gemfile +9 -0
- data/gemfiles/rails31.gemfile.lock +35 -0
- data/gemfiles/rails32.gemfile +9 -0
- data/gemfiles/rails32.gemfile.lock +35 -0
- data/lib/active_record/connection_adapters/derby_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/h2_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/hsqldb_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/informix_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/jdbc_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/jndi_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/mssql_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/oracle_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -0
- data/lib/activerecord-jdbc-adapter.rb +8 -0
- data/lib/arel/engines/sql/compilers/db2_compiler.rb +9 -0
- data/lib/arel/engines/sql/compilers/derby_compiler.rb +6 -0
- data/lib/arel/engines/sql/compilers/h2_compiler.rb +6 -0
- data/lib/arel/engines/sql/compilers/hsqldb_compiler.rb +15 -0
- data/lib/arel/engines/sql/compilers/jdbc_compiler.rb +6 -0
- data/lib/arel/engines/sql/compilers/mssql_compiler.rb +46 -0
- data/lib/arel/visitors/compat.rb +13 -0
- data/lib/arel/visitors/db2.rb +17 -0
- data/lib/arel/visitors/derby.rb +32 -0
- data/lib/arel/visitors/firebird.rb +24 -0
- data/lib/arel/visitors/hsqldb.rb +26 -0
- data/lib/arel/visitors/sql_server.rb +46 -0
- data/lib/arjdbc.rb +24 -0
- data/lib/arjdbc/db2.rb +2 -0
- data/lib/arjdbc/db2/adapter.rb +541 -0
- data/lib/arjdbc/derby.rb +7 -0
- data/lib/arjdbc/derby/adapter.rb +358 -0
- data/lib/arjdbc/derby/connection_methods.rb +19 -0
- data/lib/arjdbc/discover.rb +92 -0
- data/lib/arjdbc/firebird.rb +2 -0
- data/lib/arjdbc/firebird/adapter.rb +140 -0
- data/lib/arjdbc/h2.rb +4 -0
- data/lib/arjdbc/h2/adapter.rb +54 -0
- data/lib/arjdbc/h2/connection_methods.rb +13 -0
- data/lib/arjdbc/hsqldb.rb +4 -0
- data/lib/arjdbc/hsqldb/adapter.rb +184 -0
- data/lib/arjdbc/hsqldb/connection_methods.rb +15 -0
- data/lib/arjdbc/informix.rb +3 -0
- data/lib/arjdbc/informix/adapter.rb +142 -0
- data/lib/arjdbc/informix/connection_methods.rb +11 -0
- data/lib/arjdbc/jdbc.rb +2 -0
- data/lib/arjdbc/jdbc/adapter.rb +356 -0
- data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
- data/lib/arjdbc/jdbc/base_ext.rb +15 -0
- data/lib/arjdbc/jdbc/callbacks.rb +44 -0
- data/lib/arjdbc/jdbc/column.rb +47 -0
- data/lib/arjdbc/jdbc/compatibility.rb +51 -0
- data/lib/arjdbc/jdbc/connection.rb +134 -0
- data/lib/arjdbc/jdbc/connection_methods.rb +16 -0
- data/lib/arjdbc/jdbc/core_ext.rb +24 -0
- data/lib/arjdbc/jdbc/discover.rb +18 -0
- data/lib/arjdbc/jdbc/driver.rb +35 -0
- data/lib/arjdbc/jdbc/extension.rb +47 -0
- data/lib/arjdbc/jdbc/java.rb +14 -0
- data/lib/arjdbc/jdbc/jdbc.rake +131 -0
- data/lib/arjdbc/jdbc/missing_functionality_helper.rb +88 -0
- data/lib/arjdbc/jdbc/quoted_primary_key.rb +28 -0
- data/lib/arjdbc/jdbc/railtie.rb +9 -0
- data/lib/arjdbc/jdbc/rake_tasks.rb +10 -0
- data/lib/arjdbc/jdbc/require_driver.rb +16 -0
- data/lib/arjdbc/jdbc/type_converter.rb +126 -0
- data/lib/arjdbc/mimer.rb +2 -0
- data/lib/arjdbc/mimer/adapter.rb +142 -0
- data/lib/arjdbc/mssql.rb +4 -0
- data/lib/arjdbc/mssql/adapter.rb +477 -0
- data/lib/arjdbc/mssql/connection_methods.rb +31 -0
- data/lib/arjdbc/mssql/limit_helpers.rb +101 -0
- data/lib/arjdbc/mssql/lock_helpers.rb +72 -0
- data/lib/arjdbc/mssql/tsql_helper.rb +61 -0
- data/lib/arjdbc/mysql.rb +4 -0
- data/lib/arjdbc/mysql/adapter.rb +505 -0
- data/lib/arjdbc/mysql/connection_methods.rb +28 -0
- data/lib/arjdbc/oracle.rb +3 -0
- data/lib/arjdbc/oracle/adapter.rb +432 -0
- data/lib/arjdbc/oracle/connection_methods.rb +12 -0
- data/lib/arjdbc/postgresql.rb +4 -0
- data/lib/arjdbc/postgresql/adapter.rb +861 -0
- data/lib/arjdbc/postgresql/connection_methods.rb +23 -0
- data/lib/arjdbc/sqlite3.rb +4 -0
- data/lib/arjdbc/sqlite3/adapter.rb +389 -0
- data/lib/arjdbc/sqlite3/connection_methods.rb +35 -0
- data/lib/arjdbc/sybase.rb +2 -0
- data/lib/arjdbc/sybase/adapter.rb +46 -0
- data/lib/arjdbc/version.rb +8 -0
- data/lib/generators/jdbc/USAGE +10 -0
- data/lib/generators/jdbc/jdbc_generator.rb +9 -0
- data/lib/jdbc_adapter.rb +2 -0
- data/lib/jdbc_adapter/rake_tasks.rb +3 -0
- data/lib/jdbc_adapter/version.rb +3 -0
- data/lib/pg.rb +26 -0
- data/pom.xml +57 -0
- data/rails_generators/jdbc_generator.rb +15 -0
- data/rails_generators/templates/config/initializers/jdbc.rb +7 -0
- data/rails_generators/templates/lib/tasks/jdbc.rake +8 -0
- data/rakelib/bundler_ext.rb +11 -0
- data/rakelib/compile.rake +23 -0
- data/rakelib/db.rake +39 -0
- data/rakelib/rails.rake +41 -0
- data/src/java/arjdbc/db2/DB2RubyJdbcConnection.java +69 -0
- data/src/java/arjdbc/derby/DerbyModule.java +324 -0
- data/src/java/arjdbc/h2/H2RubyJdbcConnection.java +70 -0
- data/src/java/arjdbc/informix/InformixRubyJdbcConnection.java +74 -0
- data/src/java/arjdbc/jdbc/AdapterJavaService.java +68 -0
- data/src/java/arjdbc/jdbc/JdbcConnectionFactory.java +36 -0
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +1346 -0
- data/src/java/arjdbc/jdbc/SQLBlock.java +48 -0
- data/src/java/arjdbc/mssql/MssqlRubyJdbcConnection.java +127 -0
- data/src/java/arjdbc/mysql/MySQLModule.java +134 -0
- data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +161 -0
- data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +85 -0
- data/src/java/arjdbc/postgresql/PostgresqlRubyJdbcConnection.java +82 -0
- data/src/java/arjdbc/sqlite3/Sqlite3RubyJdbcConnection.java +126 -0
- data/test/abstract_db_create.rb +135 -0
- data/test/activerecord/connection_adapters/type_conversion_test.rb +31 -0
- data/test/activerecord/connections/native_jdbc_mysql/connection.rb +25 -0
- data/test/activerecord/jall.sh +7 -0
- data/test/activerecord/jtest.sh +3 -0
- data/test/db/db2.rb +11 -0
- data/test/db/derby.rb +12 -0
- data/test/db/h2.rb +11 -0
- data/test/db/hsqldb.rb +13 -0
- data/test/db/informix.rb +11 -0
- data/test/db/jdbc.rb +12 -0
- data/test/db/jndi_config.rb +40 -0
- data/test/db/logger.rb +3 -0
- data/test/db/mssql.rb +9 -0
- data/test/db/mysql.rb +10 -0
- data/test/db/oracle.rb +34 -0
- data/test/db/postgres.rb +18 -0
- data/test/db/sqlite3.rb +11 -0
- data/test/db2_reset_column_information_test.rb +8 -0
- data/test/db2_simple_test.rb +66 -0
- data/test/derby_migration_test.rb +68 -0
- data/test/derby_multibyte_test.rb +12 -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 +139 -0
- data/test/generic_jdbc_connection_test.rb +29 -0
- data/test/h2_change_column_test.rb +68 -0
- data/test/h2_simple_test.rb +41 -0
- data/test/has_many_through.rb +79 -0
- data/test/helper.rb +108 -0
- data/test/hsqldb_simple_test.rb +6 -0
- data/test/informix_simple_test.rb +48 -0
- data/test/jdbc_common.rb +28 -0
- data/test/jndi_callbacks_test.rb +36 -0
- data/test/jndi_test.rb +25 -0
- data/test/manualTestDatabase.rb +191 -0
- data/test/models/add_not_null_column_to_table.rb +9 -0
- data/test/models/auto_id.rb +15 -0
- data/test/models/custom_pk_name.rb +14 -0
- data/test/models/data_types.rb +30 -0
- data/test/models/entry.rb +40 -0
- data/test/models/mixed_case.rb +22 -0
- data/test/models/reserved_word.rb +15 -0
- data/test/models/string_id.rb +17 -0
- data/test/models/thing.rb +16 -0
- data/test/models/validates_uniqueness_of_string.rb +19 -0
- data/test/mssql_db_create_test.rb +26 -0
- data/test/mssql_identity_insert_test.rb +19 -0
- data/test/mssql_ignore_system_views_test.rb +27 -0
- data/test/mssql_legacy_types_test.rb +58 -0
- data/test/mssql_limit_offset_test.rb +136 -0
- data/test/mssql_multibyte_test.rb +18 -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/mssql_simple_test.rb +55 -0
- data/test/mysql_db_create_test.rb +27 -0
- data/test/mysql_index_length_test.rb +58 -0
- data/test/mysql_info_test.rb +123 -0
- data/test/mysql_multibyte_test.rb +10 -0
- data/test/mysql_nonstandard_primary_key_test.rb +42 -0
- data/test/mysql_reset_column_information_test.rb +8 -0
- data/test/mysql_simple_test.rb +125 -0
- data/test/oracle_reset_column_information_test.rb +8 -0
- data/test/oracle_simple_test.rb +18 -0
- data/test/oracle_specific_test.rb +83 -0
- data/test/postgres_db_create_test.rb +32 -0
- data/test/postgres_drop_db_test.rb +16 -0
- data/test/postgres_information_schema_leak_test.rb +29 -0
- data/test/postgres_mixed_case_test.rb +29 -0
- data/test/postgres_native_type_mapping_test.rb +93 -0
- data/test/postgres_nonseq_pkey_test.rb +38 -0
- data/test/postgres_reserved_test.rb +22 -0
- data/test/postgres_reset_column_information_test.rb +8 -0
- data/test/postgres_schema_search_path_test.rb +48 -0
- data/test/postgres_simple_test.rb +168 -0
- data/test/postgres_table_alias_length_test.rb +15 -0
- data/test/postgres_type_conversion_test.rb +34 -0
- data/test/row_locking.rb +90 -0
- data/test/simple.rb +731 -0
- data/test/sqlite3_reset_column_information_test.rb +8 -0
- data/test/sqlite3_simple_test.rb +316 -0
- data/test/sybase_jtds_simple_test.rb +28 -0
- data/test/sybase_reset_column_information_test.rb +8 -0
- metadata +288 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class ActiveRecord::Base
|
|
2
|
+
class << self
|
|
3
|
+
def oracle_connection(config)
|
|
4
|
+
config[:port] ||= 1521
|
|
5
|
+
config[:url] ||= "jdbc:oracle:thin:@#{config[:host]}:#{config[:port]}:#{config[:database]}"
|
|
6
|
+
config[:driver] ||= "oracle.jdbc.driver.OracleDriver"
|
|
7
|
+
config[:adapter_spec] = ::ArJdbc::Oracle
|
|
8
|
+
jdbc_connection(config)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
@@ -0,0 +1,861 @@
|
|
|
1
|
+
module ActiveRecord::ConnectionAdapters
|
|
2
|
+
PostgreSQLAdapter = Class.new(AbstractAdapter) unless const_defined?(:PostgreSQLAdapter)
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
module ::ArJdbc
|
|
6
|
+
module PostgreSQL
|
|
7
|
+
def self.extended(mod)
|
|
8
|
+
(class << mod; self; end).class_eval do
|
|
9
|
+
alias_chained_method :columns, :query_cache, :pg_columns
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.column_selector
|
|
14
|
+
[/postgre/i, lambda {|cfg,col| col.extend(::ArJdbc::PostgreSQL::Column)}]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.jdbc_connection_class
|
|
18
|
+
::ActiveRecord::ConnectionAdapters::PostgresJdbcConnection
|
|
19
|
+
end
|
|
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
|
|
23
|
+
module Column
|
|
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
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def extract_limit(sql_type)
|
|
100
|
+
case sql_type
|
|
101
|
+
when /^bigint/i then 8
|
|
102
|
+
when /^smallint/i then 2
|
|
103
|
+
else super
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
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
|
|
111
|
+
end
|
|
112
|
+
|
|
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
|
|
117
|
+
else
|
|
118
|
+
super
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
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
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
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
|
+
}
|
|
180
|
+
|
|
181
|
+
def adapter_name #:nodoc:
|
|
182
|
+
ADAPTER_NAME
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def self.arel2_visitors(config)
|
|
186
|
+
{}.tap {|v| %w(postgresql pg jdbcpostgresql).each {|a| v[a] = ::Arel::Visitors::PostgreSQL } }
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def postgresql_version
|
|
190
|
+
@postgresql_version ||=
|
|
191
|
+
begin
|
|
192
|
+
value = select_value('SELECT version()')
|
|
193
|
+
if value =~ /PostgreSQL (\d+)\.(\d+)\.(\d+)/
|
|
194
|
+
($1.to_i * 10000) + ($2.to_i * 100) + $3.to_i
|
|
195
|
+
else
|
|
196
|
+
0
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def native_database_types
|
|
202
|
+
NATIVE_DATABASE_TYPES
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Does PostgreSQL support migrations?
|
|
206
|
+
def supports_migrations?
|
|
207
|
+
true
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Does PostgreSQL support standard conforming strings?
|
|
211
|
+
def supports_standard_conforming_strings?
|
|
212
|
+
# Temporarily set the client message level above error to prevent unintentional
|
|
213
|
+
# error messages in the logs when working on a PostgreSQL database server that
|
|
214
|
+
# does not support standard conforming strings.
|
|
215
|
+
client_min_messages_old = client_min_messages
|
|
216
|
+
self.client_min_messages = 'panic'
|
|
217
|
+
|
|
218
|
+
# postgres-pr does not raise an exception when client_min_messages is set higher
|
|
219
|
+
# than error and "SHOW standard_conforming_strings" fails, but returns an empty
|
|
220
|
+
# PGresult instead.
|
|
221
|
+
has_support = select('SHOW standard_conforming_strings').to_a[0][0] rescue false
|
|
222
|
+
self.client_min_messages = client_min_messages_old
|
|
223
|
+
has_support
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def supports_insert_with_returning?
|
|
227
|
+
postgresql_version >= 80200
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def supports_ddl_transactions?
|
|
231
|
+
true
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def supports_savepoints?
|
|
235
|
+
true
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def create_savepoint
|
|
239
|
+
execute("SAVEPOINT #{current_savepoint_name}")
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def rollback_to_savepoint
|
|
243
|
+
execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def release_savepoint
|
|
247
|
+
execute("RELEASE SAVEPOINT #{current_savepoint_name}")
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Returns the configured supported identifier length supported by PostgreSQL,
|
|
251
|
+
# or report the default of 63 on PostgreSQL 7.x.
|
|
252
|
+
def table_alias_length
|
|
253
|
+
@table_alias_length ||= (postgresql_version >= 80000 ? select_one('SHOW max_identifier_length')['max_identifier_length'].to_i : 63)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def default_sequence_name(table_name, pk = nil)
|
|
257
|
+
default_pk, default_seq = pk_and_sequence_for(table_name)
|
|
258
|
+
default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq"
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Resets sequence to the max value of the table's pk if present.
|
|
262
|
+
def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
|
|
263
|
+
unless pk and sequence
|
|
264
|
+
default_pk, default_sequence = pk_and_sequence_for(table)
|
|
265
|
+
pk ||= default_pk
|
|
266
|
+
sequence ||= default_sequence
|
|
267
|
+
end
|
|
268
|
+
if pk
|
|
269
|
+
if sequence
|
|
270
|
+
quoted_sequence = quote_column_name(sequence)
|
|
271
|
+
|
|
272
|
+
select_value <<-end_sql, 'Reset sequence'
|
|
273
|
+
SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
|
|
274
|
+
end_sql
|
|
275
|
+
else
|
|
276
|
+
@logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Find a table's primary key and sequence.
|
|
282
|
+
def pk_and_sequence_for(table) #:nodoc:
|
|
283
|
+
# First try looking for a sequence with a dependency on the
|
|
284
|
+
# given table's primary key.
|
|
285
|
+
result = select(<<-end_sql, 'PK and serial sequence')[0]
|
|
286
|
+
SELECT attr.attname, seq.relname
|
|
287
|
+
FROM pg_class seq,
|
|
288
|
+
pg_attribute attr,
|
|
289
|
+
pg_depend dep,
|
|
290
|
+
pg_namespace name,
|
|
291
|
+
pg_constraint cons
|
|
292
|
+
WHERE seq.oid = dep.objid
|
|
293
|
+
AND seq.relkind = 'S'
|
|
294
|
+
AND attr.attrelid = dep.refobjid
|
|
295
|
+
AND attr.attnum = dep.refobjsubid
|
|
296
|
+
AND attr.attrelid = cons.conrelid
|
|
297
|
+
AND attr.attnum = cons.conkey[1]
|
|
298
|
+
AND cons.contype = 'p'
|
|
299
|
+
AND dep.refobjid = '#{quote_table_name(table)}'::regclass
|
|
300
|
+
end_sql
|
|
301
|
+
|
|
302
|
+
if result.nil? or result.empty?
|
|
303
|
+
# If that fails, try parsing the primary key's default value.
|
|
304
|
+
# Support the 7.x and 8.0 nextval('foo'::text) as well as
|
|
305
|
+
# the 8.1+ nextval('foo'::regclass).
|
|
306
|
+
result = select(<<-end_sql, 'PK and custom sequence')[0]
|
|
307
|
+
SELECT attr.attname,
|
|
308
|
+
CASE
|
|
309
|
+
WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
|
|
310
|
+
substr(split_part(def.adsrc, '''', 2),
|
|
311
|
+
strpos(split_part(def.adsrc, '''', 2), '.')+1)
|
|
312
|
+
ELSE split_part(def.adsrc, '''', 2)
|
|
313
|
+
END as relname
|
|
314
|
+
FROM pg_class t
|
|
315
|
+
JOIN pg_attribute attr ON (t.oid = attrelid)
|
|
316
|
+
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
|
|
317
|
+
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
|
|
318
|
+
WHERE t.oid = '#{quote_table_name(table)}'::regclass
|
|
319
|
+
AND cons.contype = 'p'
|
|
320
|
+
AND def.adsrc ~* 'nextval'
|
|
321
|
+
end_sql
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
[result["attname"], result["relname"]]
|
|
325
|
+
rescue
|
|
326
|
+
nil
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# Insert logic for pre-AR-3.1 adapters
|
|
330
|
+
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
|
|
331
|
+
# Extract the table from the insert sql. Yuck.
|
|
332
|
+
table = sql.split(" ", 4)[2].gsub('"', '')
|
|
333
|
+
|
|
334
|
+
# Try an insert with 'returning id' if available (PG >= 8.2)
|
|
335
|
+
if supports_insert_with_returning? && id_value.nil?
|
|
336
|
+
pk, sequence_name = *pk_and_sequence_for(table) unless pk
|
|
337
|
+
if pk
|
|
338
|
+
sql = substitute_binds(sql, binds)
|
|
339
|
+
id_value = select_value("#{sql} RETURNING #{quote_column_name(pk)}")
|
|
340
|
+
clear_query_cache #FIXME: Why now?
|
|
341
|
+
return id_value
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Otherwise, plain insert
|
|
346
|
+
execute(sql, name, binds)
|
|
347
|
+
|
|
348
|
+
# Don't need to look up id_value if we already have it.
|
|
349
|
+
# (and can't in case of non-sequence PK)
|
|
350
|
+
unless id_value
|
|
351
|
+
# If neither pk nor sequence name is given, look them up.
|
|
352
|
+
unless pk || sequence_name
|
|
353
|
+
pk, sequence_name = *pk_and_sequence_for(table)
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# If a pk is given, fallback to default sequence name.
|
|
357
|
+
# Don't fetch last insert id for a table without a pk.
|
|
358
|
+
if pk && sequence_name ||= default_sequence_name(table, pk)
|
|
359
|
+
id_value = last_insert_id(table, sequence_name)
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
id_value
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def primary_key(table)
|
|
366
|
+
pk_and_sequence = pk_and_sequence_for(table)
|
|
367
|
+
pk_and_sequence && pk_and_sequence.first
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# taken from rails postgresql adapter
|
|
371
|
+
# https://github.com/gfmurphy/rails/blob/master/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L611
|
|
372
|
+
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
|
|
373
|
+
unless pk
|
|
374
|
+
table_ref = extract_table_ref_from_insert_sql(sql)
|
|
375
|
+
pk = primary_key(table_ref) if table_ref
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk
|
|
379
|
+
|
|
380
|
+
[sql, binds]
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def pg_columns(table_name, name=nil)
|
|
384
|
+
column_definitions(table_name).map do |row|
|
|
385
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLColumn.new(
|
|
386
|
+
row["column_name"], row["column_default"], row["column_type"],
|
|
387
|
+
row["column_not_null"] == "f")
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
# current database name
|
|
392
|
+
def current_database
|
|
393
|
+
exec_query("select current_database() as database").
|
|
394
|
+
first["database"]
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# current database encoding
|
|
398
|
+
def encoding
|
|
399
|
+
exec_query(<<-end_sql).first["encoding"]
|
|
400
|
+
SELECT pg_encoding_to_char(pg_database.encoding) as encoding
|
|
401
|
+
FROM pg_database
|
|
402
|
+
WHERE pg_database.datname LIKE '#{current_database}'
|
|
403
|
+
end_sql
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# Sets the maximum number columns postgres has, default 32
|
|
407
|
+
def multi_column_index_limit=(limit)
|
|
408
|
+
@multi_column_index_limit = limit
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# Gets the maximum number columns postgres has, default 32
|
|
412
|
+
def multi_column_index_limit
|
|
413
|
+
defined?(@multi_column_index_limit) && @multi_column_index_limit || 32
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
# Based on postgresql_adapter.rb
|
|
417
|
+
def indexes(table_name, name = nil)
|
|
418
|
+
schema_search_path = @config[:schema_search_path] || select_rows('SHOW search_path')[0][0]
|
|
419
|
+
schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
|
|
420
|
+
result = select_rows(<<-SQL, name)
|
|
421
|
+
SELECT i.relname, d.indisunique, a.attname, a.attnum, d.indkey
|
|
422
|
+
FROM pg_class t, pg_class i, pg_index d, pg_attribute a,
|
|
423
|
+
generate_series(0,#{multi_column_index_limit - 1}) AS s(i)
|
|
424
|
+
WHERE i.relkind = 'i'
|
|
425
|
+
AND d.indexrelid = i.oid
|
|
426
|
+
AND d.indisprimary = 'f'
|
|
427
|
+
AND t.oid = d.indrelid
|
|
428
|
+
AND t.relname = '#{table_name}'
|
|
429
|
+
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas}) )
|
|
430
|
+
AND a.attrelid = t.oid
|
|
431
|
+
AND d.indkey[s.i]=a.attnum
|
|
432
|
+
ORDER BY i.relname
|
|
433
|
+
SQL
|
|
434
|
+
|
|
435
|
+
current_index = nil
|
|
436
|
+
indexes = []
|
|
437
|
+
|
|
438
|
+
insertion_order = []
|
|
439
|
+
index_order = nil
|
|
440
|
+
|
|
441
|
+
result.each do |row|
|
|
442
|
+
if current_index != row[0]
|
|
443
|
+
|
|
444
|
+
(index_order = row[4].split(' ')).each_with_index{ |v, i| index_order[i] = v.to_i }
|
|
445
|
+
indexes << ::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, row[0], row[1] == "t", [])
|
|
446
|
+
current_index = row[0]
|
|
447
|
+
end
|
|
448
|
+
insertion_order = row[3]
|
|
449
|
+
ind = index_order.index(insertion_order)
|
|
450
|
+
indexes.last.columns[ind] = row[2]
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
indexes
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
# take id from result of insert query
|
|
457
|
+
def last_inserted_id(result)
|
|
458
|
+
if result.is_a? Fixnum
|
|
459
|
+
result
|
|
460
|
+
else
|
|
461
|
+
result.first.first[1]
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
def last_insert_id(table, sequence_name)
|
|
466
|
+
Integer(select_value("SELECT currval('#{sequence_name}')"))
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def recreate_database(name)
|
|
470
|
+
drop_database(name)
|
|
471
|
+
create_database(name)
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
def create_database(name, options = {})
|
|
475
|
+
execute "CREATE DATABASE \"#{name}\" ENCODING='#{options[:encoding] || 'utf8'}'"
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def drop_database(name)
|
|
479
|
+
execute "DROP DATABASE IF EXISTS \"#{name}\""
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def create_schema(schema_name, pg_username)
|
|
483
|
+
execute("CREATE SCHEMA \"#{schema_name}\" AUTHORIZATION \"#{pg_username}\"")
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
def drop_schema(schema_name)
|
|
487
|
+
execute("DROP SCHEMA \"#{schema_name}\"")
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
def all_schemas
|
|
491
|
+
select('select nspname from pg_namespace').map {|r| r["nspname"] }
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
def structure_dump
|
|
495
|
+
database = @config[:database]
|
|
496
|
+
if database.nil?
|
|
497
|
+
if @config[:url] =~ /\/([^\/]*)$/
|
|
498
|
+
database = $1
|
|
499
|
+
else
|
|
500
|
+
raise "Could not figure out what database this url is for #{@config["url"]}"
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
ENV['PGHOST'] = @config[:host] if @config[:host]
|
|
505
|
+
ENV['PGPORT'] = @config[:port].to_s if @config[:port]
|
|
506
|
+
ENV['PGPASSWORD'] = @config[:password].to_s if @config[:password]
|
|
507
|
+
search_path = @config[:schema_search_path]
|
|
508
|
+
search_path = "--schema=#{search_path}" if search_path
|
|
509
|
+
|
|
510
|
+
@connection.connection.close
|
|
511
|
+
begin
|
|
512
|
+
definition = `pg_dump -i -U "#{@config[:username]}" -s -x -O #{search_path} #{database}`
|
|
513
|
+
raise "Error dumping database" if $?.exitstatus == 1
|
|
514
|
+
|
|
515
|
+
# need to patch away any references to SQL_ASCII as it breaks the JDBC driver
|
|
516
|
+
definition.gsub(/SQL_ASCII/, 'UNICODE')
|
|
517
|
+
ensure
|
|
518
|
+
reconnect!
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
# SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
|
|
523
|
+
#
|
|
524
|
+
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
|
|
525
|
+
# requires that the ORDER BY include the distinct column.
|
|
526
|
+
#
|
|
527
|
+
# distinct("posts.id", "posts.created_at desc")
|
|
528
|
+
def distinct(columns, orders) #:nodoc:
|
|
529
|
+
return "DISTINCT #{columns}" if orders.empty?
|
|
530
|
+
|
|
531
|
+
# Construct a clean list of column names from the ORDER BY clause, removing
|
|
532
|
+
# any ASC/DESC modifiers
|
|
533
|
+
order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*/i, '') }.
|
|
534
|
+
reject(&:blank?)
|
|
535
|
+
order_columns = order_columns.
|
|
536
|
+
zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
|
|
537
|
+
|
|
538
|
+
"DISTINCT #{columns}, #{order_columns * ', '}"
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
# ORDER BY clause for the passed order option.
|
|
542
|
+
#
|
|
543
|
+
# PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this
|
|
544
|
+
# by wrapping the sql as a sub-select and ordering in that query.
|
|
545
|
+
def add_order_by_for_association_limiting!(sql, options)
|
|
546
|
+
return sql if options[:order].blank?
|
|
547
|
+
|
|
548
|
+
order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
|
|
549
|
+
order.map! { |s| 'DESC' if s =~ /\bdesc$/i }
|
|
550
|
+
order = order.zip((0...order.size).to_a).map { |s,i| "id_list.alias_#{i} #{s}" }.join(', ')
|
|
551
|
+
|
|
552
|
+
sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}"
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
# from postgres_adapter.rb in rails project
|
|
556
|
+
# https://github.com/rails/rails/blob/3-1-stable/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L412
|
|
557
|
+
# Quotes PostgreSQL-specific data types for SQL input.
|
|
558
|
+
def quote(value, column = nil) #:nodoc:
|
|
559
|
+
return super unless column
|
|
560
|
+
|
|
561
|
+
case value
|
|
562
|
+
when Float
|
|
563
|
+
return super unless value.infinite? && column.type == :datetime
|
|
564
|
+
"'#{value.to_s.downcase}'"
|
|
565
|
+
when Numeric
|
|
566
|
+
return super unless column.sql_type == 'money'
|
|
567
|
+
# Not truly string input, so doesn't require (or allow) escape string syntax.
|
|
568
|
+
"'#{value}'"
|
|
569
|
+
when String
|
|
570
|
+
case column.sql_type
|
|
571
|
+
when 'bytea' then "'#{escape_bytea(value)}'"
|
|
572
|
+
when 'xml' then "xml '#{quote_string(value)}'"
|
|
573
|
+
when /^bit/
|
|
574
|
+
case value
|
|
575
|
+
when /^[01]*$/ then "B'#{value}'" # Bit-string notation
|
|
576
|
+
when /^[0-9A-F]*$/i then "X'#{value}'" # Hexadecimal notation
|
|
577
|
+
end
|
|
578
|
+
else
|
|
579
|
+
super
|
|
580
|
+
end
|
|
581
|
+
else
|
|
582
|
+
super
|
|
583
|
+
end
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
def escape_bytea(s)
|
|
587
|
+
if s
|
|
588
|
+
result = ''
|
|
589
|
+
s.each_byte { |c| result << sprintf('\\\\%03o', c) }
|
|
590
|
+
result
|
|
591
|
+
end
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
def quote_table_name(name)
|
|
595
|
+
schema, name_part = extract_pg_identifier_from_name(name.to_s)
|
|
596
|
+
|
|
597
|
+
unless name_part
|
|
598
|
+
quote_column_name(schema)
|
|
599
|
+
else
|
|
600
|
+
table_name, name_part = extract_pg_identifier_from_name(name_part)
|
|
601
|
+
"#{quote_column_name(schema)}.#{quote_column_name(table_name)}"
|
|
602
|
+
end
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
def quote_column_name(name)
|
|
606
|
+
%("#{name.to_s.gsub("\"", "\"\"")}")
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
def quoted_date(value) #:nodoc:
|
|
610
|
+
if value.acts_like?(:time) && value.respond_to?(:usec)
|
|
611
|
+
"#{super}.#{sprintf("%06d", value.usec)}"
|
|
612
|
+
else
|
|
613
|
+
super
|
|
614
|
+
end
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
def disable_referential_integrity(&block) #:nodoc:
|
|
618
|
+
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
|
|
619
|
+
yield
|
|
620
|
+
ensure
|
|
621
|
+
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
def rename_table(name, new_name)
|
|
625
|
+
execute "ALTER TABLE #{name} RENAME TO #{new_name}"
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
# Adds a new column to the named table.
|
|
629
|
+
# See TableDefinition#column for details of the options you can use.
|
|
630
|
+
def add_column(table_name, column_name, type, options = {})
|
|
631
|
+
default = options[:default]
|
|
632
|
+
notnull = options[:null] == false
|
|
633
|
+
|
|
634
|
+
# Add the column.
|
|
635
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}")
|
|
636
|
+
|
|
637
|
+
change_column_default(table_name, column_name, default) if options_include_default?(options)
|
|
638
|
+
change_column_null(table_name, column_name, false, default) if notnull
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
# Changes the column of a table.
|
|
642
|
+
def change_column(table_name, column_name, type, options = {})
|
|
643
|
+
quoted_table_name = quote_table_name(table_name)
|
|
644
|
+
|
|
645
|
+
begin
|
|
646
|
+
execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
|
647
|
+
rescue ActiveRecord::StatementInvalid => e
|
|
648
|
+
raise e if postgresql_version > 80000
|
|
649
|
+
# This is PostgreSQL 7.x, so we have to use a more arcane way of doing it.
|
|
650
|
+
begin
|
|
651
|
+
begin_db_transaction
|
|
652
|
+
tmp_column_name = "#{column_name}_ar_tmp"
|
|
653
|
+
add_column(table_name, tmp_column_name, type, options)
|
|
654
|
+
execute "UPDATE #{quoted_table_name} SET #{quote_column_name(tmp_column_name)} = CAST(#{quote_column_name(column_name)} AS #{type_to_sql(type, options[:limit], options[:precision], options[:scale])})"
|
|
655
|
+
remove_column(table_name, column_name)
|
|
656
|
+
rename_column(table_name, tmp_column_name, column_name)
|
|
657
|
+
commit_db_transaction
|
|
658
|
+
rescue
|
|
659
|
+
rollback_db_transaction
|
|
660
|
+
end
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
|
|
664
|
+
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
# Changes the default value of a table column.
|
|
668
|
+
def change_column_default(table_name, column_name, default)
|
|
669
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
def change_column_null(table_name, column_name, null, default = nil)
|
|
673
|
+
unless null || default.nil?
|
|
674
|
+
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
|
675
|
+
end
|
|
676
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
|
680
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
|
681
|
+
end
|
|
682
|
+
|
|
683
|
+
def remove_index!(table_name, index_name) #:nodoc:
|
|
684
|
+
execute "DROP INDEX #{quote_table_name(index_name)}"
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
def index_name_length
|
|
688
|
+
63
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
# Maps logical Rails types to PostgreSQL-specific data types.
|
|
692
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
|
693
|
+
return super unless type.to_s == 'integer'
|
|
694
|
+
return 'integer' unless limit
|
|
695
|
+
|
|
696
|
+
case limit
|
|
697
|
+
when 1, 2; 'smallint'
|
|
698
|
+
when 3, 4; 'integer'
|
|
699
|
+
when 5..8; 'bigint'
|
|
700
|
+
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
|
|
701
|
+
end
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
def tables(name = nil)
|
|
705
|
+
exec_query(<<-SQL, 'SCHEMA').map { |row| row["tablename"] }
|
|
706
|
+
SELECT tablename
|
|
707
|
+
FROM pg_tables
|
|
708
|
+
WHERE schemaname = ANY (current_schemas(false))
|
|
709
|
+
SQL
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
def table_exists?(name)
|
|
713
|
+
schema, table = extract_schema_and_table(name.to_s)
|
|
714
|
+
return false unless table # Abstract classes is having nil table name
|
|
715
|
+
|
|
716
|
+
binds = [[nil, table.gsub(/(^"|"$)/,'')]]
|
|
717
|
+
binds << [nil, schema] if schema
|
|
718
|
+
|
|
719
|
+
exec_query(<<-SQL, 'SCHEMA', binds).first["table_count"] > 0
|
|
720
|
+
SELECT COUNT(*) as table_count
|
|
721
|
+
FROM pg_tables
|
|
722
|
+
WHERE tablename = ?
|
|
723
|
+
AND schemaname = #{schema ? "?" : "ANY (current_schemas(false))"}
|
|
724
|
+
SQL
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
# Extracts the table and schema name from +name+
|
|
728
|
+
def extract_schema_and_table(name)
|
|
729
|
+
schema, table = name.split('.', 2)
|
|
730
|
+
|
|
731
|
+
unless table # A table was provided without a schema
|
|
732
|
+
table = schema
|
|
733
|
+
schema = nil
|
|
734
|
+
end
|
|
735
|
+
|
|
736
|
+
if name =~ /^"/ # Handle quoted table names
|
|
737
|
+
table = name
|
|
738
|
+
schema = nil
|
|
739
|
+
end
|
|
740
|
+
[schema, table]
|
|
741
|
+
end
|
|
742
|
+
|
|
743
|
+
private
|
|
744
|
+
def translate_exception(exception, message)
|
|
745
|
+
case exception.message
|
|
746
|
+
when /duplicate key value violates unique constraint/
|
|
747
|
+
::ActiveRecord::RecordNotUnique.new(message, exception)
|
|
748
|
+
when /violates foreign key constraint/
|
|
749
|
+
::ActiveRecord::InvalidForeignKey.new(message, exception)
|
|
750
|
+
else
|
|
751
|
+
super
|
|
752
|
+
end
|
|
753
|
+
end
|
|
754
|
+
|
|
755
|
+
# Returns the list of a table's column names, data types, and default values.
|
|
756
|
+
#
|
|
757
|
+
# The underlying query is roughly:
|
|
758
|
+
# SELECT column.name, column.type, default.value
|
|
759
|
+
# FROM column LEFT JOIN default
|
|
760
|
+
# ON column.table_id = default.table_id
|
|
761
|
+
# AND column.num = default.column_num
|
|
762
|
+
# WHERE column.table_id = get_table_id('table_name')
|
|
763
|
+
# AND column.num > 0
|
|
764
|
+
# AND NOT column.is_dropped
|
|
765
|
+
# ORDER BY column.num
|
|
766
|
+
#
|
|
767
|
+
# If the table name is not prefixed with a schema, the database will
|
|
768
|
+
# take the first match from the schema search path.
|
|
769
|
+
#
|
|
770
|
+
# Query implementation notes:
|
|
771
|
+
# - format_type includes the column size constraint, e.g. varchar(50)
|
|
772
|
+
# - ::regclass is a function that gives the id for a table name
|
|
773
|
+
def column_definitions(table_name) #:nodoc:
|
|
774
|
+
exec_query(<<-end_sql, 'SCHEMA')
|
|
775
|
+
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
|
|
776
|
+
FROM pg_attribute a LEFT JOIN pg_attrdef d
|
|
777
|
+
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
|
778
|
+
WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
|
|
779
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
|
780
|
+
ORDER BY a.attnum
|
|
781
|
+
end_sql
|
|
782
|
+
end
|
|
783
|
+
|
|
784
|
+
def extract_pg_identifier_from_name(name)
|
|
785
|
+
match_data = name[0,1] == '"' ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
|
|
786
|
+
|
|
787
|
+
if match_data
|
|
788
|
+
rest = name[match_data[0].length..-1]
|
|
789
|
+
rest = rest[1..-1] if rest[0,1] == "."
|
|
790
|
+
[match_data[1], (rest.length > 0 ? rest : nil)]
|
|
791
|
+
end
|
|
792
|
+
end
|
|
793
|
+
|
|
794
|
+
# from rails postgresl_adapter
|
|
795
|
+
def extract_table_ref_from_insert_sql(sql)
|
|
796
|
+
sql[/into\s+([^\(]*).*values\s*\(/i]
|
|
797
|
+
$1.strip if $1
|
|
798
|
+
end
|
|
799
|
+
end
|
|
800
|
+
end
|
|
801
|
+
|
|
802
|
+
module ActiveRecord::ConnectionAdapters
|
|
803
|
+
remove_const(:PostgreSQLAdapter) if const_defined?(:PostgreSQLAdapter)
|
|
804
|
+
|
|
805
|
+
class PostgreSQLColumn < JdbcColumn
|
|
806
|
+
include ArJdbc::PostgreSQL::Column
|
|
807
|
+
|
|
808
|
+
def initialize(name, *args)
|
|
809
|
+
if Hash === name
|
|
810
|
+
super
|
|
811
|
+
else
|
|
812
|
+
super(nil, name, *args)
|
|
813
|
+
end
|
|
814
|
+
end
|
|
815
|
+
|
|
816
|
+
def call_discovered_column_callbacks(*)
|
|
817
|
+
end
|
|
818
|
+
end
|
|
819
|
+
|
|
820
|
+
class PostgresJdbcConnection < JdbcConnection
|
|
821
|
+
alias :java_native_database_types :set_native_database_types
|
|
822
|
+
|
|
823
|
+
# override to prevent connection from loading hash from jdbc
|
|
824
|
+
# metadata, which can be expensive. We can do this since
|
|
825
|
+
# native_database_types is defined in the adapter to use a static hash
|
|
826
|
+
# not relying on the driver's metadata
|
|
827
|
+
def set_native_database_types
|
|
828
|
+
@native_types = {}
|
|
829
|
+
end
|
|
830
|
+
end
|
|
831
|
+
|
|
832
|
+
class PostgreSQLAdapter < JdbcAdapter
|
|
833
|
+
include ArJdbc::PostgreSQL
|
|
834
|
+
|
|
835
|
+
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
|
836
|
+
def xml(*args)
|
|
837
|
+
options = args.extract_options!
|
|
838
|
+
column(args[0], "xml", options)
|
|
839
|
+
end
|
|
840
|
+
|
|
841
|
+
def tsvector(*args)
|
|
842
|
+
options = args.extract_options!
|
|
843
|
+
column(args[0], "tsvector", options)
|
|
844
|
+
end
|
|
845
|
+
end
|
|
846
|
+
|
|
847
|
+
def table_definition
|
|
848
|
+
TableDefinition.new(self)
|
|
849
|
+
end
|
|
850
|
+
|
|
851
|
+
def jdbc_connection_class(spec)
|
|
852
|
+
::ArJdbc::PostgreSQL.jdbc_connection_class
|
|
853
|
+
end
|
|
854
|
+
|
|
855
|
+
def jdbc_column_class
|
|
856
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLColumn
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
alias_chained_method :columns, :query_cache, :pg_columns
|
|
860
|
+
end
|
|
861
|
+
end
|