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
@@ -0,0 +1,157 @@
1
+ /*
2
+ * The MIT License
3
+ *
4
+ * Copyright 2015 Karol Bucek
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ * THE SOFTWARE.
23
+ */
24
+ package arjdbc.postgresql;
25
+
26
+ import org.jruby.util.ByteList;
27
+
28
+ /**
29
+ * Based on JDBC PostgreSQL driver's <code>org.postgresql.util.PGbytea</code>.
30
+ *
31
+ * @author kares
32
+ */
33
+ abstract class ByteaUtils {
34
+
35
+ /*
36
+ * Converts a PG bytea raw value (i.e. the raw binary representation
37
+ * of the bytea data type) into a java byte[]
38
+ */
39
+ static ByteList toBytes(final byte[] s, final int off, final int len) {
40
+ // Starting with PG 9.0, a new hex format is supported
41
+ // that starts with "\x". Figure out which format we're
42
+ // dealing with here.
43
+ //
44
+ if ( s.length < 2 || s[0] != '\\' || s[1] != 'x' ) {
45
+ return toBytesOctalEscaped(s, off, len);
46
+ }
47
+ return new ByteList(toBytesHexEscaped(s, off, len), false);
48
+ }
49
+
50
+ private static byte[] toBytesHexEscaped(final byte[] s, final int off, final int len) {
51
+ final byte[] out = new byte[(len - 2) / 2];
52
+ for (int i = 0; i < out.length; i++) {
53
+ final int j = off + (2 + i * 2);
54
+ byte b1 = hexByte( s[j] );
55
+ byte b2 = hexByte( s[j + 1] );
56
+ // squid:S3034
57
+ // Raw byte values should not be used in bitwise operations in combination with shifts
58
+ out[i] = (byte) ((b1 << 4) | (b2 & 0xff));
59
+ }
60
+ return out;
61
+ }
62
+
63
+ private static byte hexByte(final byte b) {
64
+ // 0-9 == 48-57
65
+ if (b <= 57) return (byte) (b - 48);
66
+
67
+ // a-f == 97-102
68
+ if (b >= 97) return (byte) (b - 97 + 10);
69
+
70
+ // A-F == 65-70
71
+ return (byte) (b - 65 + 10);
72
+ }
73
+
74
+ private static final int MAX_3_BUFF_SIZE = 2 * 1024 * 1024;
75
+
76
+ private static ByteList toBytesOctalEscaped(final byte[] s, final int off, final int len) {
77
+ final byte[] out;
78
+ final int end = off + len;
79
+ int correctSize = len;
80
+ if ( len > MAX_3_BUFF_SIZE ) {
81
+ // count backslash escapes, they will be either
82
+ // backslashes or an octal escape \\ or \003
83
+ //
84
+ for ( int i = off; i < end; ++i ) {
85
+ if ( s[i] == (byte) '\\' ) {
86
+ if (s[ ++i ] == (byte) '\\') {
87
+ --correctSize;
88
+ }
89
+ else {
90
+ correctSize -= 3;
91
+ }
92
+ }
93
+ }
94
+ out = new byte[correctSize];
95
+ }
96
+ else {
97
+ out = new byte[len];
98
+ }
99
+
100
+ int pos = 0;
101
+ for ( int i = off; i < end; i++ ) {
102
+ final byte b = s[i];
103
+ if ( b == (byte) '\\' ) {
104
+ final byte b1 = s[++i];
105
+ if ( b1 == (byte) '\\' ) { // escaped \
106
+ out[ pos++ ] = (byte) '\\';
107
+ }
108
+ else {
109
+ int thebyte = (b1 - 48) * 64 + (s[++i] - 48) * 8 + (s[++i] - 48);
110
+ if ( thebyte > 127 ) thebyte -= 256;
111
+ out[ pos++ ] = (byte) thebyte;
112
+ }
113
+ }
114
+ else {
115
+ out[ pos++ ] = b;
116
+ }
117
+ }
118
+
119
+ return new ByteList(out, 0, pos, false);
120
+ }
121
+
122
+ /*
123
+ * Converts a java byte[] into a PG bytea string (i.e. the text
124
+ * representation of the bytea data type)
125
+ */
126
+ static ByteList toStr(final byte[] p_buf, final int off, final int len) {
127
+ ByteList l_strbuf = new ByteList(2 * p_buf.length);
128
+ for (int i = off; i < off + len; i++) {
129
+ int l_int = (int)p_buf[i];
130
+ if (l_int < 0) {
131
+ l_int = 256 + l_int;
132
+ }
133
+ //we escape the same non-printable characters as the backend
134
+ //we must escape all 8bit characters otherwise when convering
135
+ //from java unicode to the db character set we may end up with
136
+ //question marks if the character set is SQL_ASCII
137
+ if (l_int < 040 || l_int > 0176) {
138
+ //escape character with the form \000
139
+ l_strbuf.append((byte)'\\');
140
+ l_strbuf.append((((l_int >> 6) & 0x3) + 48));
141
+ l_strbuf.append((((l_int >> 3) & 0x7) + 48));
142
+ l_strbuf.append(((l_int & 0x07) + 48));
143
+ }
144
+ else if (p_buf[i] == (byte)'\\') {
145
+ //escape the backslash character as \\, but need four \\\\ because
146
+ //of the Java parser
147
+ l_strbuf.append((byte)'\\').append((byte)'\\');
148
+ }
149
+ else {
150
+ //other characters are left alone
151
+ l_strbuf.append(p_buf[i]);
152
+ }
153
+ }
154
+ return l_strbuf;
155
+ }
156
+
157
+ }
@@ -0,0 +1,23 @@
1
+ /*
2
+ * A class to loosen restrictions on the PgResultSetMetaData class,
3
+ * we need to be able to get the field and the method is currently set to "package".
4
+ */
5
+ package org.postgresql.jdbc;
6
+
7
+ import java.sql.SQLException;
8
+ import org.postgresql.core.BaseConnection;
9
+ import org.postgresql.core.Field;
10
+ import org.postgresql.jdbc.PgResultSetMetaData;
11
+
12
+ public class PgResultSetMetaDataWrapper {
13
+
14
+ private final PgResultSetMetaData metaData;
15
+
16
+ public PgResultSetMetaDataWrapper(PgResultSetMetaData metaData) {
17
+ this.metaData = metaData;
18
+ }
19
+
20
+ public Field getField(int i) throws SQLException {
21
+ return this.metaData.getField(i);
22
+ }
23
+ }
@@ -0,0 +1,77 @@
1
+ /*
2
+ * The MIT License
3
+ *
4
+ * Copyright 2014 Karol Bucek.
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ * THE SOFTWARE.
23
+ */
24
+ package arjdbc.postgresql;
25
+
26
+ import static arjdbc.util.QuotingUtils.quoteCharAndDecorateWith;
27
+ import static arjdbc.util.QuotingUtils.quoteCharWith;
28
+
29
+ import org.jruby.Ruby;
30
+ import org.jruby.RubyModule;
31
+ import org.jruby.RubyString;
32
+ import org.jruby.anno.JRubyMethod;
33
+ import org.jruby.runtime.ThreadContext;
34
+ import org.jruby.runtime.builtin.IRubyObject;
35
+ import org.jruby.util.ByteList;
36
+
37
+ /**
38
+ * ArJdbc::PostgreSQL
39
+ *
40
+ * @author kares
41
+ */
42
+ public class PostgreSQLModule {
43
+
44
+ public static RubyModule load(final RubyModule arJdbc) {
45
+ RubyModule postgreSQL = arJdbc.defineModuleUnder("PostgreSQL");
46
+ postgreSQL.defineAnnotatedMethods( PostgreSQLModule.class );
47
+ return postgreSQL;
48
+ }
49
+
50
+ public static RubyModule load(final Ruby runtime) {
51
+ return load( arjdbc.ArJdbcModule.get(runtime) );
52
+ }
53
+
54
+ @JRubyMethod(name = "quote_column_name", required = 1)
55
+ public static IRubyObject quote_column_name(
56
+ final ThreadContext context,
57
+ final IRubyObject self,
58
+ final IRubyObject string) { // %("#{name.to_s.gsub("\"", "\"\"")}")
59
+ return quoteCharAndDecorateWith(context, string.asString(), '"', '"', (byte) '"', (byte) '"');
60
+ }
61
+
62
+ @JRubyMethod(name = "quote_string", required = 1)
63
+ public static IRubyObject quote_string(
64
+ final ThreadContext context,
65
+ final IRubyObject self,
66
+ final IRubyObject string) {
67
+ // NOTE: since AR 5.0 standard_conforming_strings are always set - no need for extra quoting
68
+ return quoteCharWith(context, string.asString(), '\'', '\''); // string.gsub("'", "''")
69
+ }
70
+
71
+ @JRubyMethod(name = "unescape_bytea", meta = true)
72
+ public static RubyString unescape_bytea(final ThreadContext context, final IRubyObject self, final IRubyObject escaped) {
73
+ final ByteList bytes = ((RubyString) escaped).getByteList();
74
+ return RubyString.newString(context.runtime, ByteaUtils.toBytes(bytes.unsafeBytes(), bytes.getBegin(), bytes.getRealSize()));
75
+ }
76
+
77
+ }
@@ -0,0 +1,184 @@
1
+ package arjdbc.postgresql;
2
+
3
+ import arjdbc.jdbc.JdbcResult;
4
+ import arjdbc.jdbc.RubyJdbcConnection;
5
+
6
+ import java.sql.ResultSet;
7
+ import java.sql.SQLException;
8
+ import java.sql.Types;
9
+
10
+ import org.jruby.Ruby;
11
+ import org.jruby.RubyArray;
12
+ import org.jruby.RubyClass;
13
+ import org.jruby.RubyHash;
14
+ import org.jruby.RubyMethod;
15
+ import org.jruby.RubyModule;
16
+ import org.jruby.RubyString;
17
+ import org.jruby.anno.JRubyMethod;
18
+ import org.jruby.runtime.Block;
19
+ import org.jruby.runtime.Helpers;
20
+ import org.jruby.runtime.ObjectAllocator;
21
+ import org.jruby.runtime.ThreadContext;
22
+ import org.jruby.runtime.builtin.IRubyObject;
23
+
24
+ import org.postgresql.core.Field;
25
+ import org.postgresql.jdbc.PgResultSetMetaData;
26
+ import org.postgresql.jdbc.PgResultSetMetaDataWrapper; // This is a hack unfortunately to get around method scoping
27
+
28
+ /*
29
+ * This class mimics the PG:Result class enough to get by
30
+ */
31
+ public class PostgreSQLResult extends JdbcResult {
32
+
33
+ // These are needed when generating an AR::Result
34
+ private final PgResultSetMetaData resultSetMetaData;
35
+
36
+ /********* JRuby compat methods ***********/
37
+
38
+ static RubyClass createPostgreSQLResultClass(Ruby runtime, RubyClass postgreSQLConnection) {
39
+ RubyClass rubyClass = postgreSQLConnection.defineClassUnder("Result", runtime.getObject(), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
40
+ rubyClass.defineAnnotatedMethods(PostgreSQLResult.class);
41
+ rubyClass.includeModule(runtime.getEnumerable());
42
+ return rubyClass;
43
+ }
44
+
45
+ /**
46
+ * Generates a new PostgreSQLResult object for the given result set
47
+ * @param context current thread context
48
+ * @param clazz metaclass for this result object
49
+ * @param resultSet the set of results that should be returned
50
+ * @return an instantiated result object
51
+ * @throws SQLException throws!
52
+ */
53
+ static PostgreSQLResult newResult(ThreadContext context, RubyClass clazz, PostgreSQLRubyJdbcConnection connection,
54
+ ResultSet resultSet) throws SQLException {
55
+ return new PostgreSQLResult(context, clazz, connection, resultSet);
56
+ }
57
+
58
+ /********* End JRuby compat methods ***********/
59
+
60
+ private PostgreSQLResult(ThreadContext context, RubyClass clazz, RubyJdbcConnection connection,
61
+ ResultSet resultSet) throws SQLException {
62
+ super(context, clazz, connection, resultSet);
63
+
64
+ resultSetMetaData = (PgResultSetMetaData) resultSet.getMetaData();
65
+ }
66
+
67
+ /**
68
+ * Generates a type map to be given to the AR::Result object
69
+ * @param context current thread context
70
+ * @return RubyHash RubyString - column name, Type::Value - type object)
71
+ * @throws SQLException if it fails to get the field
72
+ */
73
+ @Override
74
+ protected IRubyObject columnTypeMap(final ThreadContext context) throws SQLException {
75
+ Ruby runtime = context.runtime;
76
+ RubyHash types = RubyHash.newHash(runtime);
77
+ PgResultSetMetaDataWrapper mdWrapper = new PgResultSetMetaDataWrapper(resultSetMetaData);
78
+ int columnCount = columnNames.length;
79
+
80
+ IRubyObject adapter = connection.adapter(context);
81
+ for (int i = 0; i < columnCount; i++) {
82
+ final Field field = mdWrapper.getField(i + 1);
83
+ final RubyString name = columnNames[i];
84
+ final IRubyObject type = Helpers.invoke(context, adapter, "get_oid_type",
85
+ runtime.newFixnum(field.getOID()),
86
+ runtime.newFixnum(field.getMod()),
87
+ name);
88
+
89
+ if (!type.isNil()) types.fastASet(name, type);
90
+ }
91
+
92
+ return types;
93
+ }
94
+
95
+ /**
96
+ * This is to support the Enumerable module.
97
+ * This is needed when setting up the type maps so the Enumerable methods work
98
+ * @param context the thread this is being executed on
99
+ * @param block which may handle each result
100
+ * @return this object or RubyNil
101
+ */
102
+ @JRubyMethod
103
+ public IRubyObject each(ThreadContext context, Block block) {
104
+ // At this point we don't support calling this without a block
105
+ if (block.isGiven()) {
106
+ if (tuples == null) {
107
+ populateTuples(context);
108
+ }
109
+
110
+ for (RubyHash tuple : tuples) {
111
+ block.yield(context, tuple);
112
+ }
113
+
114
+ return this;
115
+ } else {
116
+ return context.nil;
117
+ }
118
+ }
119
+
120
+ private RubyClass getBinaryDataClass(final ThreadContext context) {
121
+ return ((RubyModule) context.runtime.getModule("ActiveModel").getConstantAt("Type")).getClass("Binary").getClass("Data");
122
+ }
123
+
124
+ private boolean isBinaryType(final int type) {
125
+ return type == Types.BLOB || type == Types.BINARY || type == Types.VARBINARY || type == Types.LONGVARBINARY;
126
+ }
127
+
128
+ /**
129
+ * Gives the number of rows to be returned.
130
+ * currently defined so we match existing returned results
131
+ * @param context current thread contect
132
+ * @return <code>Fixnum</code>
133
+ */
134
+ @JRubyMethod
135
+ public IRubyObject length(final ThreadContext context) {
136
+ return values.length();
137
+ }
138
+
139
+ /**
140
+ * Creates an <code>ActiveRecord::Result</code> with the data from this result.
141
+ * Overriding the base method so we can modify binary data columns first to mark them
142
+ * as already unencoded
143
+ * @param context current thread context
144
+ * @return ActiveRecord::Result object with the data from this result set
145
+ * @throws SQLException can be caused by postgres generating its type map
146
+ */
147
+ @Override
148
+ public IRubyObject toARResult(final ThreadContext context) throws SQLException {
149
+ RubyClass BinaryDataClass = null;
150
+ int rowCount = 0;
151
+
152
+ // This is destructive, but since this is typically the final
153
+ // use of the rows I'm going to leave it this way unless it becomes an issue
154
+ for (int columnIndex = 0; columnIndex < columnTypes.length; columnIndex++) {
155
+ if (isBinaryType(columnTypes[columnIndex])) {
156
+ // Convert the values in this column to ActiveModel::Type::Binary::Data instances
157
+ // so AR knows it has already been unescaped
158
+ if (BinaryDataClass == null) {
159
+ BinaryDataClass = getBinaryDataClass(context);
160
+ rowCount = values.getLength();
161
+ }
162
+ for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
163
+ RubyArray row = (RubyArray) values.eltInternal(rowIndex);
164
+ IRubyObject value = row.eltInternal(columnIndex);
165
+ if (value != context.nil) {
166
+ row.eltInternalSet(columnIndex, (IRubyObject) BinaryDataClass.newInstance(context, value, Block.NULL_BLOCK));
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ return super.toARResult(context);
173
+ }
174
+
175
+ /**
176
+ * Returns an array of arrays of the values in the result.
177
+ * This is defined in PG::Result and is used by some Rails tests
178
+ * @return IRubyObject RubyArray of RubyArray of values
179
+ */
180
+ @JRubyMethod
181
+ public IRubyObject values() {
182
+ return values;
183
+ }
184
+ }
@@ -0,0 +1,952 @@
1
+ /***** BEGIN LICENSE BLOCK *****
2
+ * Copyright (c) 2012-2015 Karol Bucek <self@kares.org>
3
+ * Copyright (c) 2006-2010 Nick Sieger <nick@nicksieger.com>
4
+ * Copyright (c) 2006-2007 Ola Bini <ola.bini@gmail.com>
5
+ * Copyright (c) 2008-2009 Thomas E Enebo <enebo@acm.org>
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining
8
+ * a copy of this software and associated documentation files (the
9
+ * "Software"), to deal in the Software without restriction, including
10
+ * without limitation the rights to use, copy, modify, merge, publish,
11
+ * distribute, sublicense, and/or sell copies of the Software, and to
12
+ * permit persons to whom the Software is furnished to do so, subject to
13
+ * the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be
16
+ * included in all copies or substantial portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+ ***** END LICENSE BLOCK *****/
26
+ package arjdbc.postgresql;
27
+
28
+ import arjdbc.jdbc.Callable;
29
+ import arjdbc.jdbc.DriverWrapper;
30
+ import arjdbc.util.DateTimeUtils;
31
+ import arjdbc.util.StringHelper;
32
+
33
+ import java.io.ByteArrayInputStream;
34
+ import java.lang.StringBuilder;
35
+ import java.lang.reflect.InvocationTargetException;
36
+ import java.sql.Connection;
37
+ import java.sql.DatabaseMetaData;
38
+ import java.sql.Date;
39
+ import java.sql.PreparedStatement;
40
+ import java.sql.ResultSet;
41
+ import java.sql.SQLException;
42
+ import java.sql.Timestamp;
43
+ import java.sql.Types;
44
+ import java.util.ArrayList;
45
+ import java.util.Collections;
46
+ import java.util.HashMap;
47
+ import java.util.Map;
48
+ import java.util.UUID;
49
+ import java.util.regex.Pattern;
50
+ import java.util.regex.Matcher;
51
+
52
+ import org.joda.time.DateTime;
53
+ import org.joda.time.DateTimeZone;
54
+ import org.jruby.*;
55
+ import org.jruby.anno.JRubyMethod;
56
+ import org.jruby.exceptions.RaiseException;
57
+ import org.jruby.javasupport.JavaUtil;
58
+ import org.jruby.runtime.ObjectAllocator;
59
+ import org.jruby.runtime.ThreadContext;
60
+ import org.jruby.runtime.builtin.IRubyObject;
61
+ import org.jruby.util.ByteList;
62
+
63
+ import org.postgresql.PGConnection;
64
+ import org.postgresql.PGStatement;
65
+ import org.postgresql.geometric.PGbox;
66
+ import org.postgresql.geometric.PGcircle;
67
+ import org.postgresql.geometric.PGline;
68
+ import org.postgresql.geometric.PGlseg;
69
+ import org.postgresql.geometric.PGpath;
70
+ import org.postgresql.geometric.PGpoint;
71
+ import org.postgresql.geometric.PGpolygon;
72
+ import org.postgresql.util.PGInterval;
73
+ import org.postgresql.util.PGobject;
74
+
75
+ /**
76
+ *
77
+ * @author enebo
78
+ */
79
+ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection {
80
+ private static final long serialVersionUID = 7235537759545717760L;
81
+ private static final int HSTORE_TYPE = 100000 + 1111;
82
+ private static final Pattern doubleValuePattern = Pattern.compile("(-?\\d+(?:\\.\\d+)?)");
83
+ private static final Pattern uuidPattern = Pattern.compile("\\{?\\p{XDigit}{4}(?:-?(\\p{XDigit}{4})){7}\\}?"); // Fuzzy match postgres's allowed formats
84
+
85
+ private static final Map<String, Integer> POSTGRES_JDBC_TYPE_FOR = new HashMap<String, Integer>(32, 1);
86
+ static {
87
+ POSTGRES_JDBC_TYPE_FOR.put("bit", Types.OTHER);
88
+ POSTGRES_JDBC_TYPE_FOR.put("bit_varying", Types.OTHER);
89
+ POSTGRES_JDBC_TYPE_FOR.put("box", Types.OTHER);
90
+ POSTGRES_JDBC_TYPE_FOR.put("circle", Types.OTHER);
91
+ POSTGRES_JDBC_TYPE_FOR.put("citext", Types.OTHER);
92
+ POSTGRES_JDBC_TYPE_FOR.put("daterange", Types.OTHER);
93
+ POSTGRES_JDBC_TYPE_FOR.put("hstore", Types.OTHER);
94
+ POSTGRES_JDBC_TYPE_FOR.put("int4range", Types.OTHER);
95
+ POSTGRES_JDBC_TYPE_FOR.put("int8range", Types.OTHER);
96
+ POSTGRES_JDBC_TYPE_FOR.put("interval", Types.OTHER);
97
+ POSTGRES_JDBC_TYPE_FOR.put("json", Types.OTHER);
98
+ POSTGRES_JDBC_TYPE_FOR.put("jsonb", Types.OTHER);
99
+ POSTGRES_JDBC_TYPE_FOR.put("line", Types.OTHER);
100
+ POSTGRES_JDBC_TYPE_FOR.put("lseg", Types.OTHER);
101
+ POSTGRES_JDBC_TYPE_FOR.put("ltree", Types.OTHER);
102
+ POSTGRES_JDBC_TYPE_FOR.put("numrange", Types.OTHER);
103
+ POSTGRES_JDBC_TYPE_FOR.put("path", Types.OTHER);
104
+ POSTGRES_JDBC_TYPE_FOR.put("point", Types.OTHER);
105
+ POSTGRES_JDBC_TYPE_FOR.put("polygon", Types.OTHER);
106
+ POSTGRES_JDBC_TYPE_FOR.put("tsrange", Types.OTHER);
107
+ POSTGRES_JDBC_TYPE_FOR.put("tstzrange", Types.OTHER);
108
+ POSTGRES_JDBC_TYPE_FOR.put("tsvector", Types.OTHER);
109
+ POSTGRES_JDBC_TYPE_FOR.put("uuid", Types.OTHER);
110
+ }
111
+
112
+ // Used to wipe trailing 0's from points (3.0, 5.6) -> (3, 5.6)
113
+ private static final Pattern pointCleanerPattern = Pattern.compile("\\.0\\b");
114
+
115
+ private RubyClass resultClass;
116
+
117
+ public PostgreSQLRubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
118
+ super(runtime, metaClass);
119
+
120
+ resultClass = getMetaClass().getClass("Result");
121
+ }
122
+
123
+ public static RubyClass createPostgreSQLJdbcConnectionClass(Ruby runtime, RubyClass jdbcConnection) {
124
+ final RubyClass clazz = getConnectionAdapters(runtime).
125
+ defineClassUnder("PostgreSQLJdbcConnection", jdbcConnection, ALLOCATOR);
126
+ clazz.defineAnnotatedMethods(PostgreSQLRubyJdbcConnection.class);
127
+ getConnectionAdapters(runtime).setConstant("PostgresJdbcConnection", clazz); // backwards-compat
128
+ return clazz;
129
+ }
130
+
131
+ public static RubyClass load(final Ruby runtime) {
132
+ RubyClass jdbcConnection = getJdbcConnection(runtime);
133
+ RubyClass postgreSQLConnection = createPostgreSQLJdbcConnectionClass(runtime, jdbcConnection);
134
+ PostgreSQLResult.createPostgreSQLResultClass(runtime, postgreSQLConnection);
135
+ return postgreSQLConnection;
136
+ }
137
+
138
+ protected static final ObjectAllocator ALLOCATOR = new ObjectAllocator() {
139
+ public IRubyObject allocate(Ruby runtime, RubyClass klass) {
140
+ return new PostgreSQLRubyJdbcConnection(runtime, klass);
141
+ }
142
+ };
143
+
144
+ @Override
145
+ protected String buildURL(final ThreadContext context, final IRubyObject url) {
146
+ // (deprecated AR-JDBC specific url) options: disabled with adapter: postgresql
147
+ // since it collides with AR as it likes to use the key for its own purposes :
148
+ // e.g. config[:options] = "-c geqo=off"
149
+ return DriverWrapper.buildURL(url, Collections.EMPTY_MAP);
150
+ }
151
+
152
+ @Override
153
+ protected DriverWrapper newDriverWrapper(final ThreadContext context, final String driver) {
154
+ DriverWrapper driverWrapper = super.newDriverWrapper(context, driver);
155
+
156
+ final java.sql.Driver jdbcDriver = driverWrapper.getDriverInstance();
157
+ if ( jdbcDriver.getClass().getName().startsWith("org.postgresql.") ) {
158
+ try { // public static String getVersion()
159
+ final String version = (String) // "PostgreSQL 9.2 JDBC4 (build 1002)"
160
+ jdbcDriver.getClass().getMethod("getVersion").invoke(null);
161
+ if ( version != null && version.indexOf("JDBC3") >= 0 ) {
162
+ // config[:connection_alive_sql] ||= 'SELECT 1'
163
+ setConfigValueIfNotSet(context, "connection_alive_sql", context.runtime.newString("SELECT 1"));
164
+ }
165
+ }
166
+ catch (NoSuchMethodException e) { }
167
+ catch (SecurityException e) { }
168
+ catch (IllegalAccessException e) { }
169
+ catch (InvocationTargetException e) { }
170
+ }
171
+
172
+ return driverWrapper;
173
+ }
174
+
175
+ @Override
176
+ protected final IRubyObject beginTransaction(final ThreadContext context, final Connection connection,
177
+ final IRubyObject isolation) throws SQLException {
178
+ // NOTE: only reversed order - just to ~ match how Rails does it :
179
+ /* if ( connection.getAutoCommit() ) */ connection.setAutoCommit(false);
180
+ if ( isolation != null ) setTransactionIsolation(context, connection, isolation);
181
+ return context.nil;
182
+ }
183
+
184
+ // storesMixedCaseIdentifiers() return false;
185
+ // storesLowerCaseIdentifiers() return true;
186
+ // storesUpperCaseIdentifiers() return false;
187
+
188
+ @Override
189
+ protected String caseConvertIdentifierForRails(final Connection connection, final String value)
190
+ throws SQLException {
191
+ return value;
192
+ }
193
+
194
+ @Override
195
+ protected String caseConvertIdentifierForJdbc(final Connection connection, final String value)
196
+ throws SQLException {
197
+ return value;
198
+ }
199
+
200
+ @Override
201
+ protected String internedTypeFor(final ThreadContext context, final IRubyObject attribute) throws SQLException {
202
+ RubyClass arrayClass = oidArray(context);
203
+ RubyBasicObject attributeType = (RubyBasicObject) attributeType(context, attribute);
204
+ // The type or its delegate is an OID::Array
205
+ if (arrayClass.isInstance(attributeType) ||
206
+ (attributeType.hasInstanceVariable("@delegate_dc_obj") &&
207
+ arrayClass.isInstance(attributeType.getInstanceVariable("@delegate_dc_obj")))) {
208
+ return "array";
209
+ }
210
+
211
+ return super.internedTypeFor(context, attribute);
212
+ }
213
+
214
+ @JRubyMethod(name = "database_product")
215
+ public IRubyObject database_product(final ThreadContext context) {
216
+ return withConnection(context, new Callable<IRubyObject>() {
217
+ public IRubyObject call(final Connection connection) throws SQLException {
218
+ final DatabaseMetaData metaData = connection.getMetaData();
219
+ return RubyString.newString(context.runtime, metaData.getDatabaseProductName() + ' ' + metaData.getDatabaseProductVersion());
220
+ }
221
+ });
222
+ }
223
+
224
+ private transient RubyClass oidArray; // PostgreSQL::OID::Array
225
+
226
+ private RubyClass oidArray(final ThreadContext context) {
227
+ if (oidArray != null) return oidArray;
228
+ final RubyModule PostgreSQL = (RubyModule) getConnectionAdapters(context.runtime).getConstant("PostgreSQL");
229
+ return oidArray = ((RubyModule) PostgreSQL.getConstantAt("OID")).getClass("Array");
230
+ }
231
+
232
+
233
+ @Override
234
+ protected Connection newConnection() throws RaiseException, SQLException {
235
+ final Connection connection;
236
+ try {
237
+ connection = super.newConnection();
238
+ }
239
+ catch (SQLException ex) {
240
+ if ("3D000".equals(ex.getSQLState())) { // invalid_catalog_name
241
+ // org.postgresql.util.PSQLException: FATAL: database "xxx" does not exist
242
+ throw newNoDatabaseError(ex);
243
+ }
244
+ throw ex;
245
+ }
246
+ final PGConnection pgConnection;
247
+ if ( connection instanceof PGConnection ) {
248
+ pgConnection = (PGConnection) connection;
249
+ }
250
+ else {
251
+ pgConnection = connection.unwrap(PGConnection.class);
252
+ }
253
+ pgConnection.addDataType("daterange", DateRangeType.class);
254
+ pgConnection.addDataType("tsrange", TsRangeType.class);
255
+ pgConnection.addDataType("tstzrange", TstzRangeType.class);
256
+ pgConnection.addDataType("int4range", Int4RangeType.class);
257
+ pgConnection.addDataType("int8range", Int8RangeType.class);
258
+ pgConnection.addDataType("numrange", NumRangeType.class);
259
+ return connection;
260
+ }
261
+
262
+ @Override
263
+ protected PostgreSQLResult mapExecuteResult(final ThreadContext context, final Connection connection,
264
+ final ResultSet resultSet) throws SQLException {
265
+ return PostgreSQLResult.newResult(context, resultClass, this, resultSet);
266
+ }
267
+
268
+ /**
269
+ * Maps a query result set into a <code>ActiveRecord</code> result.
270
+ * @param context
271
+ * @param connection
272
+ * @param resultSet
273
+ * @return <code>ActiveRecord::Result</code>
274
+ * @throws SQLException
275
+ */
276
+ @Override
277
+ protected IRubyObject mapQueryResult(final ThreadContext context, final Connection connection,
278
+ final ResultSet resultSet) throws SQLException {
279
+ return mapExecuteResult(context, connection, resultSet).toARResult(context);
280
+ }
281
+
282
+ @Override
283
+ protected void setBlobParameter(final ThreadContext context,
284
+ final Connection connection, final PreparedStatement statement,
285
+ final int index, final IRubyObject value,
286
+ final IRubyObject attribute, final int type) throws SQLException {
287
+
288
+ if ( value instanceof RubyIO ) { // IO/File
289
+ statement.setBinaryStream(index, ((RubyIO) value).getInStream());
290
+ }
291
+ else { // should be a RubyString
292
+ final ByteList bytes = value.asString().getByteList();
293
+ statement.setBinaryStream(index,
294
+ new ByteArrayInputStream(bytes.unsafeBytes(), bytes.getBegin(), bytes.getRealSize()),
295
+ bytes.getRealSize() // length
296
+ );
297
+ }
298
+ }
299
+
300
+ @Override // to handle infinity timestamp values
301
+ protected void setTimestampParameter(final ThreadContext context,
302
+ final Connection connection, final PreparedStatement statement,
303
+ final int index, IRubyObject value,
304
+ final IRubyObject attribute, final int type) throws SQLException {
305
+
306
+ if ( value instanceof RubyFloat ) {
307
+ final double doubleValue = ( (RubyFloat) value ).getValue();
308
+ if ( Double.isInfinite(doubleValue) ) {
309
+ setTimestampInfinity(statement, index, doubleValue);
310
+ return;
311
+ }
312
+ }
313
+
314
+ RubyTime timeValue = toTime(context, value);
315
+
316
+ final Timestamp timestamp;
317
+
318
+ if (timeValue.getDateTime().getYear() > 0) {
319
+ timeValue = timeInDefaultTimeZone(context, timeValue);
320
+ DateTime dateTime = timeValue.getDateTime();
321
+ timestamp = new Timestamp(dateTime.getMillis());
322
+
323
+ if (timeValue.getNSec() > 0) timestamp.setNanos((int) (timestamp.getNanos() + timeValue.getNSec()));
324
+
325
+ statement.setTimestamp(index, timestamp, getCalendar(dateTime.getZone()));
326
+ }
327
+ else {
328
+ setTimestampBC(statement, index, timeValue);
329
+ }
330
+ }
331
+
332
+ private static void setTimestampBC(final PreparedStatement statement,
333
+ final int index, final RubyTime timeValue) throws SQLException {
334
+ DateTime dateTime = timeValue.getDateTime();
335
+ @SuppressWarnings("deprecated")
336
+ Timestamp timestamp = new Timestamp(dateTime.getYear() - 1900,
337
+ dateTime.getMonthOfYear() - 1,
338
+ dateTime.getDayOfMonth(),
339
+ dateTime.getHourOfDay(),
340
+ dateTime.getMinuteOfHour(),
341
+ dateTime.getSecondOfMinute(),
342
+ dateTime.getMillisOfSecond() * 1_000_000 + (int) timeValue.getNSec()
343
+ );
344
+
345
+ statement.setObject(index, timestamp);
346
+ }
347
+
348
+ private static void setTimestampInfinity(final PreparedStatement statement,
349
+ final int index, final double value) throws SQLException {
350
+ final Timestamp timestamp;
351
+ if ( value < 0 ) {
352
+ timestamp = new Timestamp(PGStatement.DATE_NEGATIVE_INFINITY);
353
+ }
354
+ else {
355
+ timestamp = new Timestamp(PGStatement.DATE_POSITIVE_INFINITY);
356
+ }
357
+
358
+ statement.setTimestamp( index, timestamp );
359
+ }
360
+
361
+ @Override
362
+ protected void setTimeParameter(final ThreadContext context,
363
+ final Connection connection, final PreparedStatement statement,
364
+ final int index, IRubyObject value,
365
+ final IRubyObject attribute, final int type) throws SQLException {
366
+ // to handle more fractional second precision than (default) 59.123 only
367
+ super.setTimestampParameter(context, connection, statement, index, value, attribute, type);
368
+ }
369
+
370
+ @Override
371
+ protected void setDateParameter(final ThreadContext context,
372
+ final Connection connection, final PreparedStatement statement,
373
+ final int index, IRubyObject value,
374
+ final IRubyObject attribute, final int type) throws SQLException {
375
+
376
+ if ( ! "Date".equals(value.getMetaClass().getName()) && value.respondsTo("to_date") ) {
377
+ value = value.callMethod(context, "to_date");
378
+ }
379
+
380
+ // NOTE: assuming Date#to_s does right ...
381
+ statement.setDate(index, Date.valueOf(value.toString()));
382
+ }
383
+
384
+ @Override
385
+ protected void setObjectParameter(final ThreadContext context,
386
+ final Connection connection, final PreparedStatement statement,
387
+ final int index, IRubyObject value,
388
+ final IRubyObject attribute, final int type) throws SQLException {
389
+
390
+ final String columnType = attributeSQLType(context, attribute).asJavaString();
391
+ Double[] pointValues;
392
+
393
+ switch ( columnType ) {
394
+ case "bit":
395
+ case "bit_varying":
396
+ // value should be a ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Bit::Data
397
+ setPGobjectParameter(statement, index, value.toString(), "bit");
398
+ break;
399
+
400
+ case "box":
401
+ pointValues = parseDoubles(value);
402
+ statement.setObject(index, new PGbox(pointValues[0], pointValues[1], pointValues[2], pointValues[3]));
403
+ break;
404
+
405
+ case "circle":
406
+ pointValues = parseDoubles(value);
407
+ statement.setObject(index, new PGcircle(pointValues[0], pointValues[1], pointValues[2]));
408
+ break;
409
+
410
+ case "cidr":
411
+ case "citext":
412
+ case "hstore":
413
+ case "inet":
414
+ case "ltree":
415
+ case "macaddr":
416
+ case "tsvector":
417
+ setPGobjectParameter(statement, index, value, columnType);
418
+ break;
419
+
420
+ case "enum":
421
+ // FIXME: This doesn't work but it gives a better error message than letting it be treated as a PGobject
422
+ statement.setObject(index, value.toString());
423
+ break;
424
+
425
+ case "interval":
426
+ statement.setObject(index, new PGInterval(value.toString()));
427
+ break;
428
+
429
+ case "json":
430
+ case "jsonb":
431
+ setJsonParameter(context, statement, index, value, columnType);
432
+ break;
433
+
434
+ case "line":
435
+ pointValues = parseDoubles(value);
436
+ if ( pointValues.length == 3 ) {
437
+ statement.setObject(index, new PGline(pointValues[0], pointValues[1], pointValues[2]));
438
+ } else {
439
+ statement.setObject(index, new PGline(pointValues[0], pointValues[1], pointValues[2], pointValues[3]));
440
+ }
441
+ break;
442
+
443
+ case "lseg":
444
+ pointValues = parseDoubles(value);
445
+ statement.setObject(index, new PGlseg(pointValues[0], pointValues[1], pointValues[2], pointValues[3]));
446
+ break;
447
+
448
+ case "path":
449
+ // If the value starts with "[" it is open, otherwise postgres treats it as a closed path
450
+ statement.setObject(index, new PGpath((PGpoint[]) convertToPoints(parseDoubles(value)), value.toString().startsWith("[")));
451
+ break;
452
+
453
+ case "point":
454
+ pointValues = parseDoubles(value);
455
+ statement.setObject(index, new PGpoint(pointValues[0], pointValues[1]));
456
+ break;
457
+
458
+ case "polygon":
459
+ statement.setObject(index, new PGpolygon((PGpoint[]) convertToPoints(parseDoubles(value))));
460
+ break;
461
+
462
+ case "uuid":
463
+ setUUIDParameter(statement, index, value);
464
+ break;
465
+
466
+ default:
467
+ if (columnType.endsWith("range")) {
468
+ setRangeParameter(context, statement, index, value, columnType);
469
+ } else {
470
+ setPGobjectParameter(statement, index, value, columnType);
471
+ }
472
+ }
473
+ }
474
+
475
+ // The tests won't start if this returns PGpoint[]
476
+ // it fails with a runtime error: "NativeException: java.lang.reflect.InvocationTargetException: [Lorg/postgresql/geometric/PGpoint"
477
+ private Object[] convertToPoints(Double[] values) throws SQLException {
478
+ PGpoint[] points = new PGpoint[values.length / 2];
479
+
480
+ for ( int i = 0; i < values.length; i += 2 ) {
481
+ points[i / 2] = new PGpoint(values[i], values[i + 1]);
482
+ }
483
+
484
+ return points;
485
+ }
486
+
487
+ private Double[] parseDoubles(IRubyObject value) {
488
+ Matcher matches = doubleValuePattern.matcher(value.toString());
489
+ ArrayList<Double> doubles = new ArrayList<Double>(4); // Paths and polygons may be larger but this covers points/circles/boxes/line segments
490
+
491
+ while ( matches.find() ) {
492
+ doubles.add(Double.parseDouble(matches.group()));
493
+ }
494
+
495
+ return doubles.toArray(new Double[doubles.size()]);
496
+ }
497
+
498
+ private void setJsonParameter(final ThreadContext context,
499
+ final PreparedStatement statement, final int index,
500
+ final IRubyObject value, final String columnType) throws SQLException {
501
+
502
+ final PGobject pgJson = new PGobject();
503
+ pgJson.setType(columnType);
504
+ pgJson.setValue(value.toString());
505
+ statement.setObject(index, pgJson);
506
+ }
507
+
508
+ private void setPGobjectParameter(final PreparedStatement statement, final int index,
509
+ final Object value, final String columnType) throws SQLException {
510
+
511
+ final PGobject param = new PGobject();
512
+ param.setType(columnType);
513
+ param.setValue(value.toString());
514
+ statement.setObject(index, param);
515
+ }
516
+
517
+ private void setRangeParameter(final ThreadContext context,
518
+ final PreparedStatement statement, final int index,
519
+ final IRubyObject value, final String columnType) throws SQLException {
520
+
521
+ final String rangeValue = value.toString();
522
+ final Object pgRange;
523
+
524
+ switch ( columnType ) {
525
+ case "daterange":
526
+ pgRange = new DateRangeType(rangeValue);
527
+ break;
528
+ case "tsrange":
529
+ pgRange = new TsRangeType(rangeValue);
530
+ break;
531
+ case "tstzrange":
532
+ pgRange = new TstzRangeType(rangeValue);
533
+ break;
534
+ case "int4range":
535
+ pgRange = new Int4RangeType(rangeValue);
536
+ break;
537
+ case "int8range":
538
+ pgRange = new Int8RangeType(rangeValue);
539
+ break;
540
+ default:
541
+ pgRange = new NumRangeType(rangeValue);
542
+ }
543
+
544
+ statement.setObject(index, pgRange);
545
+ }
546
+
547
+ @Override
548
+ protected void setStringParameter(final ThreadContext context,
549
+ final Connection connection, final PreparedStatement statement,
550
+ final int index, final IRubyObject value,
551
+ final IRubyObject attribute, final int type) throws SQLException {
552
+
553
+ if ( attributeSQLType(context, attribute) == context.nil ) {
554
+ /*
555
+ We have to check for a uuid here because in some cases
556
+ (for example, when doing "exists?" checks, or with legacy binds)
557
+ ActiveRecord doesn't send us the actual type of the attribute
558
+ and Postgres won't compare a uuid column with a string
559
+ */
560
+ final String uuid = value.toString();
561
+ int length = uuid.length();
562
+
563
+ // Checking the length so we don't have the overhead of the regex unless it "looks" like a UUID
564
+ if (length >= 32 && length < 40 && uuidPattern.matcher(uuid).matches()) {
565
+ setUUIDParameter(statement, index, uuid);
566
+ return;
567
+ }
568
+ }
569
+
570
+ super.setStringParameter(context, connection, statement, index, value, attribute, type);
571
+ }
572
+
573
+
574
+ private void setUUIDParameter(final PreparedStatement statement,
575
+ final int index, final IRubyObject value) throws SQLException {
576
+ setUUIDParameter(statement, index, value.toString());
577
+ }
578
+
579
+ private void setUUIDParameter(final PreparedStatement statement, final int index, String uuid) throws SQLException {
580
+
581
+ if (uuid.length() != 36) { // Assume its a non-standard format
582
+
583
+ /*
584
+ * Postgres supports a bunch of formats that aren't valid uuids, so we do too...
585
+ * A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11
586
+ * {a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}
587
+ * a0eebc999c0b4ef8bb6d6bb9bd380a11
588
+ * a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11
589
+ * {a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}
590
+ */
591
+
592
+ if (uuid.length() == 38 && uuid.charAt(0) == '{') {
593
+
594
+ // We got the {a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11} version so save some processing
595
+ uuid = uuid.substring(1, 37);
596
+
597
+ } else {
598
+
599
+ int valueIndex = 0;
600
+ int newUUIDIndex = 0;
601
+ char[] newUUIDChars = new char[36];
602
+
603
+ if (uuid.charAt(0) == '{') {
604
+ // Skip '{'
605
+ valueIndex++;
606
+ }
607
+
608
+ while (newUUIDIndex < 36) { // If we don't hit this before running out of characters it is an invalid UUID
609
+
610
+ char currentChar = uuid.charAt(valueIndex);
611
+
612
+ // Copy anything other than dashes
613
+ if (currentChar != '-') {
614
+ newUUIDChars[newUUIDIndex] = currentChar;
615
+ newUUIDIndex++;
616
+
617
+ // Insert dashes where appropriate
618
+ if(newUUIDIndex == 8 || newUUIDIndex == 13 || newUUIDIndex == 18 || newUUIDIndex == 23) {
619
+ newUUIDChars[newUUIDIndex] = '-';
620
+ newUUIDIndex++;
621
+ }
622
+ }
623
+
624
+ valueIndex++;
625
+ }
626
+
627
+ uuid = new String(newUUIDChars);
628
+
629
+ }
630
+ }
631
+
632
+ statement.setObject(index, UUID.fromString(uuid));
633
+ }
634
+
635
+ @Override
636
+ protected Integer jdbcTypeFor(final String type) {
637
+
638
+ Integer typeValue = POSTGRES_JDBC_TYPE_FOR.get(type);
639
+
640
+ if ( typeValue != null ) {
641
+ return typeValue;
642
+ }
643
+
644
+ return super.jdbcTypeFor(type);
645
+ }
646
+
647
+ @Override
648
+ protected TableName extractTableName(
649
+ final Connection connection, String catalog, String schema,
650
+ final String tableName) throws IllegalArgumentException, SQLException {
651
+ // The postgres JDBC driver will default to searching every schema if no
652
+ // schema search path is given. Default to the 'public' schema instead:
653
+ if ( schema == null ) schema = "public";
654
+ return super.extractTableName(connection, catalog, schema, tableName);
655
+ }
656
+
657
+ /**
658
+ * Determines if this field is multiple bits or a single bit (or t/f),
659
+ * if there are multiple bits they are turned into a string, if there
660
+ * is only one it is assumed to be a boolean value
661
+ * @param context current thread context
662
+ * @param resultSet the jdbc result set to pull the value from
663
+ * @param index the index of the column to convert
664
+ * @return RubyNil if NULL or RubyString of bits or RubyBoolean for a boolean value
665
+ * @throws SQLException if it failes to retrieve the value from the result set
666
+ */
667
+ @Override
668
+ protected IRubyObject bitToRuby(ThreadContext context, Ruby runtime, ResultSet resultSet, int index) throws SQLException {
669
+ String bits = resultSet.getString(index);
670
+
671
+ if (bits == null) return context.nil;
672
+ if (bits.length() > 1) return RubyString.newUnicodeString(context.runtime, bits);
673
+
674
+ // We assume it is a boolean value if it doesn't have a length
675
+ return booleanToRuby(context, runtime, resultSet, index);
676
+ }
677
+
678
+ /**
679
+ * Converts a JDBC date object to a Ruby date by parsing the string so we can handle edge cases
680
+ * @param context current thread context
681
+ * @param resultSet the jdbc result set to pull the value from
682
+ * @param index the index of the column to convert
683
+ * @return RubyNil if NULL or RubyDate if there is a value
684
+ * @throws SQLException if it failes to retrieve the value from the result set
685
+ */
686
+ @Override
687
+ protected IRubyObject dateToRuby(ThreadContext context, Ruby runtime, ResultSet resultSet, int index) throws SQLException {
688
+ // NOTE: PostgreSQL adapter under MRI using pg gem returns UTC-d Date/Time values
689
+ final String value = resultSet.getString(index);
690
+
691
+ return value == null ? context.nil : DateTimeUtils.parseDate(context, value, getDefaultTimeZone(context));
692
+ }
693
+
694
+
695
+ /**
696
+ * Detects PG specific types and converts them to their Ruby equivalents
697
+ * @param context current thread context
698
+ * @param resultSet the jdbc result set to pull the value from
699
+ * @param index the index of the column to convert
700
+ * @return RubyNil if NULL or RubyHash/RubyString/RubyObject
701
+ * @throws SQLException if it failes to retrieve the value from the result set
702
+ */
703
+ @Override
704
+ protected IRubyObject objectToRuby(ThreadContext context, Ruby runtime, ResultSet resultSet, int index) throws SQLException {
705
+ final Object object = resultSet.getObject(index);
706
+
707
+ if (object == null) return context.nil;
708
+
709
+ final Class<?> objectClass = object.getClass();
710
+
711
+ if (objectClass == UUID.class) return runtime.newString(object.toString());
712
+
713
+ if (object instanceof PGobject) {
714
+ if (objectClass == PGInterval.class) return runtime.newString(formatInterval(object));
715
+
716
+ if (objectClass == PGbox.class || objectClass == PGcircle.class ||
717
+ objectClass == PGline.class || objectClass == PGlseg.class ||
718
+ objectClass == PGpath.class || objectClass == PGpoint.class ||
719
+ objectClass == PGpolygon.class ) {
720
+ // AR 5.0+ expects that points don't have the '.0' if it is an integer
721
+ return runtime.newString(pointCleanerPattern.matcher(object.toString()).replaceAll(""));
722
+ }
723
+
724
+ // PG 9.2 JSON type will be returned here as well
725
+ return runtime.newString(object.toString());
726
+ }
727
+
728
+ if (object instanceof Map) { // hstore
729
+ // by default we avoid double parsing by driver and then column :
730
+ final RubyHash rubyObject = RubyHash.newHash(context.runtime);
731
+ rubyObject.putAll((Map) object); // converts keys/values to ruby
732
+ return rubyObject;
733
+ }
734
+
735
+ return JavaUtil.convertJavaToRuby(runtime, object);
736
+ }
737
+
738
+ /**
739
+ * Override character stream handling to be read as bytes
740
+ * @param context current thread context
741
+ * @param runtime the Ruby runtime
742
+ * @param resultSet the jdbc result set to pull the value from
743
+ * @param index the index of the column to convert
744
+ * @return RubyNil if NULL or RubyString if there is a value
745
+ * @throws SQLException if it failes to retrieve the value from the result set
746
+ */
747
+ @Override
748
+ protected IRubyObject readerToRuby(ThreadContext context, Ruby runtime,
749
+ ResultSet resultSet, int index) throws SQLException {
750
+ return stringToRuby(context, runtime, resultSet, index);
751
+ }
752
+
753
+ /**
754
+ * Converts a string column into a Ruby string by pulling the raw bytes from the column and
755
+ * turning them into a string using the default encoding
756
+ * @param context current thread context
757
+ * @param runtime the Ruby runtime
758
+ * @param resultSet the jdbc result set to pull the value from
759
+ * @param index the index of the column to convert
760
+ * @return RubyNil if NULL or RubyString if there is a value
761
+ * @throws SQLException if it failes to retrieve the value from the result set
762
+ */
763
+ @Override
764
+ protected IRubyObject stringToRuby(ThreadContext context, Ruby runtime, ResultSet resultSet, int index) throws SQLException {
765
+ final byte[] value = resultSet.getBytes(index);
766
+
767
+ return value == null ? context.nil : StringHelper.newDefaultInternalString(context.runtime, value);
768
+ }
769
+
770
+ /**
771
+ * Converts a JDBC time object to a Ruby time by parsing it as a string
772
+ * @param context current thread context
773
+ * @param runtime the Ruby runtime
774
+ * @param resultSet the jdbc result set to pull the value from
775
+ * @param column the index of the column to convert
776
+ * @return RubyNil if NULL or RubyDate if there is a value
777
+ * @throws SQLException if it failes to retrieve the value from the result set
778
+ */
779
+ @Override
780
+ protected IRubyObject timeToRuby(ThreadContext context, Ruby runtime, ResultSet resultSet, int column) throws SQLException {
781
+ final String value = resultSet.getString(column); // Using resultSet.getTimestamp(column) only gets .999 (3) precision
782
+
783
+ return value == null ? context.nil : DateTimeUtils.parseTime(context, value, getDefaultTimeZone(context));
784
+ }
785
+
786
+ /**
787
+ * Converts a JDBC timestamp object to a Ruby time by parsing it as a string
788
+ * @param context current thread context
789
+ * @param runtime the Ruby runtime
790
+ * @param resultSet the jdbc result set to pull the value from
791
+ * @param column the index of the column to convert
792
+ * @return RubyNil if NULL or RubyDate if there is a value
793
+ * @throws SQLException if it failes to retrieve the value from the result set
794
+ */
795
+ @Override
796
+ protected IRubyObject timestampToRuby(ThreadContext context, Ruby runtime, ResultSet resultSet,
797
+ int column) throws SQLException {
798
+ // NOTE: using Timestamp we loose information such as BC :
799
+ // Timestamp: '0001-12-31 22:59:59.0' String: '0001-12-31 22:59:59 BC'
800
+ final String value = resultSet.getString(column);
801
+
802
+ if (value == null) return context.nil;
803
+
804
+ final int len = value.length();
805
+ if (len < 10 && value.charAt(len - 1) == 'y') { // infinity / -infinity
806
+ IRubyObject infinity = parseInfinity(context.runtime, value);
807
+
808
+ if (infinity != null) return infinity;
809
+ }
810
+
811
+ // handles '0001-01-01 23:59:59 BC'
812
+ return DateTimeUtils.parseDateTime(context, value, getDefaultTimeZone(context));
813
+ }
814
+
815
+ private IRubyObject parseInfinity(final Ruby runtime, final String value) {
816
+ if ("infinity".equals(value)) return RubyFloat.newFloat(runtime, RubyFloat.INFINITY);
817
+ if ("-infinity".equals(value)) return RubyFloat.newFloat(runtime, -RubyFloat.INFINITY);
818
+
819
+ return null;
820
+ }
821
+
822
+ // NOTE: do not use PG classes in the API so that loading is delayed !
823
+ private static String formatInterval(final Object object) {
824
+ final PGInterval interval = (PGInterval) object;
825
+ final StringBuilder str = new StringBuilder(32);
826
+
827
+ final int years = interval.getYears();
828
+ if (years != 0) str.append(years).append(" years ");
829
+
830
+ final int months = interval.getMonths();
831
+ if (months != 0) str.append(months).append(" months ");
832
+
833
+ final int days = interval.getDays();
834
+ if (days != 0) str.append(days).append(" days ");
835
+
836
+ final int hours = interval.getHours();
837
+ final int mins = interval.getMinutes();
838
+ final int secs = (int) interval.getSeconds();
839
+ if (hours != 0 || mins != 0 || secs != 0) { // xx:yy:zz if not all 00
840
+ if (hours < 10) str.append('0');
841
+
842
+ str.append(hours).append(':');
843
+
844
+ if (mins < 10) str.append('0');
845
+
846
+ str.append(mins).append(':');
847
+
848
+ if (secs < 10) str.append('0');
849
+
850
+ str.append(secs);
851
+
852
+ } else if (str.length() > 1) {
853
+ str.deleteCharAt(str.length() - 1); // " " at the end
854
+ }
855
+
856
+ return str.toString();
857
+ }
858
+
859
+ // NOTE: without these custom registered Postgre (driver) types
860
+ // ... we can not set range parameters in prepared statements !
861
+
862
+ public static class DateRangeType extends PGobject {
863
+
864
+ private static final long serialVersionUID = -5378414736244196691L;
865
+
866
+ public DateRangeType() {
867
+ setType("daterange");
868
+ }
869
+
870
+ public DateRangeType(final String value) throws SQLException {
871
+ this();
872
+ setValue(value);
873
+ }
874
+
875
+ }
876
+
877
+ public static class TsRangeType extends PGobject {
878
+
879
+ private static final long serialVersionUID = -2991390995527988409L;
880
+
881
+ public TsRangeType() {
882
+ setType("tsrange");
883
+ }
884
+
885
+ public TsRangeType(final String value) throws SQLException {
886
+ this();
887
+ setValue(value);
888
+ }
889
+
890
+ }
891
+
892
+ public static class TstzRangeType extends PGobject {
893
+
894
+ private static final long serialVersionUID = 6492535255861743334L;
895
+
896
+ public TstzRangeType() {
897
+ setType("tstzrange");
898
+ }
899
+
900
+ public TstzRangeType(final String value) throws SQLException {
901
+ this();
902
+ setValue(value);
903
+ }
904
+
905
+ }
906
+
907
+ public static class Int4RangeType extends PGobject {
908
+
909
+ private static final long serialVersionUID = 4490562039665289763L;
910
+
911
+ public Int4RangeType() {
912
+ setType("int4range");
913
+ }
914
+
915
+ public Int4RangeType(final String value) throws SQLException {
916
+ this();
917
+ setValue(value);
918
+ }
919
+
920
+ }
921
+
922
+ public static class Int8RangeType extends PGobject {
923
+
924
+ private static final long serialVersionUID = -1458706215346897102L;
925
+
926
+ public Int8RangeType() {
927
+ setType("int8range");
928
+ }
929
+
930
+ public Int8RangeType(final String value) throws SQLException {
931
+ this();
932
+ setValue(value);
933
+ }
934
+
935
+ }
936
+
937
+ public static class NumRangeType extends PGobject {
938
+
939
+ private static final long serialVersionUID = 5892509252900362510L;
940
+
941
+ public NumRangeType() {
942
+ setType("numrange");
943
+ }
944
+
945
+ public NumRangeType(final String value) throws SQLException {
946
+ this();
947
+ setValue(value);
948
+ }
949
+
950
+ }
951
+
952
+ }