activerecord-jdbc-adapter 1.0.3-java → 50.1-java

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 (268) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +33 -0
  3. data/.travis.yml +79 -0
  4. data/.yardopts +4 -0
  5. data/CONTRIBUTING.md +50 -0
  6. data/Gemfile +91 -0
  7. data/History.md +1191 -0
  8. data/LICENSE.txt +22 -17
  9. data/README.md +169 -0
  10. data/RUNNING_TESTS.md +127 -0
  11. data/Rakefile +294 -5
  12. data/Rakefile.jdbc +20 -0
  13. data/activerecord-jdbc-adapter.gemspec +55 -0
  14. data/lib/active_record/connection_adapters/as400_adapter.rb +2 -0
  15. data/lib/active_record/connection_adapters/db2_adapter.rb +1 -0
  16. data/lib/active_record/connection_adapters/firebird_adapter.rb +1 -0
  17. data/lib/active_record/connection_adapters/mariadb_adapter.rb +1 -0
  18. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +1 -0
  19. data/lib/activerecord-jdbc-adapter.rb +0 -5
  20. data/lib/arel/visitors/compat.rb +60 -0
  21. data/lib/arel/visitors/db2.rb +128 -6
  22. data/lib/arel/visitors/derby.rb +103 -10
  23. data/lib/arel/visitors/firebird.rb +79 -0
  24. data/lib/arel/visitors/h2.rb +25 -0
  25. data/lib/arel/visitors/hsqldb.rb +18 -10
  26. data/lib/arel/visitors/postgresql_jdbc.rb +6 -0
  27. data/lib/arel/visitors/sql_server.rb +225 -0
  28. data/lib/arel/visitors/sql_server/ng42.rb +293 -0
  29. data/lib/arjdbc.rb +11 -21
  30. data/lib/arjdbc/abstract/connection_management.rb +35 -0
  31. data/lib/arjdbc/abstract/core.rb +64 -0
  32. data/lib/arjdbc/abstract/database_statements.rb +64 -0
  33. data/lib/arjdbc/abstract/statement_cache.rb +58 -0
  34. data/lib/arjdbc/abstract/transaction_support.rb +86 -0
  35. data/lib/arjdbc/db2.rb +3 -1
  36. data/lib/arjdbc/db2/adapter.rb +630 -250
  37. data/lib/arjdbc/db2/as400.rb +130 -0
  38. data/lib/arjdbc/db2/column.rb +167 -0
  39. data/lib/arjdbc/db2/connection_methods.rb +44 -0
  40. data/lib/arjdbc/derby.rb +1 -5
  41. data/lib/arjdbc/derby/active_record_patch.rb +13 -0
  42. data/lib/arjdbc/derby/adapter.rb +409 -217
  43. data/lib/arjdbc/derby/connection_methods.rb +16 -14
  44. data/lib/arjdbc/derby/schema_creation.rb +15 -0
  45. data/lib/arjdbc/discover.rb +62 -50
  46. data/lib/arjdbc/firebird.rb +3 -1
  47. data/lib/arjdbc/firebird/adapter.rb +365 -62
  48. data/lib/arjdbc/firebird/connection_methods.rb +23 -0
  49. data/lib/arjdbc/h2.rb +2 -3
  50. data/lib/arjdbc/h2/adapter.rb +273 -6
  51. data/lib/arjdbc/h2/connection_methods.rb +23 -8
  52. data/lib/arjdbc/hsqldb.rb +2 -3
  53. data/lib/arjdbc/hsqldb/adapter.rb +204 -77
  54. data/lib/arjdbc/hsqldb/connection_methods.rb +24 -10
  55. data/lib/arjdbc/hsqldb/explain_support.rb +35 -0
  56. data/lib/arjdbc/hsqldb/schema_creation.rb +11 -0
  57. data/lib/arjdbc/informix.rb +4 -2
  58. data/lib/arjdbc/informix/adapter.rb +78 -54
  59. data/lib/arjdbc/informix/connection_methods.rb +8 -9
  60. data/lib/arjdbc/jdbc.rb +59 -2
  61. data/lib/arjdbc/jdbc/adapter.rb +356 -166
  62. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  63. data/lib/arjdbc/jdbc/adapter_require.rb +46 -0
  64. data/lib/arjdbc/jdbc/base_ext.rb +15 -0
  65. data/lib/arjdbc/jdbc/callbacks.rb +27 -18
  66. data/lib/arjdbc/jdbc/column.rb +79 -20
  67. data/lib/arjdbc/jdbc/connection.rb +5 -119
  68. data/lib/arjdbc/jdbc/connection_methods.rb +32 -4
  69. data/lib/arjdbc/jdbc/error.rb +65 -0
  70. data/lib/arjdbc/jdbc/extension.rb +41 -29
  71. data/lib/arjdbc/jdbc/java.rb +5 -6
  72. data/lib/arjdbc/jdbc/jdbc.rake +3 -126
  73. data/lib/arjdbc/jdbc/railtie.rb +2 -9
  74. data/lib/arjdbc/jdbc/rake_tasks.rb +3 -10
  75. data/lib/arjdbc/jdbc/serialized_attributes_helper.rb +3 -0
  76. data/lib/arjdbc/jdbc/type_cast.rb +166 -0
  77. data/lib/arjdbc/jdbc/type_converter.rb +35 -19
  78. data/lib/arjdbc/mssql.rb +6 -3
  79. data/lib/arjdbc/mssql/adapter.rb +630 -298
  80. data/lib/arjdbc/mssql/column.rb +200 -0
  81. data/lib/arjdbc/mssql/connection_methods.rb +66 -17
  82. data/lib/arjdbc/mssql/explain_support.rb +99 -0
  83. data/lib/arjdbc/mssql/limit_helpers.rb +189 -50
  84. data/lib/arjdbc/mssql/lock_methods.rb +77 -0
  85. data/lib/arjdbc/mssql/types.rb +343 -0
  86. data/lib/arjdbc/mssql/utils.rb +82 -0
  87. data/lib/arjdbc/mysql.rb +2 -3
  88. data/lib/arjdbc/mysql/adapter.rb +86 -356
  89. data/lib/arjdbc/mysql/connection_methods.rb +159 -23
  90. data/lib/arjdbc/oracle/adapter.rb +714 -263
  91. data/lib/arjdbc/postgresql.rb +2 -3
  92. data/lib/arjdbc/postgresql/_bc_time_cast_patch.rb +24 -0
  93. data/lib/arjdbc/postgresql/adapter.rb +570 -400
  94. data/lib/arjdbc/postgresql/base/array_decoder.rb +26 -0
  95. data/lib/arjdbc/postgresql/base/array_encoder.rb +25 -0
  96. data/lib/arjdbc/postgresql/base/array_parser.rb +95 -0
  97. data/lib/arjdbc/postgresql/base/pgconn.rb +11 -0
  98. data/lib/arjdbc/postgresql/column.rb +51 -0
  99. data/lib/arjdbc/postgresql/connection_methods.rb +57 -18
  100. data/lib/arjdbc/postgresql/name.rb +24 -0
  101. data/lib/arjdbc/postgresql/oid_types.rb +192 -0
  102. data/lib/arjdbc/railtie.rb +11 -0
  103. data/lib/arjdbc/sqlite3.rb +2 -3
  104. data/lib/arjdbc/sqlite3/adapter.rb +518 -198
  105. data/lib/arjdbc/sqlite3/connection_methods.rb +49 -24
  106. data/lib/arjdbc/sybase.rb +2 -2
  107. data/lib/arjdbc/sybase/adapter.rb +7 -6
  108. data/lib/arjdbc/tasks.rb +13 -0
  109. data/lib/arjdbc/tasks/database_tasks.rb +52 -0
  110. data/lib/arjdbc/tasks/databases.rake +91 -0
  111. data/lib/arjdbc/tasks/databases3.rake +215 -0
  112. data/lib/arjdbc/tasks/databases4.rake +39 -0
  113. data/lib/arjdbc/tasks/db2_database_tasks.rb +104 -0
  114. data/lib/arjdbc/tasks/derby_database_tasks.rb +95 -0
  115. data/lib/arjdbc/tasks/h2_database_tasks.rb +31 -0
  116. data/lib/arjdbc/tasks/hsqldb_database_tasks.rb +70 -0
  117. data/lib/arjdbc/tasks/jdbc_database_tasks.rb +169 -0
  118. data/lib/arjdbc/tasks/mssql_database_tasks.rb +46 -0
  119. data/lib/arjdbc/util/quoted_cache.rb +60 -0
  120. data/lib/arjdbc/util/serialized_attributes.rb +98 -0
  121. data/lib/arjdbc/util/table_copier.rb +110 -0
  122. data/lib/arjdbc/version.rb +1 -6
  123. data/lib/generators/jdbc/USAGE +9 -0
  124. data/lib/generators/jdbc/jdbc_generator.rb +8 -0
  125. data/lib/jdbc_adapter.rb +1 -1
  126. data/lib/jdbc_adapter/rake_tasks.rb +3 -2
  127. data/lib/jdbc_adapter/version.rb +2 -1
  128. data/pom.xml +114 -0
  129. data/rails_generators/jdbc_generator.rb +1 -1
  130. data/rails_generators/templates/config/initializers/jdbc.rb +8 -5
  131. data/rails_generators/templates/lib/tasks/jdbc.rake +7 -4
  132. data/rakelib/01-tomcat.rake +51 -0
  133. data/rakelib/02-test.rake +132 -0
  134. data/rakelib/bundler_ext.rb +11 -0
  135. data/rakelib/compile.rake +67 -22
  136. data/rakelib/db.rake +61 -0
  137. data/rakelib/rails.rake +204 -29
  138. data/src/java/arjdbc/ArJdbcModule.java +286 -0
  139. data/src/java/arjdbc/db2/DB2Module.java +76 -0
  140. data/src/java/arjdbc/db2/DB2RubyJdbcConnection.java +126 -0
  141. data/src/java/arjdbc/derby/DerbyModule.java +99 -243
  142. data/src/java/arjdbc/derby/DerbyRubyJdbcConnection.java +152 -0
  143. data/src/java/arjdbc/firebird/FirebirdRubyJdbcConnection.java +174 -0
  144. data/src/java/arjdbc/{jdbc/JdbcConnectionFactory.java → h2/H2Module.java} +20 -6
  145. data/src/java/arjdbc/h2/H2RubyJdbcConnection.java +27 -12
  146. data/src/java/arjdbc/hsqldb/HSQLDBModule.java +73 -0
  147. data/src/java/arjdbc/informix/InformixRubyJdbcConnection.java +7 -6
  148. data/src/java/arjdbc/jdbc/AdapterJavaService.java +7 -29
  149. data/src/java/arjdbc/jdbc/Callable.java +44 -0
  150. data/src/java/arjdbc/jdbc/ConnectionFactory.java +132 -0
  151. data/src/java/arjdbc/jdbc/DataSourceConnectionFactory.java +157 -0
  152. data/src/java/arjdbc/jdbc/DriverConnectionFactory.java +63 -0
  153. data/src/java/arjdbc/jdbc/DriverWrapper.java +119 -0
  154. data/src/java/arjdbc/jdbc/JdbcResult.java +130 -0
  155. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +3622 -948
  156. data/src/java/arjdbc/mssql/MSSQLModule.java +90 -0
  157. data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +181 -0
  158. data/src/java/arjdbc/mysql/MySQLModule.java +99 -81
  159. data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +294 -0
  160. data/src/java/arjdbc/oracle/OracleModule.java +80 -0
  161. data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +387 -17
  162. data/src/java/arjdbc/postgresql/ByteaUtils.java +157 -0
  163. data/src/java/arjdbc/postgresql/PgResultSetMetaDataWrapper.java +23 -0
  164. data/src/java/arjdbc/postgresql/PostgreSQLModule.java +77 -0
  165. data/src/java/arjdbc/postgresql/PostgreSQLResult.java +184 -0
  166. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +952 -0
  167. data/src/java/arjdbc/sqlite3/SQLite3Module.java +73 -0
  168. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +525 -0
  169. data/src/java/arjdbc/util/CallResultSet.java +826 -0
  170. data/src/java/arjdbc/util/DateTimeUtils.java +580 -0
  171. data/src/java/arjdbc/util/ObjectSupport.java +65 -0
  172. data/src/java/arjdbc/util/QuotingUtils.java +138 -0
  173. data/src/java/arjdbc/util/StringCache.java +63 -0
  174. data/src/java/arjdbc/util/StringHelper.java +159 -0
  175. metadata +245 -268
  176. data/History.txt +0 -369
  177. data/Manifest.txt +0 -180
  178. data/README.txt +0 -181
  179. data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -1
  180. data/lib/arel/engines/sql/compilers/db2_compiler.rb +0 -9
  181. data/lib/arel/engines/sql/compilers/derby_compiler.rb +0 -6
  182. data/lib/arel/engines/sql/compilers/h2_compiler.rb +0 -6
  183. data/lib/arel/engines/sql/compilers/hsqldb_compiler.rb +0 -15
  184. data/lib/arel/engines/sql/compilers/jdbc_compiler.rb +0 -6
  185. data/lib/arel/engines/sql/compilers/mssql_compiler.rb +0 -46
  186. data/lib/arel/visitors/mssql.rb +0 -44
  187. data/lib/arjdbc/jdbc/compatibility.rb +0 -51
  188. data/lib/arjdbc/jdbc/core_ext.rb +0 -24
  189. data/lib/arjdbc/jdbc/discover.rb +0 -18
  190. data/lib/arjdbc/jdbc/driver.rb +0 -44
  191. data/lib/arjdbc/jdbc/missing_functionality_helper.rb +0 -87
  192. data/lib/arjdbc/jdbc/quoted_primary_key.rb +0 -28
  193. data/lib/arjdbc/jdbc/require_driver.rb +0 -16
  194. data/lib/arjdbc/mimer.rb +0 -2
  195. data/lib/arjdbc/mimer/adapter.rb +0 -142
  196. data/lib/arjdbc/mssql/tsql_helper.rb +0 -61
  197. data/lib/arjdbc/oracle.rb +0 -3
  198. data/lib/arjdbc/oracle/connection_methods.rb +0 -11
  199. data/lib/pg.rb +0 -4
  200. data/rakelib/package.rake +0 -92
  201. data/rakelib/test.rake +0 -81
  202. data/src/java/arjdbc/jdbc/SQLBlock.java +0 -48
  203. data/src/java/arjdbc/mssql/MssqlRubyJdbcConnection.java +0 -127
  204. data/src/java/arjdbc/postgresql/PostgresqlRubyJdbcConnection.java +0 -57
  205. data/src/java/arjdbc/sqlite3/Sqlite3RubyJdbcConnection.java +0 -64
  206. data/test/abstract_db_create.rb +0 -117
  207. data/test/activerecord/connection_adapters/type_conversion_test.rb +0 -31
  208. data/test/activerecord/connections/native_jdbc_mysql/connection.rb +0 -25
  209. data/test/db/db2.rb +0 -11
  210. data/test/db/derby.rb +0 -12
  211. data/test/db/h2.rb +0 -11
  212. data/test/db/hsqldb.rb +0 -13
  213. data/test/db/informix.rb +0 -11
  214. data/test/db/jdbc.rb +0 -11
  215. data/test/db/jndi_config.rb +0 -40
  216. data/test/db/logger.rb +0 -3
  217. data/test/db/mssql.rb +0 -9
  218. data/test/db/mysql.rb +0 -10
  219. data/test/db/oracle.rb +0 -34
  220. data/test/db/postgres.rb +0 -9
  221. data/test/db/sqlite3.rb +0 -11
  222. data/test/db2_simple_test.rb +0 -66
  223. data/test/derby_migration_test.rb +0 -68
  224. data/test/derby_multibyte_test.rb +0 -12
  225. data/test/derby_simple_test.rb +0 -99
  226. data/test/generic_jdbc_connection_test.rb +0 -29
  227. data/test/h2_simple_test.rb +0 -41
  228. data/test/has_many_through.rb +0 -79
  229. data/test/helper.rb +0 -5
  230. data/test/hsqldb_simple_test.rb +0 -6
  231. data/test/informix_simple_test.rb +0 -48
  232. data/test/jdbc_common.rb +0 -25
  233. data/test/jndi_callbacks_test.rb +0 -40
  234. data/test/jndi_test.rb +0 -25
  235. data/test/manualTestDatabase.rb +0 -191
  236. data/test/models/add_not_null_column_to_table.rb +0 -12
  237. data/test/models/auto_id.rb +0 -18
  238. data/test/models/data_types.rb +0 -28
  239. data/test/models/entry.rb +0 -43
  240. data/test/models/mixed_case.rb +0 -25
  241. data/test/models/reserved_word.rb +0 -18
  242. data/test/models/string_id.rb +0 -18
  243. data/test/models/validates_uniqueness_of_string.rb +0 -19
  244. data/test/mssql_db_create_test.rb +0 -26
  245. data/test/mssql_identity_insert_test.rb +0 -19
  246. data/test/mssql_legacy_types_test.rb +0 -58
  247. data/test/mssql_limit_offset_test.rb +0 -136
  248. data/test/mssql_multibyte_test.rb +0 -18
  249. data/test/mssql_simple_test.rb +0 -55
  250. data/test/mysql_db_create_test.rb +0 -27
  251. data/test/mysql_info_test.rb +0 -113
  252. data/test/mysql_multibyte_test.rb +0 -10
  253. data/test/mysql_nonstandard_primary_key_test.rb +0 -42
  254. data/test/mysql_simple_test.rb +0 -49
  255. data/test/oracle_simple_test.rb +0 -18
  256. data/test/oracle_specific_test.rb +0 -83
  257. data/test/pick_rails_version.rb +0 -3
  258. data/test/postgres_db_create_test.rb +0 -32
  259. data/test/postgres_drop_db_test.rb +0 -16
  260. data/test/postgres_mixed_case_test.rb +0 -29
  261. data/test/postgres_nonseq_pkey_test.rb +0 -38
  262. data/test/postgres_reserved_test.rb +0 -22
  263. data/test/postgres_schema_search_path_test.rb +0 -44
  264. data/test/postgres_simple_test.rb +0 -51
  265. data/test/postgres_table_alias_length_test.rb +0 -15
  266. data/test/simple.rb +0 -546
  267. data/test/sqlite3_simple_test.rb +0 -233
  268. data/test/sybase_jtds_simple_test.rb +0 -28
