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