activerecord-jdbc-alt-adapter 50.3.0-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +35 -0
- data/.travis.yml +100 -0
- data/.yardopts +4 -0
- data/CONTRIBUTING.md +50 -0
- data/Gemfile +92 -0
- data/History.md +1191 -0
- data/LICENSE.txt +26 -0
- data/README.md +240 -0
- data/RUNNING_TESTS.md +127 -0
- data/Rakefile +336 -0
- data/Rakefile.jdbc +20 -0
- data/activerecord-jdbc-adapter.gemspec +55 -0
- data/activerecord-jdbc-alt-adapter.gemspec +56 -0
- data/lib/active_record/connection_adapters/as400_adapter.rb +2 -0
- data/lib/active_record/connection_adapters/db2_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/derby_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/firebird_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/h2_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/hsqldb_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/informix_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/jdbc_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/jndi_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/mariadb_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/mssql_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +1 -0
- data/lib/activerecord-jdbc-adapter.rb +1 -0
- data/lib/arel/visitors/compat.rb +60 -0
- data/lib/arel/visitors/db2.rb +137 -0
- data/lib/arel/visitors/derby.rb +112 -0
- data/lib/arel/visitors/firebird.rb +79 -0
- data/lib/arel/visitors/h2.rb +25 -0
- data/lib/arel/visitors/hsqldb.rb +32 -0
- data/lib/arel/visitors/postgresql_jdbc.rb +6 -0
- data/lib/arel/visitors/sql_server.rb +225 -0
- data/lib/arel/visitors/sql_server/ng42.rb +294 -0
- data/lib/arel/visitors/sqlserver.rb +214 -0
- data/lib/arjdbc.rb +19 -0
- data/lib/arjdbc/abstract/connection_management.rb +35 -0
- data/lib/arjdbc/abstract/core.rb +74 -0
- data/lib/arjdbc/abstract/database_statements.rb +64 -0
- data/lib/arjdbc/abstract/statement_cache.rb +58 -0
- data/lib/arjdbc/abstract/transaction_support.rb +86 -0
- data/lib/arjdbc/db2.rb +4 -0
- data/lib/arjdbc/db2/adapter.rb +789 -0
- data/lib/arjdbc/db2/as400.rb +130 -0
- data/lib/arjdbc/db2/column.rb +167 -0
- data/lib/arjdbc/db2/connection_methods.rb +44 -0
- data/lib/arjdbc/derby.rb +3 -0
- data/lib/arjdbc/derby/active_record_patch.rb +13 -0
- data/lib/arjdbc/derby/adapter.rb +540 -0
- data/lib/arjdbc/derby/connection_methods.rb +20 -0
- data/lib/arjdbc/derby/schema_creation.rb +15 -0
- data/lib/arjdbc/discover.rb +104 -0
- data/lib/arjdbc/firebird.rb +4 -0
- data/lib/arjdbc/firebird/adapter.rb +434 -0
- data/lib/arjdbc/firebird/connection_methods.rb +23 -0
- data/lib/arjdbc/h2.rb +3 -0
- data/lib/arjdbc/h2/adapter.rb +303 -0
- data/lib/arjdbc/h2/connection_methods.rb +27 -0
- data/lib/arjdbc/hsqldb.rb +3 -0
- data/lib/arjdbc/hsqldb/adapter.rb +297 -0
- data/lib/arjdbc/hsqldb/connection_methods.rb +28 -0
- data/lib/arjdbc/hsqldb/explain_support.rb +35 -0
- data/lib/arjdbc/hsqldb/schema_creation.rb +11 -0
- data/lib/arjdbc/informix.rb +5 -0
- data/lib/arjdbc/informix/adapter.rb +162 -0
- data/lib/arjdbc/informix/connection_methods.rb +9 -0
- data/lib/arjdbc/jdbc.rb +59 -0
- data/lib/arjdbc/jdbc/adapter.rb +475 -0
- data/lib/arjdbc/jdbc/adapter_require.rb +46 -0
- data/lib/arjdbc/jdbc/base_ext.rb +15 -0
- data/lib/arjdbc/jdbc/callbacks.rb +53 -0
- data/lib/arjdbc/jdbc/column.rb +97 -0
- data/lib/arjdbc/jdbc/connection.rb +14 -0
- data/lib/arjdbc/jdbc/connection_methods.rb +37 -0
- data/lib/arjdbc/jdbc/error.rb +65 -0
- data/lib/arjdbc/jdbc/extension.rb +59 -0
- data/lib/arjdbc/jdbc/java.rb +13 -0
- data/lib/arjdbc/jdbc/railtie.rb +2 -0
- data/lib/arjdbc/jdbc/rake_tasks.rb +3 -0
- data/lib/arjdbc/jdbc/serialized_attributes_helper.rb +3 -0
- data/lib/arjdbc/jdbc/type_cast.rb +166 -0
- data/lib/arjdbc/jdbc/type_converter.rb +142 -0
- data/lib/arjdbc/mssql.rb +7 -0
- data/lib/arjdbc/mssql/adapter.rb +384 -0
- data/lib/arjdbc/mssql/column.rb +29 -0
- data/lib/arjdbc/mssql/connection_methods.rb +79 -0
- data/lib/arjdbc/mssql/database_statements.rb +134 -0
- data/lib/arjdbc/mssql/errors.rb +6 -0
- data/lib/arjdbc/mssql/explain_support.rb +129 -0
- data/lib/arjdbc/mssql/extensions.rb +36 -0
- data/lib/arjdbc/mssql/limit_helpers.rb +231 -0
- data/lib/arjdbc/mssql/lock_methods.rb +77 -0
- data/lib/arjdbc/mssql/old_adapter.rb +804 -0
- data/lib/arjdbc/mssql/old_column.rb +200 -0
- data/lib/arjdbc/mssql/quoting.rb +101 -0
- data/lib/arjdbc/mssql/schema_creation.rb +31 -0
- data/lib/arjdbc/mssql/schema_definitions.rb +74 -0
- data/lib/arjdbc/mssql/schema_statements.rb +329 -0
- data/lib/arjdbc/mssql/transaction.rb +69 -0
- data/lib/arjdbc/mssql/types.rb +52 -0
- data/lib/arjdbc/mssql/types/binary_types.rb +33 -0
- data/lib/arjdbc/mssql/types/date_and_time_types.rb +134 -0
- data/lib/arjdbc/mssql/types/deprecated_types.rb +40 -0
- data/lib/arjdbc/mssql/types/numeric_types.rb +71 -0
- data/lib/arjdbc/mssql/types/string_types.rb +56 -0
- data/lib/arjdbc/mssql/utils.rb +66 -0
- data/lib/arjdbc/mysql.rb +3 -0
- data/lib/arjdbc/mysql/adapter.rb +140 -0
- data/lib/arjdbc/mysql/connection_methods.rb +166 -0
- data/lib/arjdbc/oracle/adapter.rb +863 -0
- data/lib/arjdbc/postgresql.rb +3 -0
- data/lib/arjdbc/postgresql/adapter.rb +687 -0
- data/lib/arjdbc/postgresql/base/array_decoder.rb +26 -0
- data/lib/arjdbc/postgresql/base/array_encoder.rb +25 -0
- data/lib/arjdbc/postgresql/base/array_parser.rb +95 -0
- data/lib/arjdbc/postgresql/base/pgconn.rb +11 -0
- data/lib/arjdbc/postgresql/column.rb +51 -0
- data/lib/arjdbc/postgresql/connection_methods.rb +67 -0
- data/lib/arjdbc/postgresql/name.rb +24 -0
- data/lib/arjdbc/postgresql/oid_types.rb +266 -0
- data/lib/arjdbc/railtie.rb +11 -0
- data/lib/arjdbc/sqlite3.rb +3 -0
- data/lib/arjdbc/sqlite3/adapter.rb +678 -0
- data/lib/arjdbc/sqlite3/connection_methods.rb +59 -0
- data/lib/arjdbc/sybase.rb +2 -0
- data/lib/arjdbc/sybase/adapter.rb +47 -0
- data/lib/arjdbc/tasks.rb +13 -0
- data/lib/arjdbc/tasks/database_tasks.rb +31 -0
- data/lib/arjdbc/tasks/databases.rake +48 -0
- data/lib/arjdbc/tasks/db2_database_tasks.rb +104 -0
- data/lib/arjdbc/tasks/derby_database_tasks.rb +95 -0
- data/lib/arjdbc/tasks/h2_database_tasks.rb +31 -0
- data/lib/arjdbc/tasks/hsqldb_database_tasks.rb +70 -0
- data/lib/arjdbc/tasks/jdbc_database_tasks.rb +169 -0
- data/lib/arjdbc/tasks/mssql_database_tasks.rb +46 -0
- data/lib/arjdbc/util/quoted_cache.rb +60 -0
- data/lib/arjdbc/util/serialized_attributes.rb +98 -0
- data/lib/arjdbc/util/table_copier.rb +110 -0
- data/lib/arjdbc/version.rb +3 -0
- data/lib/generators/jdbc/USAGE +9 -0
- data/lib/generators/jdbc/jdbc_generator.rb +17 -0
- data/lib/jdbc_adapter.rb +2 -0
- data/lib/jdbc_adapter/rake_tasks.rb +4 -0
- data/lib/jdbc_adapter/version.rb +4 -0
- data/pom.xml +114 -0
- data/rails_generators/jdbc_generator.rb +15 -0
- data/rails_generators/templates/config/initializers/jdbc.rb +10 -0
- data/rails_generators/templates/lib/tasks/jdbc.rake +11 -0
- data/rakelib/01-tomcat.rake +51 -0
- data/rakelib/02-test.rake +132 -0
- data/rakelib/bundler_ext.rb +11 -0
- data/rakelib/db.rake +75 -0
- data/rakelib/rails.rake +223 -0
- data/src/java/arjdbc/ArJdbcModule.java +276 -0
- data/src/java/arjdbc/db2/DB2Module.java +76 -0
- data/src/java/arjdbc/db2/DB2RubyJdbcConnection.java +126 -0
- data/src/java/arjdbc/derby/DerbyModule.java +178 -0
- data/src/java/arjdbc/derby/DerbyRubyJdbcConnection.java +152 -0
- data/src/java/arjdbc/firebird/FirebirdRubyJdbcConnection.java +174 -0
- data/src/java/arjdbc/h2/H2Module.java +50 -0
- data/src/java/arjdbc/h2/H2RubyJdbcConnection.java +85 -0
- data/src/java/arjdbc/hsqldb/HSQLDBModule.java +73 -0
- data/src/java/arjdbc/informix/InformixRubyJdbcConnection.java +75 -0
- data/src/java/arjdbc/jdbc/AdapterJavaService.java +43 -0
- data/src/java/arjdbc/jdbc/Callable.java +44 -0
- data/src/java/arjdbc/jdbc/ConnectionFactory.java +45 -0
- data/src/java/arjdbc/jdbc/DataSourceConnectionFactory.java +156 -0
- data/src/java/arjdbc/jdbc/DriverConnectionFactory.java +63 -0
- data/src/java/arjdbc/jdbc/DriverWrapper.java +119 -0
- data/src/java/arjdbc/jdbc/JdbcResult.java +130 -0
- data/src/java/arjdbc/jdbc/RubyConnectionFactory.java +61 -0
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +3979 -0
- data/src/java/arjdbc/mssql/MSSQLModule.java +90 -0
- data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +508 -0
- data/src/java/arjdbc/mysql/MySQLModule.java +152 -0
- data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +294 -0
- data/src/java/arjdbc/oracle/OracleModule.java +80 -0
- data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +455 -0
- data/src/java/arjdbc/postgresql/ByteaUtils.java +157 -0
- data/src/java/arjdbc/postgresql/PgDateTimeUtils.java +52 -0
- data/src/java/arjdbc/postgresql/PostgreSQLModule.java +77 -0
- data/src/java/arjdbc/postgresql/PostgreSQLResult.java +192 -0
- data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +948 -0
- data/src/java/arjdbc/sqlite3/SQLite3Module.java +73 -0
- data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +525 -0
- data/src/java/arjdbc/util/CallResultSet.java +826 -0
- data/src/java/arjdbc/util/DateTimeUtils.java +699 -0
- data/src/java/arjdbc/util/ObjectSupport.java +65 -0
- data/src/java/arjdbc/util/QuotingUtils.java +137 -0
- data/src/java/arjdbc/util/StringCache.java +63 -0
- data/src/java/arjdbc/util/StringHelper.java +145 -0
- 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
|
+
}
|