@@ -1,4 +1,3 @@
1
- require 'arjdbc/jdbc'
2
- jdbc_require_driver 'jdbc/postgres'
3
- require 'arjdbc/postgresql/connection_methods'
1
+ require 'arjdbc'
4
2
  require 'arjdbc/postgresql/adapter'
3
+ require 'arjdbc/postgresql/connection_methods'
@@ -0,0 +1,24 @@
1
+ ActiveRecord::ConnectionAdapters::PostgreSQL::OID::DateTime.class_eval do
2
+ def cast_value(value)
3
+ return value unless value.is_a?(::String)
4
+
5
+ case value
6
+ when 'infinity' then ::Float::INFINITY
7
+ when '-infinity' then -::Float::INFINITY
8
+ # when / BC$/
9
+ # astronomical_year = format("%04d", -value[/^\d+/].to_i + 1)
10
+ # super(value.sub(/ BC$/, "").sub(/^\d+/, astronomical_year))
11
+ # else
12
+ # super
13
+ else
14
+ if value.end_with?(' BC')
15
+ astronomical_year = format("%04d", -value[/^\d+/].to_i + 1)
16
+ DateTime.parse("#{value}"[0...-3].sub(/^\d+/, astronomical_year))
17
+ else
18
+ super
19
+ end
20
+ end
21
+ end
22
+
23
+ def apply_seconds_precision(value); value end
24
+ end
@@ -1,404 +1,403 @@
1
- module ActiveRecord::ConnectionAdapters
2
- PostgreSQLAdapter = Class.new(AbstractAdapter) unless const_defined?(:PostgreSQLAdapter)
3
- end
4
-
5
- module ::ArJdbc
1
+ # frozen_string_literal: false
2
+ ArJdbc.load_java_part :PostgreSQL
3
+
4
+ require 'ipaddr'
5
+ require 'active_record/connection_adapters/abstract_adapter'
6
+ require 'active_record/connection_adapters/postgresql/column'
7
+ require 'active_record/connection_adapters/postgresql/explain_pretty_printer'
8
+ require 'active_record/connection_adapters/postgresql/quoting'
9
+ require 'active_record/connection_adapters/postgresql/referential_integrity'
10
+ require 'active_record/connection_adapters/postgresql/schema_dumper'
11
+ require 'active_record/connection_adapters/postgresql/schema_statements'
12
+ require 'active_record/connection_adapters/postgresql/type_metadata'
13
+ require 'active_record/connection_adapters/postgresql/utils'
14
+ require 'arjdbc/abstract/core'
15
+ require 'arjdbc/abstract/connection_management'
16
+ require 'arjdbc/abstract/database_statements'
17
+ require 'arjdbc/abstract/statement_cache'
18
+ require 'arjdbc/abstract/transaction_support'
19
+ require 'arjdbc/postgresql/base/array_decoder'
20
+ require 'arjdbc/postgresql/base/array_encoder'
21
+ require 'arjdbc/postgresql/name'
22
+
23
+ module ArJdbc
24
+ # Strives to provide Rails built-in PostgreSQL adapter (API) compatibility.
6
25
  module PostgreSQL
