activerecord-jdbc-alt-adapter 50.3.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (198) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +35 -0
  3. data/.travis.yml +100 -0
  4. data/.yardopts +4 -0
  5. data/CONTRIBUTING.md +50 -0
  6. data/Gemfile +92 -0
  7. data/History.md +1191 -0
  8. data/LICENSE.txt +26 -0
  9. data/README.md +240 -0
  10. data/RUNNING_TESTS.md +127 -0
  11. data/Rakefile +336 -0
  12. data/Rakefile.jdbc +20 -0
  13. data/activerecord-jdbc-adapter.gemspec +55 -0
  14. data/activerecord-jdbc-alt-adapter.gemspec +56 -0
  15. data/lib/active_record/connection_adapters/as400_adapter.rb +2 -0
  16. data/lib/active_record/connection_adapters/db2_adapter.rb +1 -0
  17. data/lib/active_record/connection_adapters/derby_adapter.rb +1 -0
  18. data/lib/active_record/connection_adapters/firebird_adapter.rb +1 -0
  19. data/lib/active_record/connection_adapters/h2_adapter.rb +1 -0
  20. data/lib/active_record/connection_adapters/hsqldb_adapter.rb +1 -0
  21. data/lib/active_record/connection_adapters/informix_adapter.rb +1 -0
  22. data/lib/active_record/connection_adapters/jdbc_adapter.rb +1 -0
  23. data/lib/active_record/connection_adapters/jndi_adapter.rb +1 -0
  24. data/lib/active_record/connection_adapters/mariadb_adapter.rb +1 -0
  25. data/lib/active_record/connection_adapters/mssql_adapter.rb +1 -0
  26. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
  27. data/lib/active_record/connection_adapters/mysql_adapter.rb +1 -0
  28. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1 -0
  29. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -0
  30. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +1 -0
  31. data/lib/activerecord-jdbc-adapter.rb +1 -0
  32. data/lib/arel/visitors/compat.rb +60 -0
  33. data/lib/arel/visitors/db2.rb +137 -0
  34. data/lib/arel/visitors/derby.rb +112 -0
  35. data/lib/arel/visitors/firebird.rb +79 -0
  36. data/lib/arel/visitors/h2.rb +25 -0
  37. data/lib/arel/visitors/hsqldb.rb +32 -0
  38. data/lib/arel/visitors/postgresql_jdbc.rb +6 -0
  39. data/lib/arel/visitors/sql_server.rb +225 -0
  40. data/lib/arel/visitors/sql_server/ng42.rb +294 -0
  41. data/lib/arel/visitors/sqlserver.rb +214 -0
  42. data/lib/arjdbc.rb +19 -0
  43. data/lib/arjdbc/abstract/connection_management.rb +35 -0
  44. data/lib/arjdbc/abstract/core.rb +74 -0
  45. data/lib/arjdbc/abstract/database_statements.rb +64 -0
  46. data/lib/arjdbc/abstract/statement_cache.rb +58 -0
  47. data/lib/arjdbc/abstract/transaction_support.rb +86 -0
  48. data/lib/arjdbc/db2.rb +4 -0
  49. data/lib/arjdbc/db2/adapter.rb +789 -0
  50. data/lib/arjdbc/db2/as400.rb +130 -0
  51. data/lib/arjdbc/db2/column.rb +167 -0
  52. data/lib/arjdbc/db2/connection_methods.rb +44 -0
  53. data/lib/arjdbc/derby.rb +3 -0
  54. data/lib/arjdbc/derby/active_record_patch.rb +13 -0
  55. data/lib/arjdbc/derby/adapter.rb +540 -0
  56. data/lib/arjdbc/derby/connection_methods.rb +20 -0
  57. data/lib/arjdbc/derby/schema_creation.rb +15 -0
  58. data/lib/arjdbc/discover.rb +104 -0
  59. data/lib/arjdbc/firebird.rb +4 -0
  60. data/lib/arjdbc/firebird/adapter.rb +434 -0
  61. data/lib/arjdbc/firebird/connection_methods.rb +23 -0
  62. data/lib/arjdbc/h2.rb +3 -0
  63. data/lib/arjdbc/h2/adapter.rb +303 -0
  64. data/lib/arjdbc/h2/connection_methods.rb +27 -0
  65. data/lib/arjdbc/hsqldb.rb +3 -0
  66. data/lib/arjdbc/hsqldb/adapter.rb +297 -0
  67. data/lib/arjdbc/hsqldb/connection_methods.rb +28 -0
  68. data/lib/arjdbc/hsqldb/explain_support.rb +35 -0
  69. data/lib/arjdbc/hsqldb/schema_creation.rb +11 -0
  70. data/lib/arjdbc/informix.rb +5 -0
  71. data/lib/arjdbc/informix/adapter.rb +162 -0
  72. data/lib/arjdbc/informix/connection_methods.rb +9 -0
  73. data/lib/arjdbc/jdbc.rb +59 -0
  74. data/lib/arjdbc/jdbc/adapter.rb +475 -0
  75. data/lib/arjdbc/jdbc/adapter_require.rb +46 -0
  76. data/lib/arjdbc/jdbc/base_ext.rb +15 -0
  77. data/lib/arjdbc/jdbc/callbacks.rb +53 -0
  78. data/lib/arjdbc/jdbc/column.rb +97 -0
  79. data/lib/arjdbc/jdbc/connection.rb +14 -0
  80. data/lib/arjdbc/jdbc/connection_methods.rb +37 -0
  81. data/lib/arjdbc/jdbc/error.rb +65 -0
  82. data/lib/arjdbc/jdbc/extension.rb +59 -0
  83. data/lib/arjdbc/jdbc/java.rb +13 -0
  84. data/lib/arjdbc/jdbc/railtie.rb +2 -0
  85. data/lib/arjdbc/jdbc/rake_tasks.rb +3 -0
  86. data/lib/arjdbc/jdbc/serialized_attributes_helper.rb +3 -0
  87. data/lib/arjdbc/jdbc/type_cast.rb +166 -0
  88. data/lib/arjdbc/jdbc/type_converter.rb +142 -0
  89. data/lib/arjdbc/mssql.rb +7 -0
  90. data/lib/arjdbc/mssql/adapter.rb +384 -0
  91. data/lib/arjdbc/mssql/column.rb +29 -0
  92. data/lib/arjdbc/mssql/connection_methods.rb +79 -0
  93. data/lib/arjdbc/mssql/database_statements.rb +134 -0
  94. data/lib/arjdbc/mssql/errors.rb +6 -0
  95. data/lib/arjdbc/mssql/explain_support.rb +129 -0
  96. data/lib/arjdbc/mssql/extensions.rb +36 -0
  97. data/lib/arjdbc/mssql/limit_helpers.rb +231 -0
  98. data/lib/arjdbc/mssql/lock_methods.rb +77 -0
  99. data/lib/arjdbc/mssql/old_adapter.rb +804 -0
  100. data/lib/arjdbc/mssql/old_column.rb +200 -0
  101. data/lib/arjdbc/mssql/quoting.rb +101 -0
  102. data/lib/arjdbc/mssql/schema_creation.rb +31 -0
  103. data/lib/arjdbc/mssql/schema_definitions.rb +74 -0
  104. data/lib/arjdbc/mssql/schema_statements.rb +329 -0
  105. data/lib/arjdbc/mssql/transaction.rb +69 -0
  106. data/lib/arjdbc/mssql/types.rb +52 -0
  107. data/lib/arjdbc/mssql/types/binary_types.rb +33 -0
  108. data/lib/arjdbc/mssql/types/date_and_time_types.rb +134 -0
  109. data/lib/arjdbc/mssql/types/deprecated_types.rb +40 -0
  110. data/lib/arjdbc/mssql/types/numeric_types.rb +71 -0
  111. data/lib/arjdbc/mssql/types/string_types.rb +56 -0
  112. data/lib/arjdbc/mssql/utils.rb +66 -0
  113. data/lib/arjdbc/mysql.rb +3 -0
  114. data/lib/arjdbc/mysql/adapter.rb +140 -0
  115. data/lib/arjdbc/mysql/connection_methods.rb +166 -0
  116. data/lib/arjdbc/oracle/adapter.rb +863 -0
  117. data/lib/arjdbc/postgresql.rb +3 -0
  118. data/lib/arjdbc/postgresql/adapter.rb +687 -0
  119. data/lib/arjdbc/postgresql/base/array_decoder.rb +26 -0
  120. data/lib/arjdbc/postgresql/base/array_encoder.rb +25 -0
  121. data/lib/arjdbc/postgresql/base/array_parser.rb +95 -0
  122. data/lib/arjdbc/postgresql/base/pgconn.rb +11 -0
  123. data/lib/arjdbc/postgresql/column.rb +51 -0
  124. data/lib/arjdbc/postgresql/connection_methods.rb +67 -0
  125. data/lib/arjdbc/postgresql/name.rb +24 -0
  126. data/lib/arjdbc/postgresql/oid_types.rb +266 -0
  127. data/lib/arjdbc/railtie.rb +11 -0
  128. data/lib/arjdbc/sqlite3.rb +3 -0
  129. data/lib/arjdbc/sqlite3/adapter.rb +678 -0
  130. data/lib/arjdbc/sqlite3/connection_methods.rb +59 -0
  131. data/lib/arjdbc/sybase.rb +2 -0
  132. data/lib/arjdbc/sybase/adapter.rb +47 -0
  133. data/lib/arjdbc/tasks.rb +13 -0
  134. data/lib/arjdbc/tasks/database_tasks.rb +31 -0
  135. data/lib/arjdbc/tasks/databases.rake +48 -0
  136. data/lib/arjdbc/tasks/db2_database_tasks.rb +104 -0
  137. data/lib/arjdbc/tasks/derby_database_tasks.rb +95 -0
  138. data/lib/arjdbc/tasks/h2_database_tasks.rb +31 -0
  139. data/lib/arjdbc/tasks/hsqldb_database_tasks.rb +70 -0
  140. data/lib/arjdbc/tasks/jdbc_database_tasks.rb +169 -0
  141. data/lib/arjdbc/tasks/mssql_database_tasks.rb +46 -0
  142. data/lib/arjdbc/util/quoted_cache.rb +60 -0
  143. data/lib/arjdbc/util/serialized_attributes.rb +98 -0
  144. data/lib/arjdbc/util/table_copier.rb +110 -0
  145. data/lib/arjdbc/version.rb +3 -0
  146. data/lib/generators/jdbc/USAGE +9 -0
  147. data/lib/generators/jdbc/jdbc_generator.rb +17 -0
  148. data/lib/jdbc_adapter.rb +2 -0
  149. data/lib/jdbc_adapter/rake_tasks.rb +4 -0
  150. data/lib/jdbc_adapter/version.rb +4 -0
  151. data/pom.xml +114 -0
  152. data/rails_generators/jdbc_generator.rb +15 -0
  153. data/rails_generators/templates/config/initializers/jdbc.rb +10 -0
  154. data/rails_generators/templates/lib/tasks/jdbc.rake +11 -0
  155. data/rakelib/01-tomcat.rake +51 -0
  156. data/rakelib/02-test.rake +132 -0
  157. data/rakelib/bundler_ext.rb +11 -0
  158. data/rakelib/db.rake +75 -0
  159. data/rakelib/rails.rake +223 -0
  160. data/src/java/arjdbc/ArJdbcModule.java +276 -0
  161. data/src/java/arjdbc/db2/DB2Module.java +76 -0
  162. data/src/java/arjdbc/db2/DB2RubyJdbcConnection.java +126 -0
  163. data/src/java/arjdbc/derby/DerbyModule.java +178 -0
  164. data/src/java/arjdbc/derby/DerbyRubyJdbcConnection.java +152 -0
  165. data/src/java/arjdbc/firebird/FirebirdRubyJdbcConnection.java +174 -0
  166. data/src/java/arjdbc/h2/H2Module.java +50 -0
  167. data/src/java/arjdbc/h2/H2RubyJdbcConnection.java +85 -0
  168. data/src/java/arjdbc/hsqldb/HSQLDBModule.java +73 -0
  169. data/src/java/arjdbc/informix/InformixRubyJdbcConnection.java +75 -0
  170. data/src/java/arjdbc/jdbc/AdapterJavaService.java +43 -0
  171. data/src/java/arjdbc/jdbc/Callable.java +44 -0
  172. data/src/java/arjdbc/jdbc/ConnectionFactory.java +45 -0
  173. data/src/java/arjdbc/jdbc/DataSourceConnectionFactory.java +156 -0
  174. data/src/java/arjdbc/jdbc/DriverConnectionFactory.java +63 -0
  175. data/src/java/arjdbc/jdbc/DriverWrapper.java +119 -0
  176. data/src/java/arjdbc/jdbc/JdbcResult.java +130 -0
  177. data/src/java/arjdbc/jdbc/RubyConnectionFactory.java +61 -0
  178. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +3979 -0
  179. data/src/java/arjdbc/mssql/MSSQLModule.java +90 -0
  180. data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +508 -0
  181. data/src/java/arjdbc/mysql/MySQLModule.java +152 -0
  182. data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +294 -0
  183. data/src/java/arjdbc/oracle/OracleModule.java +80 -0
  184. data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +455 -0
  185. data/src/java/arjdbc/postgresql/ByteaUtils.java +157 -0
  186. data/src/java/arjdbc/postgresql/PgDateTimeUtils.java +52 -0
  187. data/src/java/arjdbc/postgresql/PostgreSQLModule.java +77 -0
  188. data/src/java/arjdbc/postgresql/PostgreSQLResult.java +192 -0
  189. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +948 -0
  190. data/src/java/arjdbc/sqlite3/SQLite3Module.java +73 -0
  191. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +525 -0
  192. data/src/java/arjdbc/util/CallResultSet.java +826 -0
  193. data/src/java/arjdbc/util/DateTimeUtils.java +699 -0
  194. data/src/java/arjdbc/util/ObjectSupport.java +65 -0
  195. data/src/java/arjdbc/util/QuotingUtils.java +137 -0
  196. data/src/java/arjdbc/util/StringCache.java +63 -0
  197. data/src/java/arjdbc/util/StringHelper.java +145 -0
  198. metadata +269 -0
