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

Sign up to get free protection for your applications and to get access to all the features.
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
-