7
- def self.extended(mod)
8
- mod.class.class_eval do
9
- alias_chained_method :insert, :query_dirty, :insert
10
- end
11
- end
12
26
 
13
- def self.column_selector
14
- [/postgre/i, lambda {|cfg,col| col.extend(::ArJdbc::PostgreSQL::Column)}]
15
- end
27
+ require 'arjdbc/postgresql/column'
28
+ require 'arel/visitors/postgresql_jdbc'
29
+ # @private
30
+ IndexDefinition = ::ActiveRecord::ConnectionAdapters::IndexDefinition
16
31
 
32
+ # @private
33
+ ForeignKeyDefinition = ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition
34
+
35
+ # @private
36
+ Type = ::ActiveRecord::Type
37
+
38
+ # @see ActiveRecord::ConnectionAdapters::JdbcAdapter#jdbc_connection_class
17
39
  def self.jdbc_connection_class
18
- ::ActiveRecord::ConnectionAdapters::PostgresJdbcConnection
40
+ ::ActiveRecord::ConnectionAdapters::PostgreSQLJdbcConnection
19
41
  end
20
42
 
21
- module Column
22
- def type_cast(value)
23
- case type
24
- when :boolean then cast_to_boolean(value)
25
- else super
43
+ # @see ActiveRecord::ConnectionAdapters::JdbcAdapter#jdbc_column_class
44
+ def jdbc_column_class; ::ActiveRecord::ConnectionAdapters::PostgreSQLColumn end
45
+
46
+ ADAPTER_NAME = 'PostgreSQL'.freeze
47
+
48
+ def adapter_name
49
+ ADAPTER_NAME
50
+ end
51
+
52
+ # TODO: Update this to pull info from the DatabaseMetaData object?
53
+ def postgresql_version
54
+ @postgresql_version ||=
55
+ begin
56
+ version = @connection.database_product
57
+ if version =~ /PostgreSQL (\d+)\.(\d+)\.(\d+)/
58
+ ($1.to_i * 10000) + ($2.to_i * 100) + $3.to_i
59
+ else
60
+ 0
61
+ end
26
62
  end
63
+ end
64
+
65
+ def redshift?
66
+ # SELECT version() :
67
+ # PostgreSQL 8.0.2 on i686-pc-linux-gnu, compiled by GCC gcc (GCC) 3.4.2 20041017 (Red Hat 3.4.2-6.fc3), Redshift 1.0.647
68
+ if ( redshift = config[:redshift] ).nil?
69
+ redshift = !! (@connection.database_product || '').index('Redshift')
27
70
  end
71
+ redshift
72
+ end
73
+ private :redshift?
28
74
 