@@ -0,0 +1,948 @@
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.ext.bigdecimal.RubyBigDecimal;
58
+ import org.jruby.javasupport.JavaUtil;
59
+ import org.jruby.runtime.ObjectAllocator;
60
+ import org.jruby.runtime.ThreadContext;
61
+ import org.jruby.runtime.builtin.IRubyObject;
62
+ import org.jruby.util.ByteList;
63
+
64
+ import org.postgresql.PGConnection;
65
+ import org.postgresql.PGStatement;
66
+ import org.postgresql.geometric.PGbox;
67
+ import org.postgresql.geometric.PGcircle;
68
+ import org.postgresql.geometric.PGline;
69
+ import org.postgresql.geometric.PGlseg;
70
+ import org.postgresql.geometric.PGpath;
71
+ import org.postgresql.geometric.PGpoint;
72
+ import org.postgresql.geometric.PGpolygon;
73
+ import org.postgresql.util.PGInterval;
74
+ import org.postgresql.util.PGobject;
75
+
76
+ /**
77
+ *
78
+ * @author enebo
79
+ */
80
+ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection {
81
+ private static final long serialVersionUID = 7235537759545717760L;
82
+ private static final int HSTORE_TYPE = 100000 + 1111;
83
+ private static final Pattern doubleValuePattern = Pattern.compile("(-?\\d+(?:\\.\\d+)?)");
84
+ private static final Pattern uuidPattern = Pattern.compile("\\{?\\p{XDigit}{4}(?:-?(\\p{XDigit}{4})){7}\\}?"); // Fuzzy match postgres's allowed formats
85
+
86
+ private static final Map<String, Integer> POSTGRES_JDBC_TYPE_FOR = new HashMap<String, Integer>(32, 1);
87
+ static {
88
+ POSTGRES_JDBC_TYPE_FOR.put("bit", Types.OTHER);
89
+ POSTGRES_JDBC_TYPE_FOR.put("bit_varying", Types.OTHER);
90
+ POSTGRES_JDBC_TYPE_FOR.put("box", Types.OTHER);
91
+ POSTGRES_JDBC_TYPE_FOR.put("circle", Types.OTHER);
92
+ POSTGRES_JDBC_TYPE_FOR.put("citext", Types.OTHER);
93
+ POSTGRES_JDBC_TYPE_FOR.put("daterange", Types.OTHER);
94
+ POSTGRES_JDBC_TYPE_FOR.put("hstore", Types.OTHER);
95
+ POSTGRES_JDBC_TYPE_FOR.put("int4range", Types.OTHER);
96
+ POSTGRES_JDBC_TYPE_FOR.put("int8range", Types.OTHER);
97
+ POSTGRES_JDBC_TYPE_FOR.put("interval", Types.OTHER);
98
+ POSTGRES_JDBC_TYPE_FOR.put("json", Types.OTHER);
99
+ POSTGRES_JDBC_TYPE_FOR.put("jsonb", Types.OTHER);
100
+ POSTGRES_JDBC_TYPE_FOR.put("line", Types.OTHER);
101
+ POSTGRES_JDBC_TYPE_FOR.put("lseg", Types.OTHER);
102
+ POSTGRES_JDBC_TYPE_FOR.put("ltree", Types.OTHER);
103
+ POSTGRES_JDBC_TYPE_FOR.put("money", Types.OTHER);
104
+ POSTGRES_JDBC_TYPE_FOR.put("numrange", Types.OTHER);
105
+ POSTGRES_JDBC_TYPE_FOR.put("path", Types.OTHER);
106
+ POSTGRES_JDBC_TYPE_FOR.put("point", Types.OTHER);
107
+ POSTGRES_JDBC_TYPE_FOR.put("polygon", Types.OTHER);
108
+ POSTGRES_JDBC_TYPE_FOR.put("tsrange", Types.OTHER);
109
+ POSTGRES_JDBC_TYPE_FOR.put("tstzrange", Types.OTHER);
110
+ POSTGRES_JDBC_TYPE_FOR.put("tsvector", Types.OTHER);
111
+ POSTGRES_JDBC_TYPE_FOR.put("uuid", Types.OTHER);
112
+ }
113
+
114
+ // Used to wipe trailing 0's from points (3.0, 5.6) -> (3, 5.6)
115
+ private static final Pattern pointCleanerPattern = Pattern.compile("\\.0\\b");
116
+
117
+ private RubyClass resultClass;
118
+
119
+ public PostgreSQLRubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
120
+ super(runtime, metaClass);
121
+
122
+ resultClass = getMetaClass().getClass("Result");
123
+ }
124
+
125
+ public static RubyClass createPostgreSQLJdbcConnectionClass(Ruby runtime, RubyClass jdbcConnection) {
126
+ final RubyClass clazz = getConnectionAdapters(runtime).
127
+ defineClassUnder("PostgreSQLJdbcConnection", jdbcConnection, ALLOCATOR);
128
+ clazz.defineAnnotatedMethods(PostgreSQLRubyJdbcConnection.class);
129
+ getConnectionAdapters(runtime).setConstant("PostgresJdbcConnection", clazz); // backwards-compat
130
+ return clazz;
131
+ }
132
+
133
+ public static RubyClass load(final Ruby runtime) {
134
+ RubyClass jdbcConnection = getJdbcConnection(runtime);
135
+ RubyClass postgreSQLConnection = createPostgreSQLJdbcConnectionClass(runtime, jdbcConnection);
136
+ PostgreSQLResult.createPostgreSQLResultClass(runtime, postgreSQLConnection);
137
+ return postgreSQLConnection;
138
+ }
139
+
140
+ protected static final ObjectAllocator ALLOCATOR = new ObjectAllocator() {
141
+ public IRubyObject allocate(Ruby runtime, RubyClass klass) {
142
+ return new PostgreSQLRubyJdbcConnection(runtime, klass);
143
+ }
144
+ };
145
+
146
+ @Override
147
+ protected String buildURL(final ThreadContext context, final IRubyObject url) {
148
+ // (deprecated AR-JDBC specific url) options: disabled with adapter: postgresql
149
+ // since it collides with AR as it likes to use the key for its own purposes :
150
+ // e.g. config[:options] = "-c geqo=off"
151
+ return DriverWrapper.buildURL(url, Collections.EMPTY_MAP);
152
+ }
153
+
154
+ @Override
155
+ protected DriverWrapper newDriverWrapper(final ThreadContext context, final String driver) {
156
+ DriverWrapper driverWrapper = super.newDriverWrapper(context, driver);
157
+
158
+ final java.sql.Driver jdbcDriver = driverWrapper.getDriverInstance();
159
+ if ( jdbcDriver.getClass().getName().startsWith("org.postgresql.") ) {
160
+ try { // public static String getVersion()
161
+ final String version = (String) // "PostgreSQL 9.2 JDBC4 (build 1002)"
162
+ jdbcDriver.getClass().getMethod("getVersion").invoke(null);
163
+ if ( version != null && version.indexOf("JDBC3") >= 0 ) {
164
+ // config[:connection_alive_sql] ||= 'SELECT 1'
165
+ setConfigValueIfNotSet(context, "connection_alive_sql", context.runtime.newString("SELECT 1"));
166
+ }
167
+ }
168
+ catch (NoSuchMethodException e) { }
169
+ catch (SecurityException e) { }
170
+ catch (IllegalAccessException e) { }
171
+ catch (InvocationTargetException e) { }
172
+ }
173
+
174
+ return driverWrapper;
175
+ }
176
+
177
+ @Override
178
+ protected final IRubyObject beginTransaction(final ThreadContext context, final Connection connection,
179
+ final IRubyObject isolation) throws SQLException {
180
+ // NOTE: only reversed order - just to ~ match how Rails does it :
181
+ /* if ( connection.getAutoCommit() ) */ connection.setAutoCommit(false);
182
+ if ( isolation != null ) setTransactionIsolation(context, connection, isolation);
183
+ return context.nil;
184
+ }
185
+
186
+ // storesMixedCaseIdentifiers() return false;
187
+ // storesLowerCaseIdentifiers() return true;
188
+ // storesUpperCaseIdentifiers() return false;
189
+
190
+ @Override
191
+ protected String caseConvertIdentifierForRails(final Connection connection, final String value)
192
+ throws SQLException {
193
+ return value;
194
+ }
195
+
196
+ @Override
197
+ protected String caseConvertIdentifierForJdbc(final Connection connection, final String value)
198
+ throws SQLException {
199
+ return value;
200
+ }
201
+
202
+ @Override
203
+ protected String internedTypeFor(final ThreadContext context, final IRubyObject attribute) throws SQLException {
204
+ RubyClass arrayClass = oidArray(context);
205
+ RubyBasicObject attributeType = (RubyBasicObject) attributeType(context, attribute);
206
+ // The type or its delegate is an OID::Array
207
+ if (arrayClass.isInstance(attributeType) ||
208
+ (attributeType.hasInstanceVariable("@delegate_dc_obj") &&
209
+ arrayClass.isInstance(attributeType.getInstanceVariable("@delegate_dc_obj")))) {
210
+ return "array";
211
+ }
212
+
213
+ return super.internedTypeFor(context, attribute);
214
+ }
215
+
216
+ @JRubyMethod(name = "database_product")
217
+ public IRubyObject database_product(final ThreadContext context) {
218
+ return withConnection(context, new Callable<IRubyObject>() {
219
+ public IRubyObject call(final Connection connection) throws SQLException {
220
+ final DatabaseMetaData metaData = connection.getMetaData();
221
+ return RubyString.newString(context.runtime, metaData.getDatabaseProductName() + ' ' + metaData.getDatabaseProductVersion());
222
+ }
223
+ });
224
+ }
225
+
226
+ private transient RubyClass oidArray; // PostgreSQL::OID::Array
227
+
228
+ private RubyClass oidArray(final ThreadContext context) {
229
+ if (oidArray != null) return oidArray;
230
+ final RubyModule PostgreSQL = (RubyModule) getConnectionAdapters(context.runtime).getConstant("PostgreSQL");
231
+ return oidArray = ((RubyModule) PostgreSQL.getConstantAt("OID")).getClass("Array");
232
+ }
233
+
234
+
235
+ @Override
236
+ protected Connection newConnection() throws RaiseException, SQLException {
237
+ final Connection connection;
238
+ try {
239
+ connection = super.newConnection();
240
+ }
241
+ catch (SQLException ex) {
242
+ if ("3D000".equals(ex.getSQLState())) { // invalid_catalog_name
243
+ // org.postgresql.util.PSQLException: FATAL: database "xxx" does not exist
244
+ throw newNoDatabaseError(ex);
245
+ }
246
+ throw ex;
247
+ }
248
+ final PGConnection pgConnection;
249
+ if ( connection instanceof PGConnection ) {
250
+ pgConnection = (PGConnection) connection;
251
+ }
252
+ else {
253
+ pgConnection = connection.unwrap(PGConnection.class);
254
+ }
255
+ pgConnection.addDataType("daterange", DateRangeType.class);
256
+ pgConnection.addDataType("tsrange", TsRangeType.class);
257
+ pgConnection.addDataType("tstzrange", TstzRangeType.class);
258
+ pgConnection.addDataType("int4range", Int4RangeType.class);
259
+ pgConnection.addDataType("int8range", Int8RangeType.class);
260
+ pgConnection.addDataType("numrange", NumRangeType.class);
261
+ return connection;
262
+ }
263
+
264
+ @Override
265
+ protected PostgreSQLResult mapExecuteResult(final ThreadContext context, final Connection connection,
266
+ final ResultSet resultSet) throws SQLException {
267
+ return PostgreSQLResult.newResult(context, resultClass, this, resultSet);
268
+ }
269
+
270
+ /**
271
+ * Maps a query result set into a <code>ActiveRecord</code> result.
272
+ * @param context
273
+ * @param connection
274
+ * @param resultSet
275
+ * @return <code>ActiveRecord::Result</code>
276
+ * @throws SQLException
277
+ */
278
+ @Override
279
+ protected IRubyObject mapQueryResult(final ThreadContext context, final Connection connection,
280
+ final ResultSet resultSet) throws SQLException {
281
+ return mapExecuteResult(context, connection, resultSet).toARResult(context);
282
+ }
283
+
284
+ @Override
285
+ protected void setArrayParameter(final ThreadContext context,
286
+ final Connection connection, final PreparedStatement statement,
287
+ final int index, final IRubyObject value,
288
+ final IRubyObject attribute, final int type) throws SQLException {
289
+
290
+ final String typeName = resolveArrayBaseTypeName(context, attribute);
291
+ final RubyArray valueForDB = (RubyArray) value.callMethod(context, "values");
292
+
293
+ Object[] values;
294
+ switch (typeName) {
295
+ case "datetime":
296
+ case "timestamp": {
297
+ values = PgDateTimeUtils.timestampStringArray(context, valueForDB);
298
+ break;
299
+ }
300
+ default:
301
+ values = valueForDB.toArray();
302
+ break;
303
+ }
304
+
305
+ statement.setArray(index, connection.createArrayOf(typeName, values));
306
+ }
307
+
308
+ @Override
309
+ protected void setBlobParameter(final ThreadContext context,
310
+ final Connection connection, final PreparedStatement statement,
311
+ final int index, final IRubyObject value,
312
+ final IRubyObject attribute, final int type) throws SQLException {
313
+
314
+ if ( value instanceof RubyIO ) { // IO/File
315
+ statement.setBinaryStream(index, ((RubyIO) value).getInStream());
316
+ }
317
+ else { // should be a RubyString
318
+ final ByteList bytes = value.asString().getByteList();
319
+ statement.setBinaryStream(index,
320
+ new ByteArrayInputStream(bytes.unsafeBytes(), bytes.getBegin(), bytes.getRealSize()),
321
+ bytes.getRealSize() // length
322
+ );
323
+ }
324
+ }
325
+
326
+ @Override // to handle infinity timestamp values
327
+ protected void setTimestampParameter(final ThreadContext context,
328
+ final Connection connection, final PreparedStatement statement,
329
+ final int index, IRubyObject value,
330
+ final IRubyObject attribute, final int type) throws SQLException {
331
+ // PGJDBC uses strings internally anyway, so using Timestamp doesn't do any good
332
+ String tsString = PgDateTimeUtils.timestampValueToString(context, value, null, true);
333
+ statement.setObject(index, tsString, Types.OTHER);
334
+ }
335
+
336
+ private static void setTimestampInfinity(final PreparedStatement statement,
337
+ final int index, final double value) throws SQLException {
338
+ final Timestamp timestamp;
339
+ if ( value < 0 ) {
340
+ timestamp = new Timestamp(PGStatement.DATE_NEGATIVE_INFINITY);
341
+ }
342
+ else {
343
+ timestamp = new Timestamp(PGStatement.DATE_POSITIVE_INFINITY);
344
+ }
345
+
346
+ statement.setTimestamp( index, timestamp );
347
+ }
348
+
349
+ @Override
350
+ protected void setTimeParameter(final ThreadContext context,
351
+ final Connection connection, final PreparedStatement statement,
352
+ final int index, IRubyObject value,
353
+ final IRubyObject attribute, final int type) throws SQLException {
354
+ // to handle more fractional second precision than (default) 59.123 only
355
+ String timeStr = DateTimeUtils.timeString(context, value, getDefaultTimeZone(context), true);
356
+ statement.setObject(index, timeStr, Types.OTHER);
357
+ }
358
+
359
+ @Override
360
+ protected void setDateParameter(final ThreadContext context,
361
+ final Connection connection, final PreparedStatement statement,
362
+ final int index, IRubyObject value,
363
+ final IRubyObject attribute, final int type) throws SQLException {
364
+
365
+ if ( ! "Date".equals(value.getMetaClass().getName()) && value.respondsTo("to_date") ) {
366
+ value = value.callMethod(context, "to_date");
367
+ }
368
+
369
+ // NOTE: assuming Date#to_s does right ...
370
+ statement.setDate(index, Date.valueOf(value.toString()));
371
+ }
372
+
373
+ @Override
374
+ protected void setObjectParameter(final ThreadContext context,
375
+ final Connection connection, final PreparedStatement statement,
376
+ final int index, IRubyObject value,
377
+ final IRubyObject attribute, final int type) throws SQLException {
378
+
379
+ final String columnType = attributeSQLType(context, attribute).asJavaString();
380
+ Double[] pointValues;
381
+
382
+ switch ( columnType ) {
383
+ case "bit":
384
+ case "bit_varying":
385
+ // value should be a ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Bit::Data
386
+ setPGobjectParameter(statement, index, value.toString(), "bit");
387
+ break;
388
+
389
+ case "box":
390
+ pointValues = parseDoubles(value);
391
+ statement.setObject(index, new PGbox(pointValues[0], pointValues[1], pointValues[2], pointValues[3]));
392
+ break;
393
+
394
+ case "circle":
395
+ pointValues = parseDoubles(value);
396
+ statement.setObject(index, new PGcircle(pointValues[0], pointValues[1], pointValues[2]));
397
+ break;
398
+
399
+ case "cidr":
400
+ case "citext":
401
+ case "hstore":
402
+ case "inet":
403
+ case "ltree":
404
+ case "macaddr":
405
+ case "tsvector":
406
+ setPGobjectParameter(statement, index, value, columnType);
407
+ break;
408
+
409
+ case "enum":
410
+ statement.setObject(index, value.toString(), Types.OTHER);
411
+ break;
412
+
413
+ case "interval":
414
+ statement.setObject(index, new PGInterval(value.toString()));
415
+ break;
416
+
417
+ case "json":
418
+ case "jsonb":
419
+ setJsonParameter(context, statement, index, value, columnType);
420
+ break;
421
+
422
+ case "line":
423
+ pointValues = parseDoubles(value);
424
+ if ( pointValues.length == 3 ) {
425
+ statement.setObject(index, new PGline(pointValues[0], pointValues[1], pointValues[2]));
426
+ } else {
427
+ statement.setObject(index, new PGline(pointValues[0], pointValues[1], pointValues[2], pointValues[3]));
428
+ }
429
+ break;
430
+
431
+ case "money":
432
+ if (value instanceof RubyBigDecimal) {
433
+ statement.setBigDecimal(index, ((RubyBigDecimal) value).getValue());
434
+ } else {
435
+ setPGobjectParameter(statement, index, value, columnType);
436
+ }
437
+ break;
438
+
439
+ case "lseg":
440
+ pointValues = parseDoubles(value);
441
+ statement.setObject(index, new PGlseg(pointValues[0], pointValues[1], pointValues[2], pointValues[3]));
442
+ break;
443
+
444
+ case "path":
445
+ // If the value starts with "[" it is open, otherwise postgres treats it as a closed path
446
+ statement.setObject(index, new PGpath((PGpoint[]) convertToPoints(parseDoubles(value)), value.toString().startsWith("[")));
447
+ break;
448
+
449
+ case "point":
450
+ pointValues = parseDoubles(value);
451
+ statement.setObject(index, new PGpoint(pointValues[0], pointValues[1]));
452
+ break;
453
+
454
+ case "polygon":
455
+ statement.setObject(index, new PGpolygon((PGpoint[]) convertToPoints(parseDoubles(value))));
456
+ break;
457
+
458
+ case "uuid":
459
+ setUUIDParameter(statement, index, value);
460
+ break;
461
+
462
+ default:
463
+ if (columnType.endsWith("range")) {
464
+ setRangeParameter(context, statement, index, value, columnType);
465
+ } else {
466
+ setPGobjectParameter(statement, index, value, columnType);
467
+ }
468
+ }
469
+ }
470
+
471
+ // The tests won't start if this returns PGpoint[]
472
+ // it fails with a runtime error: "NativeException: java.lang.reflect.InvocationTargetException: [Lorg/postgresql/geometric/PGpoint"
473
+ private Object[] convertToPoints(Double[] values) throws SQLException {
474
+ PGpoint[] points = new PGpoint[values.length / 2];
475
+
476
+ for ( int i = 0; i < values.length; i += 2 ) {
477
+ points[i / 2] = new PGpoint(values[i], values[i + 1]);
478
+ }
479
+
480
+ return points;
481
+ }
482
+
483
+ private static Double[] parseDoubles(IRubyObject value) {
484
+ Matcher matches = doubleValuePattern.matcher(value.toString());
485
+ ArrayList<Double> doubles = new ArrayList<Double>(4); // Paths and polygons may be larger but this covers points/circles/boxes/line segments
486
+
487
+ while ( matches.find() ) {
488
+ doubles.add(Double.parseDouble(matches.group()));
489
+ }
490
+
491
+ return doubles.toArray(new Double[doubles.size()]);
492
+ }
493
+
494
+ private void setJsonParameter(final ThreadContext context,
495
+ final PreparedStatement statement, final int index,
496
+ final IRubyObject value, final String columnType) throws SQLException {
497
+
498
+ final PGobject pgJson = new PGobject();
499
+ pgJson.setType(columnType);
500
+ pgJson.setValue(value.toString());
501
+ statement.setObject(index, pgJson);
502
+ }
503
+
504
+ private void setPGobjectParameter(final PreparedStatement statement, final int index,
505
+ final Object value, final String columnType) throws SQLException {
506
+
507
+ final PGobject param = new PGobject();
508
+ param.setType(columnType);
509
+ param.setValue(value.toString());
510
+ statement.setObject(index, param);
511
+ }
512
+
513
+ private void setRangeParameter(final ThreadContext context,
514
+ final PreparedStatement statement, final int index,
515
+ final IRubyObject value, final String columnType) throws SQLException {
516
+
517
+ final String rangeValue = value.toString();
518
+ final Object pgRange;
519
+
520
+ switch ( columnType ) {
521
+ case "daterange":
522
+ pgRange = new DateRangeType(rangeValue);
523
+ break;
524
+ case "tsrange":
525
+ pgRange = new TsRangeType(rangeValue);
526
+ break;
527
+ case "tstzrange":
528
+ pgRange = new TstzRangeType(rangeValue);
529
+ break;
530
+ case "int4range":
531
+ pgRange = new Int4RangeType(rangeValue);
532
+ break;
533
+ case "int8range":
534
+ pgRange = new Int8RangeType(rangeValue);
535
+ break;
536
+ default:
537
+ pgRange = new NumRangeType(rangeValue);
538
+ }
539
+
540
+ statement.setObject(index, pgRange);
541
+ }
542
+
543
+ @Override
544
+ protected void setStringParameter(final ThreadContext context,
545
+ final Connection connection, final PreparedStatement statement,
546
+ final int index, final IRubyObject value,
547
+ final IRubyObject attribute, final int type) throws SQLException {
548
+
549
+ if ( attributeSQLType(context, attribute) == context.nil ) {
550
+ /*
551
+ We have to check for a uuid here because in some cases
552
+ (for example, when doing "exists?" checks, or with legacy binds)
553
+ ActiveRecord doesn't send us the actual type of the attribute
554
+ and Postgres won't compare a uuid column with a string
555
+ */
556
+ final String uuid = value.toString();
557
+ int length = uuid.length();
558
+
559
+ // Checking the length so we don't have the overhead of the regex unless it "looks" like a UUID
560
+ if (length >= 32 && length < 40 && uuidPattern.matcher(uuid).matches()) {
561
+ setUUIDParameter(statement, index, uuid);
562
+ return;
563
+ }
564
+ }
565
+
566
+ super.setStringParameter(context, connection, statement, index, value, attribute, type);
567
+ }
568
+
569
+
570
+ private void setUUIDParameter(final PreparedStatement statement,
571
+ final int index, final IRubyObject value) throws SQLException {
572
+ setUUIDParameter(statement, index, value.toString());
573
+ }
574
+
575
+ private void setUUIDParameter(final PreparedStatement statement, final int index, String uuid) throws SQLException {
576
+
577
+ if (uuid.length() != 36) { // Assume its a non-standard format
578
+
579
+ /*
580
+ * Postgres supports a bunch of formats that aren't valid uuids, so we do too...
581
+ * A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11
582
+ * {a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}
583
+ * a0eebc999c0b4ef8bb6d6bb9bd380a11
584
+ * a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11
585
+ * {a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}
586
+ */
587
+
588
+ if (uuid.length() == 38 && uuid.charAt(0) == '{') {
589
+
590
+ // We got the {a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11} version so save some processing
591
+ uuid = uuid.substring(1, 37);
592
+
593
+ } else {
594
+
595
+ int valueIndex = 0;
596
+ int newUUIDIndex = 0;
597
+ char[] newUUIDChars = new char[36];
598
+
599
+ if (uuid.charAt(0) == '{') {
600
+ // Skip '{'
601
+ valueIndex++;
602
+ }
603
+
604
+ while (newUUIDIndex < 36) { // If we don't hit this before running out of characters it is an invalid UUID
605
+
606
+ char currentChar = uuid.charAt(valueIndex);
607
+
608
+ // Copy anything other than dashes
609
+ if (currentChar != '-') {
610
+ newUUIDChars[newUUIDIndex] = currentChar;
611
+ newUUIDIndex++;
612
+
613
+ // Insert dashes where appropriate
614
+ if(newUUIDIndex == 8 || newUUIDIndex == 13 || newUUIDIndex == 18 || newUUIDIndex == 23) {
615
+ newUUIDChars[newUUIDIndex] = '-';
616
+ newUUIDIndex++;
617
+ }
618
+ }
619
+
620
+ valueIndex++;
621
+ }
622
+
623
+ uuid = new String(newUUIDChars);
624
+
625
+ }
626
+ }
627
+
628
+ statement.setObject(index, UUID.fromString(uuid));
629
+ }
630
+
631
+ @Override
632
+ protected Integer jdbcTypeFor(final String type) {
633
+
634
+ Integer typeValue = POSTGRES_JDBC_TYPE_FOR.get(type);
635
+
636
+ if ( typeValue != null ) {
637
+ return typeValue;
638
+ }
639
+
640
+ return super.jdbcTypeFor(type);
641
+ }
642
+
643
+ @Override
644
+ protected TableName extractTableName(
645
+ final Connection connection, String catalog, String schema,
646
+ final String tableName) throws IllegalArgumentException, SQLException {
647
+ // The postgres JDBC driver will default to searching every schema if no
648
+ // schema search path is given. Default to the 'public' schema instead:
649
+ if ( schema == null ) schema = "public";
650
+ return super.extractTableName(connection, catalog, schema, tableName);
651
+ }
652
+
653
+ /**
654
+ * Determines if this field is multiple bits or a single bit (or t/f),
655
+ * if there are multiple bits they are turned into a string, if there
656
+ * is only one it is assumed to be a boolean value
657
+ * @param context current thread context
658
+ * @param resultSet the jdbc result set to pull the value from
659
+ * @param index the index of the column to convert
660
+ * @return RubyNil if NULL or RubyString of bits or RubyBoolean for a boolean value
661
+ * @throws SQLException if it failes to retrieve the value from the result set
662
+ */
663
+ @Override
664
+ protected IRubyObject bitToRuby(ThreadContext context, Ruby runtime, ResultSet resultSet, int index) throws SQLException {
665
+ String bits = resultSet.getString(index);
666
+
667
+ if (bits == null) return context.nil;
668
+ if (bits.length() > 1) return RubyString.newUnicodeString(context.runtime, bits);
669
+
670
+ // We assume it is a boolean value if it doesn't have a length
671
+ return booleanToRuby(context, runtime, resultSet, index);
672
+ }
673
+
674
+ /**
675
+ * Converts a JDBC date object to a Ruby date by parsing the string so we can handle edge cases
676
+ * @param context current thread context
677
+ * @param resultSet the jdbc result set to pull the value from
678
+ * @param index the index of the column to convert
679
+ * @return RubyNil if NULL or RubyDate if there is a value
680
+ * @throws SQLException if it failes to retrieve the value from the result set
681
+ */
682
+ @Override
683
+ protected IRubyObject dateToRuby(ThreadContext context, Ruby runtime, ResultSet resultSet, int index) throws SQLException {
684
+ // NOTE: PostgreSQL adapter under MRI using pg gem returns UTC-d Date/Time values
685
+ final String value = resultSet.getString(index);
686
+
687
+ return value == null ? context.nil : DateTimeUtils.parseDate(context, value, getDefaultTimeZone(context));
688
+ }
689
+
690
+
691
+ /**
692
+ * Detects PG specific types and converts them to their Ruby equivalents
693
+ * @param context current thread context
694
+ * @param resultSet the jdbc result set to pull the value from
695
+ * @param index the index of the column to convert
696
+ * @return RubyNil if NULL or RubyHash/RubyString/RubyObject
697
+ * @throws SQLException if it failes to retrieve the value from the result set
698
+ */
699
+ @Override
700
+ protected IRubyObject objectToRuby(ThreadContext context, Ruby runtime, ResultSet resultSet, int index) throws SQLException {
701
+ final Object object = resultSet.getObject(index);
702
+
703
+ if (object == null) return context.nil;
704
+
705
+ final Class<?> objectClass = object.getClass();
706
+
707
+ if (objectClass == UUID.class) return runtime.newString(object.toString());
708
+
709
+ if (object instanceof PGobject) {
710
+ if (objectClass == PGInterval.class) return runtime.newString(formatInterval(object));
711
+
712
+ if (objectClass == PGbox.class || objectClass == PGcircle.class ||
713
+ objectClass == PGline.class || objectClass == PGlseg.class ||
714
+ objectClass == PGpath.class || objectClass == PGpoint.class ||
715
+ objectClass == PGpolygon.class ) {
716
+ // AR 5.0+ expects that points don't have the '.0' if it is an integer
717
+ return runtime.newString(pointCleanerPattern.matcher(object.toString()).replaceAll(""));
718
+ }
719
+
720
+ // PG 9.2 JSON type will be returned here as well
721
+ return runtime.newString(object.toString());
722
+ }
723
+
724
+ if (object instanceof Map) { // hstore
725
+ // by default we avoid double parsing by driver and then column :
726
+ final RubyHash rubyObject = RubyHash.newHash(context.runtime);
727
+ rubyObject.putAll((Map) object); // converts keys/values to ruby
728
+ return rubyObject;
729
+ }
730
+
731
+ return JavaUtil.convertJavaToRuby(runtime, object);
732
+ }
733
+
734
+ /**
735
+ * Override character stream handling to be read as bytes
736
+ * @param context current thread context
737
+ * @param runtime the Ruby runtime
738
+ * @param resultSet the jdbc result set to pull the value from
739
+ * @param index the index of the column to convert
740
+ * @return RubyNil if NULL or RubyString if there is a value
741
+ * @throws SQLException if it failes to retrieve the value from the result set
742
+ */
743
+ @Override
744
+ protected IRubyObject readerToRuby(ThreadContext context, Ruby runtime,
745
+ ResultSet resultSet, int index) throws SQLException {
746
+ return stringToRuby(context, runtime, resultSet, index);
747
+ }
748
+
749
+ /**
750
+ * Converts a string column into a Ruby string by pulling the raw bytes from the column and
751
+ * turning them into a string using the default encoding
752
+ * @param context current thread context
753
+ * @param runtime the Ruby runtime
754
+ * @param resultSet the jdbc result set to pull the value from
755
+ * @param index the index of the column to convert
756
+ * @return RubyNil if NULL or RubyString if there is a value
757
+ * @throws SQLException if it failes to retrieve the value from the result set
758
+ */
759
+ @Override
760
+ protected IRubyObject stringToRuby(ThreadContext context, Ruby runtime, ResultSet resultSet, int index) throws SQLException {
761
+ final byte[] value = resultSet.getBytes(index);
762
+
763
+ return value == null ? context.nil : StringHelper.newDefaultInternalString(context.runtime, value);
764
+ }
765
+
766
+ /**
767
+ * Converts a JDBC time object to a Ruby time by parsing it as a string
768
+ * @param context current thread context
769
+ * @param runtime the Ruby runtime
770
+ * @param resultSet the jdbc result set to pull the value from
771
+ * @param column the index of the column to convert
772
+ * @return RubyNil if NULL or RubyDate if there is a value
773
+ * @throws SQLException if it failes to retrieve the value from the result set
774
+ */
775
+ @Override
776
+ protected IRubyObject timeToRuby(ThreadContext context, Ruby runtime, ResultSet resultSet, int column) throws SQLException {
777
+ final String value = resultSet.getString(column); // Using resultSet.getTimestamp(column) only gets .999 (3) precision
778
+
779
+ return value == null ? context.nil : DateTimeUtils.parseTime(context, value, getDefaultTimeZone(context));
780
+ }
781
+
782
+ /**
783
+ * Converts a JDBC timestamp object to a Ruby time by parsing it as a string
784
+ * @param context current thread context
785
+ * @param runtime the Ruby runtime
786
+ * @param resultSet the jdbc result set to pull the value from
787
+ * @param column the index of the column to convert
788
+ * @return RubyNil if NULL or RubyDate if there is a value
789
+ * @throws SQLException if it failes to retrieve the value from the result set
790
+ */
791
+ @Override
792
+ protected IRubyObject timestampToRuby(ThreadContext context, Ruby runtime, ResultSet resultSet,
793
+ int column) throws SQLException {
794
+ // NOTE: using Timestamp we loose information such as BC :
795
+ // Timestamp: '0001-12-31 22:59:59.0' String: '0001-12-31 22:59:59 BC'
796
+ final String value = resultSet.getString(column);
797
+
798
+ if (value == null) return context.nil;
799
+
800
+ final int len = value.length();
801
+ if (len < 10 && value.charAt(len - 1) == 'y') { // infinity / -infinity
802
+ IRubyObject infinity = parseInfinity(context.runtime, value);
803
+
804
+ if (infinity != null) return infinity;
805
+ }
806
+
807
+ // handles '0001-01-01 23:59:59 BC'
808
+ return DateTimeUtils.parseDateTime(context, value, getDefaultTimeZone(context));
809
+ }
810
+
811
+ private IRubyObject parseInfinity(final Ruby runtime, final String value) {
812
+ if ("infinity".equals(value)) return RubyFloat.newFloat(runtime, RubyFloat.INFINITY);
813
+ if ("-infinity".equals(value)) return RubyFloat.newFloat(runtime, -RubyFloat.INFINITY);
814
+
815
+ return null;
816
+ }
817
+
818
+ // NOTE: do not use PG classes in the API so that loading is delayed !
819
+ private static String formatInterval(final Object object) {
820
+ final PGInterval interval = (PGInterval) object;
821
+ final StringBuilder str = new StringBuilder(32);
822
+
823
+ final int years = interval.getYears();
824
+ if (years != 0) str.append(years).append(" years ");
825
+
826
+ final int months = interval.getMonths();
827
+ if (months != 0) str.append(months).append(" months ");
828
+
829
+ final int days = interval.getDays();
830
+ if (days != 0) str.append(days).append(" days ");
831
+
832
+ final int hours = interval.getHours();
833
+ final int mins = interval.getMinutes();
834
+ final int secs = (int) interval.getSeconds();
835
+ if (hours != 0 || mins != 0 || secs != 0) { // xx:yy:zz if not all 00
836
+ if (hours < 10) str.append('0');
837
+
838
+ str.append(hours).append(':');
839
+
840
+ if (mins < 10) str.append('0');
841
+
842
+ str.append(mins).append(':');
843
+
844
+ if (secs < 10) str.append('0');
845
+
846
+ str.append(secs);
847
+
848
+ } else if (str.length() > 1) {
849
+ str.deleteCharAt(str.length() - 1); // " " at the end
850
+ }
851
+
852
+ return str.toString();
853
+ }
854
+
855
+ // NOTE: without these custom registered Postgre (driver) types
856
+ // ... we can not set range parameters in prepared statements !
857
+
858
+ public static class DateRangeType extends PGobject {
859
+
860
+ private static final long serialVersionUID = -5378414736244196691L;
861
+
862
+ public DateRangeType() {
863
+ setType("daterange");
864
+ }
865
+
866
+ public DateRangeType(final String value) throws SQLException {
867
+ this();
868
+ setValue(value);
869
+ }
870
+
871
+ }
872
+
873
+ public static class TsRangeType extends PGobject {
874
+
875
+ private static final long serialVersionUID = -2991390995527988409L;
876
+
877
+ public TsRangeType() {
878
+ setType("tsrange");
879
+ }
880
+
881
+ public TsRangeType(final String value) throws SQLException {
882
+ this();
883
+ setValue(value);
884
+ }
885
+
886
+ }
887
+
888
+ public static class TstzRangeType extends PGobject {
889
+
890
+ private static final long serialVersionUID = 6492535255861743334L;
891
+
892
+ public TstzRangeType() {
893
+ setType("tstzrange");
894
+ }
895
+
896
+ public TstzRangeType(final String value) throws SQLException {
897
+ this();
898
+ setValue(value);
899
+ }
900
+
901
+ }
902
+
903
+ public static class Int4RangeType extends PGobject {
904
+
905
+ private static final long serialVersionUID = 4490562039665289763L;
906
+
907
+ public Int4RangeType() {
908
+ setType("int4range");
909
+ }
910
+
911
+ public Int4RangeType(final String value) throws SQLException {
912
+ this();
913
+ setValue(value);
914
+ }
915
+
916
+ }
917
+
918
+ public static class Int8RangeType extends PGobject {
919
+
920
+ private static final long serialVersionUID = -1458706215346897102L;
921
+
922
+ public Int8RangeType() {
923
+ setType("int8range");
924
+ }
925
+
926
+ public Int8RangeType(final String value) throws SQLException {
927
+ this();
928
+ setValue(value);
929
+ }
930
+
931
+ }
932
+
933
+ public static class NumRangeType extends PGobject {
934
+
935
+ private static final long serialVersionUID = 5892509252900362510L;
936
+
937
+ public NumRangeType() {
938
+ setType("numrange");
939
+ }
940
+
941
+ public NumRangeType(final String value) throws SQLException {
942
+ this();
943
+ setValue(value);
944
+ }
945
+
946
+ }
947
+
948
+ }