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