29
- def extract_limit(sql_type)
30
- case sql_type
31
- when /^bigint/i; 8
32
- when /^smallint/i; 2
33
- when /^bool/i; nil # ACTIVERECORD_JDBC-135
34
- else super
75
+ def use_insert_returning?
76
+ if @use_insert_returning.nil?
77
+ @use_insert_returning = supports_insert_with_returning?
78
+ end
79
+ @use_insert_returning
80
+ end
81
+
82
+ def set_client_encoding(encoding)
83
+ ActiveRecord::Base.logger.warn "client_encoding is set by the driver and should not be altered, ('#{encoding}' ignored)"
84
+ ActiveRecord::Base.logger.debug "Set the 'allowEncodingChanges' driver property (e.g. using config[:properties]) if you need to override the client encoding when doing a copy."
85
+ end
86
+
87
+ # Configures the encoding, verbosity, schema search path, and time zone of the connection.
88
+ # This is called on `connection.connect` and should not be called manually.
89
+ def configure_connection
90
+ #if encoding = config[:encoding]
91
+ # The client_encoding setting is set by the driver and should not be altered.
92
+ # If the driver detects a change it will abort the connection.
93
+ # see http://jdbc.postgresql.org/documentation/91/connect.html
94
+ # self.set_client_encoding(encoding)
95
+ #end
96
+ self.client_min_messages = config[:min_messages] || 'warning'
97
+ self.schema_search_path = config[:schema_search_path] || config[:schema_order]
98
+
99
+ # Use standard-conforming strings if available so we don't have to do the E'...' dance.
100
+ set_standard_conforming_strings
101
+
102
+ # If using Active Record's time zone support configure the connection to return
103
+ # TIMESTAMP WITH ZONE types in UTC.
104
+ # (SET TIME ZONE does not use an equals sign like other SET variables)
105
+ if ActiveRecord::Base.default_timezone == :utc
106
+ execute("SET time zone 'UTC'", 'SCHEMA')
107
+ elsif tz = local_tz
108
+ execute("SET time zone '#{tz}'", 'SCHEMA')
109
+ end unless redshift?
110
+
111
+ # SET statements from :variables config hash
112
+ # http://www.postgresql.org/docs/8.3/static/sql-set.html
113
+ (config[:variables] || {}).map do |k, v|
114
+ if v == ':default' || v == :default
115
+ # Sets the value to the global or compile default
116
+ execute("SET SESSION #{k} TO DEFAULT", 'SCHEMA')
117
+ elsif ! v.nil?
118
+ execute("SET SESSION #{k} TO #{quote(v)}", 'SCHEMA')
35
119
  end
36
120
  end
121
+ end
37
122
 
38
- def simplified_type(field_type)
39
- return :integer if field_type =~ /^serial/i
40
- return :string if field_type =~ /\[\]$/i || field_type =~ /^interval/i
41
- return :string if field_type =~ /^(?:point|lseg|box|"?path"?|polygon|circle)/i
42
- return :datetime if field_type =~ /^timestamp/i
43
- return :float if field_type =~ /^(?:real|double precision)$/i
44
- return :binary if field_type =~ /^bytea/i
45
- return :boolean if field_type =~ /^bool/i
46
- super
123
+ # @private
124
+ ActiveRecordError = ::ActiveRecord::ActiveRecordError
125
+
126
+ NATIVE_DATABASE_TYPES = {
127
+ bigserial: 'bigserial',
128
+ primary_key: 'serial primary key',
129
+ bigint: { name: 'bigint' },
130
+ binary: { name: 'bytea' },
131
+ bit: { name: 'bit' },
132
+ bit_varying: { name: 'bit varying' },
133
+ boolean: { name: 'boolean' },
134
+ box: { name: 'box' },
135
+ char: { name: 'char' },
136
+ cidr: { name: 'cidr' },
137
+ circle: { name: 'circle' },
138
+ citext: { name: 'citext' },
139
+ date: { name: 'date' },
140
+ daterange: { name: 'daterange' },
141
+ datetime: { name: 'timestamp' },
142
+ decimal: { name: 'decimal' }, # :limit => 1000
143
+ float: { name: 'float' },
144
+ hstore: { name: 'hstore' },
145
+ inet: { name: 'inet' },
146
+ int4range: { name: 'int4range' },
147
+ int8range: { name: 'int8range' },
148
+ integer: { name: 'integer' },
149
+ interval: { name: 'interval' }, # This doesn't get added to AR's postgres adapter until 5.1 but it fixes broken tests in 5.0 ...
150
+ json: { name: 'json' },
151
+ jsonb: { name: 'jsonb' },
152
+ line: { name: 'line' },
153
+ lseg: { name: 'lseg' },
154
+ ltree: { name: 'ltree' },
155
+ macaddr: { name: 'macaddr' },
156
+ money: { name: 'money' },
157
+ numeric: { name: 'numeric' },
158
+ numrange: { name: 'numrange' },
159
+ path: { name: 'path' },
160
+ point: { name: 'point' },
161
+ polygon: { name: 'polygon' },
162
+ serial: { name: 'serial' }, # auto-inc integer, bigserial, smallserial
163
+ string: { name: 'character varying' },
164
+ text: { name: 'text' },
165
+ time: { name: 'time' },
166
+ timestamp: { name: 'timestamp' },
167
+ tsrange: { name: 'tsrange' },
168
+ tstzrange: { name: 'tstzrange' },
169
+ tsvector: { name: 'tsvector' },
170
+ uuid: { name: 'uuid' },
171
+ xml: { name: 'xml' }
172
+ }
173
+
174
+ def native_database_types
175
+ NATIVE_DATABASE_TYPES
176
+ end
177
+
178
+ def valid_type?(type)
179
+ !native_database_types[type].nil?
180
+ end
181
+
182
+ # Enable standard-conforming strings if available.
183
+ def set_standard_conforming_strings
184
+ self.standard_conforming_strings=(true)
185
+ end
186
+
187
+ # Enable standard-conforming strings if available.
188
+ def standard_conforming_strings=(enable)
189
+ client_min_messages = self.client_min_messages
190
+ begin
191
+ self.client_min_messages = 'panic'
192
+ value = enable ? "on" : "off"
193
+ execute("SET standard_conforming_strings = #{value}", 'SCHEMA')
194
+ @standard_conforming_strings = ( value == "on" )
195
+ rescue
196
+ @standard_conforming_strings = :unsupported
197
+ ensure
198
+ self.client_min_messages = client_min_messages
47
199
  end
200
+ end
48
201
 
49
- def cast_to_boolean(value)
50
- return nil if value.nil?
51
- if value == true || value == false
52
- value
53
- else
54
- %w(true t 1).include?(value.to_s.downcase)
202
+ def standard_conforming_strings?
203
+ if @standard_conforming_strings.nil?
204
+ client_min_messages = self.client_min_messages
205
+ begin
206
+ self.client_min_messages = 'panic'
207
+ value = select_one('SHOW standard_conforming_strings', 'SCHEMA')['standard_conforming_strings']
208
+ @standard_conforming_strings = ( value == "on" )
209
+ rescue
210
+ @standard_conforming_strings = :unsupported
211
+ ensure
212
+ self.client_min_messages = client_min_messages
55
213
  end
56
214
  end
215
+ @standard_conforming_strings == true # return false if :unsupported
216
+ end
57
217
 
58
- # Post process default value from JDBC into a Rails-friendly format (columns{-internal})
59
- def default_value(value)
60
- # Boolean types
61
- return "t" if value =~ /true/i
62
- return "f" if value =~ /false/i
218
+ def supports_ddl_transactions?; true end
63
219
 
64
- # Char/String/Bytea type values
65
- return $1 if value =~ /^'(.*)'::(bpchar|text|character varying|bytea)$/
220
+ def supports_explain?; true end
66
221
 
67
- # Numeric values
68
- return value if value =~ /^-?[0-9]+(\.[0-9]*)?/
222
+ def supports_expression_index?; true end
69
223
 
70
- # Fixed dates / timestamp
71
- return $1 if value =~ /^'(.+)'::(date|timestamp)/
224
+ def supports_foreign_keys?; true end
72
225
 
73
- # Anything else is blank, some user type, or some function
74
- # and we can't know the value of that, so return nil.
75
- return nil
76
- end
77
- end
226
+ def supports_index_sort_order?; true end
78
227
 
79
- def modify_types(tp)
80
- tp[:primary_key] = "serial primary key"
81
- tp[:string][:limit] = 255
82
- tp[:integer][:limit] = nil
83
- tp[:boolean] = { :name => "boolean" }
84
- tp[:float] = { :name => "float" }
85
- tp[:decimal] = { :name => "decimal" }
86
- tp
87
- end
228
+ def supports_migrations?; true end
88
229
 
89
- def adapter_name #:nodoc:
90
- 'PostgreSQL'
91
- end
230
+ def supports_partial_index?; true end
92
231
 
93
- def arel2_visitors
94
- {'jdbcpostgresql' => ::Arel::Visitors::PostgreSQL}
95
- end
232
+ def supports_primary_key?; true end # Supports finding primary key on non-Active Record tables
96
233
 
97
- def postgresql_version
98
- @postgresql_version ||=
99
- begin
100
- value = select_value('SELECT version()')
101
- if value =~ /PostgreSQL (\d+)\.(\d+)\.(\d+)/
102
- ($1.to_i * 10000) + ($2.to_i * 100) + $3.to_i
103
- else
104
- 0
105
- end
106
- end
107
- end
234
+ def supports_savepoints?; true end
108
235
 
109
- # Does PostgreSQL support migrations?
110
- def supports_migrations?
111
- true
112
- end
236
+ def supports_transaction_isolation?(level = nil); true end
237
+
238
+ def supports_views?; true end
113
239
 
114
240
  # Does PostgreSQL support standard conforming strings?
115
241
  def supports_standard_conforming_strings?
116
- # Temporarily set the client message level above error to prevent unintentional
117
- # error messages in the logs when working on a PostgreSQL database server that
118
- # does not support standard conforming strings.
119
- client_min_messages_old = client_min_messages
120
- self.client_min_messages = 'panic'
242
+ standard_conforming_strings?
243
+ @standard_conforming_strings != :unsupported
244
+ end
121
245
 
122
- # postgres-pr does not raise an exception when client_min_messages is set higher
123
- # than error and "SHOW standard_conforming_strings" fails, but returns an empty
124
- # PGresult instead.
125
- has_support = select('SHOW standard_conforming_strings').to_a[0][0] rescue false
126
- self.client_min_messages = client_min_messages_old
127
- has_support
246
+ def supports_hex_escaped_bytea?
247
+ postgresql_version >= 90000
128
248
  end
129
249
 
130
250
  def supports_insert_with_returning?
131
251
  postgresql_version >= 80200
132
252
  end
133
253
 
134
- def supports_ddl_transactions?
135
- true
254
+ # Range data-types weren't introduced until PostgreSQL 9.2.
255
+ def supports_ranges?
256
+ postgresql_version >= 90200
136
257
  end
137
258
 
138
- def supports_savepoints?
139
- true
259
+ def supports_extensions?
260
+ postgresql_version >= 90200
140
261
  end
141
262
 
142
- def supports_count_distinct? #:nodoc:
143
- false
263
+ def enable_extension(name)
264
+ execute("CREATE EXTENSION IF NOT EXISTS \"#{name}\"")
144
265
  end
145
266
 
146
- def create_savepoint
147
- execute("SAVEPOINT #{current_savepoint_name}")
267
+ def disable_extension(name)
268
+ execute("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE")
148
269
  end
149
270
 
150
- def rollback_to_savepoint
151
- execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
271
+ def extension_enabled?(name)
272
+ if supports_extensions?
273
+ rows = select_rows("SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL)", 'SCHEMA')
274
+ available = rows.first.first # true/false or 't'/'f'
275
+ available == true || available == 't'
276
+ end
152
277
  end
153
278
 
154
- def release_savepoint
155
- execute("RELEASE SAVEPOINT #{current_savepoint_name}")
279
+ def extensions
280
+ if supports_extensions?
281
+ rows = select_rows "SELECT extname from pg_extension", "SCHEMA"
282
+ rows.map { |row| row.first }
283
+ else
284
+ []
285
+ end
156
286
  end
157
287
 
158
- # Returns the configured supported identifier length supported by PostgreSQL,
159
- # or report the default of 63 on PostgreSQL 7.x.
160
- def table_alias_length
161
- @table_alias_length ||= (postgresql_version >= 80000 ? select_one('SHOW max_identifier_length')['max_identifier_length'].to_i : 63)
288
+ def index_algorithms
289
+ { :concurrently => 'CONCURRENTLY' }
162
290
  end
163
291
 
164
- def default_sequence_name(table_name, pk = nil)
165
- default_pk, default_seq = pk_and_sequence_for(table_name)
166
- default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq"
292
+ # Set the authorized user for this session.
293
+ def session_auth=(user)
294
+ clear_cache!
295
+ execute "SET SESSION AUTHORIZATION #{user}"
167
296
  end
168
297
 
169
- # Resets sequence to the max value of the table's pk if present.
170
- def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
171
- unless pk and sequence
172
- default_pk, default_sequence = pk_and_sequence_for(table)
173
- pk ||= default_pk
174
- sequence ||= default_sequence
175
- end
176
- if pk
177
- if sequence
178
- quoted_sequence = quote_column_name(sequence)
179
-
180
- select_value <<-end_sql, 'Reset sequence'
181
- 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)
182
- end_sql
183
- else
184
- @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
185
- end
298
+ # Came from postgres_adapter
299
+ def get_advisory_lock(lock_id) # :nodoc:
300
+ unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
301
+ raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer")
186
302
  end
303
+ select_value("SELECT pg_try_advisory_lock(#{lock_id});")
187
304
  end
188
305
 
189
- # Find a table's primary key and sequence.
190
- def pk_and_sequence_for(table) #:nodoc:
191
- # First try looking for a sequence with a dependency on the
192
- # given table's primary key.
193
- result = select(<<-end_sql, 'PK and serial sequence')[0]
194
- SELECT attr.attname, seq.relname
195
- FROM pg_class seq,
196
- pg_attribute attr,
197
- pg_depend dep,
198
- pg_namespace name,
199
- pg_constraint cons
200
- WHERE seq.oid = dep.objid
201
- AND seq.relkind = 'S'
202
- AND attr.attrelid = dep.refobjid
203
- AND attr.attnum = dep.refobjsubid
204
- AND attr.attrelid = cons.conrelid
205
- AND attr.attnum = cons.conkey[1]
206
- AND cons.contype = 'p'
207
- AND dep.refobjid = '#{quote_table_name(table)}'::regclass
208
- end_sql
209
-
210
- if result.nil? or result.empty?
211
- # If that fails, try parsing the primary key's default value.
212
- # Support the 7.x and 8.0 nextval('foo'::text) as well as
213
- # the 8.1+ nextval('foo'::regclass).
214
- result = select(<<-end_sql, 'PK and custom sequence')[0]
215
- SELECT attr.attname,
216
- CASE
217
- WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
218
- substr(split_part(def.adsrc, '''', 2),
219
- strpos(split_part(def.adsrc, '''', 2), '.')+1)
220
- ELSE split_part(def.adsrc, '''', 2)
221
- END as relname
222
- FROM pg_class t
223
- JOIN pg_attribute attr ON (t.oid = attrelid)
224
- JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
225
- JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
226
- WHERE t.oid = '#{quote_table_name(table)}'::regclass
227
- AND cons.contype = 'p'
228
- AND def.adsrc ~* 'nextval'
229
- end_sql
306
+ # Came from postgres_adapter
307
+ def release_advisory_lock(lock_id) # :nodoc:
308
+ unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
309
+ raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer")
230
310
  end
231
-
232
- [result["attname"], result["relname"]]
233
- rescue
234
- nil
311
+ select_value("SELECT pg_advisory_unlock(#{lock_id})")
235
312
  end
236
313
 
237
- def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
238
- # Extract the table from the insert sql. Yuck.
239
- table = sql.split(" ", 4)[2].gsub('"', '')
240
-
241
- # Try an insert with 'returning id' if available (PG >= 8.2)
242
- if supports_insert_with_returning? && id_value.nil?
243
- pk, sequence_name = *pk_and_sequence_for(table) unless pk
244
- if pk
245
- id_value = select_value("#{sql} RETURNING #{quote_column_name(pk)}")
246
- clear_query_cache #FIXME: Why now?
247
- return id_value
248
- end
249
- end
250
-
251
- # Otherwise, plain insert
252
- execute(sql, name)
253
-
254
- # Don't need to look up id_value if we already have it.
255
- # (and can't in case of non-sequence PK)
256
- unless id_value
257
- # If neither pk nor sequence name is given, look them up.
258
- unless pk || sequence_name
259
- pk, sequence_name = *pk_and_sequence_for(table)
260
- end
314
+ # Returns the configured supported identifier length supported by PostgreSQL
315
+ def max_identifier_length
316
+ @max_identifier_length ||= select_one('SHOW max_identifier_length', 'SCHEMA'.freeze)['max_identifier_length'].to_i
317
+ end
318
+ alias table_alias_length max_identifier_length
319
+ alias index_name_length max_identifier_length
261
320
 
262
- # If a pk is given, fallback to default sequence name.
263
- # Don't fetch last insert id for a table without a pk.
264
- if pk && sequence_name ||= default_sequence_name(table, pk)
265
- id_value = last_insert_id(table, sequence_name)
321
+ def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
322
+ val = super
323
+ if !use_insert_returning? && pk
324
+ unless sequence_name
325
+ table_ref = extract_table_ref_from_insert_sql(sql)
326
+ sequence_name = default_sequence_name(table_ref, pk)
327
+ return val unless sequence_name
266
328
  end
329
+ last_insert_id_result(sequence_name)
330
+ else
331
+ val
267
332
  end
268
- id_value
269
333
  end
270
334
 
271
- def columns(table_name, name=nil)
272
- schema_name = @config[:schema_search_path]
273
- if table_name =~ /\./
274
- parts = table_name.split(/\./)
275
- table_name = parts.pop
276
- schema_name = parts.join(".")
277
- end
278
- @connection.columns_internal(table_name, name, schema_name)
335
+ def explain(arel, binds = [])
336
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
337
+ ActiveRecord::ConnectionAdapters::PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
279
338
  end
280
339
 
281
- # From postgresql_adapter.rb
282
- def indexes(table_name, name = nil)
283
- result = select_rows(<<-SQL, name)
284
- SELECT i.relname, d.indisunique, a.attname
285
- FROM pg_class t, pg_class i, pg_index d, pg_attribute a
286
- WHERE i.relkind = 'i'
287
- AND d.indexrelid = i.oid
288
- AND d.indisprimary = 'f'
289
- AND t.oid = d.indrelid
290
- AND t.relname = '#{table_name}'
291
- AND a.attrelid = t.oid
292
- AND ( d.indkey[0]=a.attnum OR d.indkey[1]=a.attnum
293
- OR d.indkey[2]=a.attnum OR d.indkey[3]=a.attnum
294
- OR d.indkey[4]=a.attnum OR d.indkey[5]=a.attnum
295
- OR d.indkey[6]=a.attnum OR d.indkey[7]=a.attnum
296
- OR d.indkey[8]=a.attnum OR d.indkey[9]=a.attnum )
297
- ORDER BY i.relname
298
- SQL
299
-
300
- current_index = nil
301
- indexes = []
302
-
303
- result.each do |row|
304
- if current_index != row[0]
305
- indexes << ::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, row[0], row[1] == "t", [])
306
- current_index = row[0]
340
+ # @note Only for "better" AR 4.0 compatibility.
341
+ # @private
342
+ def query(sql, name = nil)
343
+ log(sql, name) do
344
+ result = []
345
+ @connection.execute_query_raw(sql, []) do |*values|
346
+ # We need to use #deep_dup here because it appears that
347
+ # the java method is reusing an object in some cases
348
+ # which makes all of the entries in the "result"
349
+ # array end up with the same values as the last row
350
+ result << values.deep_dup
307
351
  end
308
-
309
- indexes.last.columns << row[2]
352
+ result
310
353
  end
311
-
312
- indexes
313
- end
314
-
315
- def last_insert_id(table, sequence_name)
316
- Integer(select_value("SELECT currval('#{sequence_name}')"))
317
- end
318
-
319
- def recreate_database(name)
320
- drop_database(name)
321
- create_database(name)
322
- end
323
-
324
- def create_database(name, options = {})
325
- execute "CREATE DATABASE \"#{name}\" ENCODING='#{options[:encoding] || 'utf8'}'"
326
354
  end
327
355
 
328
- def drop_database(name)
329
- execute "DROP DATABASE IF EXISTS \"#{name}\""
356
+ # We need to make sure to deallocate all the prepared statements
357
+ # since apparently calling close on the statement object
358
+ # doesn't always free the server resources and calling
359
+ # 'DISCARD ALL' fails if we are inside a transaction
360
+ def clear_cache!
361
+ super
362
+ @connection.execute 'DEALLOCATE ALL' if supports_statement_cache? && @connection.active?
330
363
  end
331
364
 
332
- def create_schema(schema_name, pg_username)
333
- execute("CREATE SCHEMA \"#{schema_name}\" AUTHORIZATION \"#{pg_username}\"")
365
+ def reset!
366
+ clear_cache!
367
+ reset_transaction
368
+ @connection.rollback # Have to deal with rollbacks differently than the AR adapter
369
+ @connection.execute 'DISCARD ALL'
370
+ @connection.configure_connection
334
371
  end
335
372
 
336
- def drop_schema(schema_name)
337
- execute("DROP SCHEMA \"#{schema_name}\"")
373
+ def last_insert_id_result(sequence_name)
374
+ exec_query("SELECT currval('#{sequence_name}')", 'SQL')
338
375
  end
339
376
 
340
377
  def all_schemas
341
- select('select nspname from pg_namespace').map {|r| r["nspname"] }
378
+ select('SELECT nspname FROM pg_namespace').map { |row| row["nspname"] }
342
379
  end
343
380
 
344
- def primary_key(table)
345
- pk_and_sequence = pk_and_sequence_for(table)
346
- pk_and_sequence && pk_and_sequence.first
381
+ # Returns the current client message level.
382
+ def client_min_messages
383
+ return nil if redshift? # not supported on Redshift
384
+ # Need to use #execute so we don't try to access the type map before it is initialized
385
+ execute('SHOW client_min_messages', 'SCHEMA').values.first.first
347
386
  end
348
387
 
349
- def structure_dump
350
- database = @config[:database]
351
- if database.nil?
352
- if @config[:url] =~ /\/([^\/]*)$/
353
- database = $1
354
- else
355
- raise "Could not figure out what database this url is for #{@config["url"]}"
356
- end
357
- end
358
-
359
- ENV['PGHOST'] = @config[:host] if @config[:host]
360
- ENV['PGPORT'] = @config[:port].to_s if @config[:port]
361
- ENV['PGPASSWORD'] = @config[:password].to_s if @config[:password]
362
- search_path = @config[:schema_search_path]
363
- search_path = "--schema=#{search_path}" if search_path
364
-
365
- @connection.connection.close
366
- begin
367
- definition = `pg_dump -i -U "#{@config[:username]}" -s -x -O #{search_path} #{database}`
368
- raise "Error dumping database" if $?.exitstatus == 1
369
-
370
- # need to patch away any references to SQL_ASCII as it breaks the JDBC driver
371
- definition.gsub(/SQL_ASCII/, 'UNICODE')
372
- ensure
373
- reconnect!
374
- end
375
- end
376
-
377
- # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
378
- #
379
- # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
380
- # requires that the ORDER BY include the distinct column.
381
- #
382
- # distinct("posts.id", "posts.created_at desc")
383
- def distinct(columns, order_by)
384
- return "DISTINCT #{columns}" if order_by.blank?
385
-
386
- # construct a clean list of column names from the ORDER BY clause, removing
387
- # any asc/desc modifiers
388
- order_columns = order_by.split(',').collect { |s| s.split.first }
389
- order_columns.delete_if(&:blank?)
390
- order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
391
-
392
- # return a DISTINCT ON() clause that's distinct on the columns we want but includes
393
- # all the required columns for the ORDER BY to work properly
394
- sql = "DISTINCT ON (#{columns}) #{columns}, "
395
- sql << order_columns * ', '
388
+ # Set the client message level.
389
+ def client_min_messages=(level)
390
+ # NOTE: for now simply ignore the writer (no warn on Redshift) so that
391
+ # the AR copy-pasted PpstgreSQL parts stay the same as much as possible
392
+ return nil if redshift? # not supported on Redshift
393
+ execute("SET client_min_messages TO '#{level}'", 'SCHEMA')
396
394
  end
397
395
 
398
396
  # ORDER BY clause for the passed order option.
399
397
  #
400
- # PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this
401
- # by wrapping the sql as a sub-select and ordering in that query.
398
+ # PostgreSQL does not allow arbitrary ordering when using DISTINCT ON,
399
+ # so we work around this by wrapping the SQL as a sub-select and ordering
400
+ # in that query.
402
401
  def add_order_by_for_association_limiting!(sql, options)
403
402
  return sql if options[:order].blank?
404
403
 
@@ -409,36 +408,20 @@ module ::ArJdbc
409
408
  sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}"
410
409
  end
411
410
 
412
- def quote(value, column = nil) #:nodoc:
413
- return super unless column
414
-
415
- if value.kind_of?(String) && column.type == :binary
416
- "'#{escape_bytea(value)}'"
417
- elsif value.kind_of?(String) && column.sql_type == 'xml'
418
- "xml '#{quote_string(value)}'"
419
- elsif value.kind_of?(Numeric) && column.sql_type == 'money'
420
- # Not truly string input, so doesn't require (or allow) escape string syntax.
421
- "'#{value}'"
422
- elsif value.kind_of?(String) && column.sql_type =~ /^bit/
423
- case value
424
- when /^[01]*$/
425
- "B'#{value}'" # Bit-string notation
426
- when /^[0-9A-F]*$/i
427
- "X'#{value}'" # Hexadecimal notation
428
- end
429
- else
430
- super
431
- end
432
- end
411
+ # @note #quote_string implemented as native
433
412
 
434
- def escape_bytea(s)
435
- if s
413
+ def escape_bytea(string)
414
+ return unless string
415
+ if supports_hex_escaped_bytea?
416
+ "\\x#{string.unpack("H*")[0]}"
417
+ else
436
418
  result = ''
437
- s.each_byte { |c| result << sprintf('\\\\%03o', c) }
419
+ string.each_byte { |c| result << sprintf('\\\\%03o', c) }
438
420
  result
439
421
  end
440
422
  end
441
423
 
424
+ # @override
442
425
  def quote_table_name(name)
443
426
  schema, name_part = extract_pg_identifier_from_name(name.to_s)
444
427
 
@@ -450,98 +433,285 @@ module ::ArJdbc
450
433
  end
451
434
  end
452
435
 
453
- def quote_column_name(name)
454
- %("#{name}")
436
+ # @note #quote_column_name implemented as native
437
+ alias_method :quote_schema_name, :quote_column_name
438
+
439
+ # Need to clear the cache even though the AR adapter doesn't for some reason
440
+ def remove_column(table_name, column_name, type = nil, options = {})
441
+ super
442
+ clear_cache!
455
443
  end
456
444
 
457
- def quoted_date(value) #:nodoc:
458
- if value.acts_like?(:time) && value.respond_to?(:usec)
459
- "#{super}.#{sprintf("%06d", value.usec)}"
460
- else
461
- super
445
+ # @private
446
+ def column_for(table_name, column_name)
447
+ column_name = column_name.to_s
448
+ for column in columns(table_name)
449
+ return column if column.name == column_name
462
450
  end
451
+ nil
463
452
  end
464
453
 
465
- def disable_referential_integrity(&block) #:nodoc:
466
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
467
- yield
468
- ensure
469
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
454
+ # Returns the list of a table's column names, data types, and default values.
455
+ #
456
+ # If the table name is not prefixed with a schema, the database will
457
+ # take the first match from the schema search path.
458
+ #
459
+ # Query implementation notes:
460
+ # - format_type includes the column size constraint, e.g. varchar(50)
461
+ # - ::regclass is a function that gives the id for a table name
462
+ def column_definitions(table_name)
463
+ select_rows(<<-end_sql, 'SCHEMA')
464
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod),
465
+ pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
466
+ (SELECT c.collname FROM pg_collation c, pg_type t
467
+ WHERE c.oid = a.attcollation AND t.oid = a.atttypid
468
+ AND a.attcollation <> t.typcollation),
469
+ col_description(a.attrelid, a.attnum) AS comment
470
+ FROM pg_attribute a
471
+ LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
472
+ WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
473
+ AND a.attnum > 0 AND NOT a.attisdropped
474
+ ORDER BY a.attnum
475
+ end_sql
476
+ end
477
+ private :column_definitions
478
+
479
+ def truncate(table_name, name = nil)
480
+ execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
481
+ end
482
+
483
+ # Returns an array of indexes for the given table.
484
+ def indexes(table_name, name = nil)
485
+ # FIXME: AR version => table = Utils.extract_schema_qualified_name(table_name.to_s)
486
+ schema, table = extract_schema_and_table(table_name.to_s)
487
+
488
+ result = query(<<-SQL, 'SCHEMA')
489
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
490
+ pg_catalog.obj_description(i.oid, 'pg_class') AS comment,
491
+ (SELECT COUNT(*) FROM pg_opclass o
492
+ JOIN (SELECT unnest(string_to_array(d.indclass::text, ' '))::int oid) c
493
+ ON o.oid = c.oid WHERE o.opcdefault = 'f')
494
+ FROM pg_class t
495
+ INNER JOIN pg_index d ON t.oid = d.indrelid
496
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
497
+ LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
498
+ WHERE i.relkind = 'i'
499
+ AND d.indisprimary = 'f'
500
+ AND t.relname = '#{table}'
501
+ AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
502
+ ORDER BY i.relname
503
+ SQL
504
+
505
+ result.map do |row|
506
+ index_name = row[0]
507
+ # FIXME: These values [1,2] are returned in a different format than AR expects, maybe we could update it on the Java side to be more accurate
508
+ unique = row[1].is_a?(String) ? row[1] == 't' : row[1] # JDBC gets us a boolean
509
+ indkey = row[2].is_a?(Java::OrgPostgresqlUtil::PGobject) ? row[2].value : row[2]
510
+ indkey = indkey.split(" ").map(&:to_i)
511
+ inddef = row[3]
512
+ oid = row[4]
513
+ comment = row[5]
514
+ opclass = row[6]
515
+
516
+ using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/).flatten
517
+
518
+ if indkey.include?(0) || opclass > 0
519
+ columns = expressions
520
+ else
521
+ columns = Hash[query(<<-SQL.strip_heredoc, "SCHEMA")].values_at(*indkey).compact
522
+ SELECT a.attnum, a.attname
523
+ FROM pg_attribute a
524
+ WHERE a.attrelid = #{oid}
525
+ AND a.attnum IN (#{indkey.join(",")})
526
+ SQL
527
+
528
+ # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
529
+ orders = Hash[
530
+ expressions.scan(/(\w+) DESC/).flatten.map { |order_column| [order_column, :desc] }
531
+ ]
532
+ end
533
+
534
+ IndexDefinition.new(table_name, index_name, unique, columns, [], orders, where, nil, using.to_sym, comment.presence)
535
+ end.compact
470
536
  end
471
537
 
472
- def rename_table(name, new_name)
473
- execute "ALTER TABLE #{name} RENAME TO #{new_name}"
538
+ # @private
539
+ def column_name_for_operation(operation, node)
540
+ case operation
541
+ when 'maximum' then 'max'
542
+ when 'minimum' then 'min'
543
+ when 'average' then 'avg'
544
+ else operation.downcase
545
+ end
474
546
  end
475
547
 
476
- def add_column(table_name, column_name, type, options = {})
477
- execute("ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}")
478
- change_column_default(table_name, column_name, options[:default]) unless options[:default].nil?
479
- if options[:null] == false
480
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)} = '#{options[:default]}'") if options[:default]
481
- execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} SET NOT NULL")
548
+ private
549
+
550
+ # Pulled from ActiveRecord's Postgres adapter and modified to use execute
551
+ def can_perform_case_insensitive_comparison_for?(column)
552
+ @case_insensitive_cache ||= {}
553
+ @case_insensitive_cache[column.sql_type] ||= begin
554
+ sql = <<-end_sql
555
+ SELECT exists(
556
+ SELECT * FROM pg_proc
557
+ WHERE proname = 'lower'
558
+ AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
559
+ ) OR exists(
560
+ SELECT * FROM pg_proc
561
+ INNER JOIN pg_cast
562
+ ON ARRAY[casttarget]::oidvector = proargtypes
563
+ WHERE proname = 'lower'
564
+ AND castsource = #{quote column.sql_type}::regtype
565
+ )
566
+ end_sql
567
+ select_value(sql, 'SCHEMA')
482
568
  end
483
569
  end
484
570
 
485
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
486
- begin
487
- execute "ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
488
- rescue ActiveRecord::StatementInvalid
489
- # This is PG7, so we use a more arcane way of doing it.
490
- begin_db_transaction
491
- add_column(table_name, "#{column_name}_ar_tmp", type, options)
492
- execute "UPDATE #{table_name} SET #{column_name}_ar_tmp = CAST(#{column_name} AS #{type_to_sql(type, options[:limit], options[:precision], options[:scale])})"
493
- remove_column(table_name, column_name)
494
- rename_column(table_name, "#{column_name}_ar_tmp", column_name)
495
- commit_db_transaction
571
+ def translate_exception(exception, message)
572
+ case exception.message
573
+ when /duplicate key value violates unique constraint/
574
+ ::ActiveRecord::RecordNotUnique.new(message)
575
+ when /violates foreign key constraint/
576
+ ::ActiveRecord::InvalidForeignKey.new(message)
577
+ when /value too long/
578
+ ::ActiveRecord::ValueTooLong.new(message)
579
+ else
580
+ super
496
581
  end
497
- change_column_default(table_name, column_name, options[:default]) unless options[:default].nil?
498
582
  end
499
583
 
500
- def change_column_default(table_name, column_name, default) #:nodoc:
501
- execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT '#{default}'"
584
+ # @private `Utils.extract_schema_and_table` from AR
585
+ def extract_schema_and_table(name)
586
+ result = name.scan(/[^".\s]+|"[^"]*"/)[0, 2]
587
+ result.each { |m| m.gsub!(/(^"|"$)/, '') }
588
+ result.unshift(nil) if result.size == 1 # schema == nil
589
+ result # [schema, table]
502
590
  end
503
591
 
504
- def change_column_null(table_name, column_name, null, default = nil)
505
- unless null || default.nil?
506
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
592
+ def extract_pg_identifier_from_name(name)
593
+ match_data = name[0, 1] == '"' ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
594
+
595
+ if match_data
596
+ rest = name[match_data[0].length..-1]
597
+ rest = rest[1..-1] if rest[0, 1] == "."
598
+ [match_data[1], (rest.length > 0 ? rest : nil)]
507
599
  end
508
- execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
509
600
  end
510
601
 
511
- def rename_column(table_name, column_name, new_column_name) #:nodoc:
512
- execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
602
+ def extract_table_ref_from_insert_sql(sql)
603
+ sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
604
+ $1.strip if $1
513
605
  end
514
606
 
515
- def remove_index(table_name, options) #:nodoc:
516
- execute "DROP INDEX #{index_name(table_name, options)}"
607
+ def local_tz
608
+ @local_tz ||= execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
517
609
  end
518
610
 
519
- def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
520
- return super unless type.to_s == 'integer'
611
+ end
612
+ end
613
+
614
+ require 'arjdbc/util/quoted_cache'
615
+
616
+ module ActiveRecord::ConnectionAdapters
617
+
618
+ # NOTE: seems needed on 4.x due loading of '.../postgresql/oid' which
619
+ # assumes: class PostgreSQLAdapter < AbstractAdapter
620
+ remove_const(:PostgreSQLAdapter) if const_defined?(:PostgreSQLAdapter)
621
+
622
+ class PostgreSQLAdapter < AbstractAdapter
623
+
624
+ # Try to use as much of the built in postgres logic as possible
625
+ # maybe someday we can extend the actual adapter
626
+ include ActiveRecord::ConnectionAdapters::PostgreSQL::ColumnDumper
627
+ include ActiveRecord::ConnectionAdapters::PostgreSQL::ReferentialIntegrity
628
+ include ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaStatements
629
+ include ActiveRecord::ConnectionAdapters::PostgreSQL::Quoting
630
+
631
+ include Jdbc::ConnectionPoolCallbacks
521
632
 
522
- if limit.nil? || limit == 4
523
- 'integer'
524
- elsif limit < 4
525
- 'smallint'
633
+ include ArJdbc::Abstract::Core
634
+ include ArJdbc::Abstract::ConnectionManagement
635
+ include ArJdbc::Abstract::DatabaseStatements
636
+ include ArJdbc::Abstract::StatementCache
637
+ include ArJdbc::Abstract::TransactionSupport
638
+ include ArJdbc::PostgreSQL
639
+
640
+ require 'arjdbc/postgresql/oid_types'
641
+ include ::ArJdbc::PostgreSQL::OIDTypes
642
+
643
+ load 'arjdbc/postgresql/_bc_time_cast_patch.rb'
644
+
645
+ include ::ArJdbc::PostgreSQL::ColumnHelpers
646
+
647
+ include ::ArJdbc::Util::QuotedCache
648
+
649
+ # AR expects OID to be available on the adapter
650
+ OID = ActiveRecord::ConnectionAdapters::PostgreSQL::OID
651
+
652
+ def initialize(connection, logger = nil, config = {})
653
+ # @local_tz is initialized as nil to avoid warnings when connect tries to use it
654
+ @local_tz = nil
655
+
656
+ super # configure_connection happens in super
657
+
658
+ initialize_type_map(@type_map = Type::HashLookupTypeMap.new)
659
+
660
+ @use_insert_returning = @config.key?(:insert_returning) ?
661
+ self.class.type_cast_config_to_boolean(@config[:insert_returning]) : nil
662
+ end
663
+
664
+ def arel_visitor # :nodoc:
665
+ Arel::Visitors::PostgreSQL.new(self)
666
+ end
667
+
668
+ require 'active_record/connection_adapters/postgresql/schema_definitions'
669
+
670
+ ColumnDefinition = ActiveRecord::ConnectionAdapters::PostgreSQL::ColumnDefinition
671
+ ColumnMethods = ActiveRecord::ConnectionAdapters::PostgreSQL::ColumnMethods
672
+ TableDefinition = ActiveRecord::ConnectionAdapters::PostgreSQL::TableDefinition
673
+ Table = ActiveRecord::ConnectionAdapters::PostgreSQL::Table
674
+
675
+ def create_table_definition(*args) # :nodoc:
676
+ TableDefinition.new(*args)
677
+ end
678
+
679
+ def exec_query(sql, name = nil, binds = [], prepare: false)
680
+ super
681
+ rescue ActiveRecord::StatementInvalid => e
682
+ raise unless e.cause.message.include?('cached plan must not change result type'.freeze)
683
+
684
+ if open_transactions > 0
685
+ # In a transaction, have to fail it - See AR code for details
686
+ raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
526
687
  else
527
- 'bigint'
688
+ # Not in a transaction, clear the prepared statement and try again
689
+ delete_cached_statement(sql)
690
+ retry
528
691
  end
529
692
  end
530
693
 
531
- def tables
532
- @connection.tables(database_name, nil, nil, ["TABLE"])
694
+ public :sql_for_insert
695
+
696
+ def schema_creation # :nodoc:
697
+ PostgreSQL::SchemaCreation.new self
698
+ end
699
+
700
+ def update_table_definition(table_name, base)
701
+ Table.new(table_name, base)
702
+ end
703
+
704
+ def jdbc_connection_class(spec)
705
+ ::ArJdbc::PostgreSQL.jdbc_connection_class
533
706
  end
534
707
 
535
708
  private
536
- def extract_pg_identifier_from_name(name)
537
- match_data = name[0,1] == '"' ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
538
709
 
539
- if match_data
540
- rest = name[match_data[0].length..-1]
541
- rest = rest[1..-1] if rest[0,1] == "."
542
- [match_data[1], (rest.length > 0 ? rest : nil)]
543
- end
710
+ # Prepared statements aren't schema aware so we need to make sure we
711
+ # store different PreparedStatement objects for different schemas
712
+ def cached_statement_key(sql)
713
+ "#{schema_search_path}-#{sql}"
544
714
  end
715
+
545
716
  end
546
717
  end
547
-