activerecord-jdbc-adapter 1.3.0.beta2 → 1.3.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (282) hide show
  1. data/.gitignore +14 -8
  2. data/.travis.yml +40 -31
  3. data/.yardopts +4 -0
  4. data/Appraisals +2 -5
  5. data/CONTRIBUTING.md +46 -0
  6. data/Gemfile +21 -4
  7. data/Gemfile.lock +42 -17
  8. data/{History.txt → History.md} +142 -75
  9. data/README.md +102 -104
  10. data/RUNNING_TESTS.md +76 -0
  11. data/Rakefile.jdbc +20 -0
  12. data/activerecord-jdbc-adapter.gemspec +35 -18
  13. data/gemfiles/rails23.gemfile +4 -3
  14. data/gemfiles/rails23.gemfile.lock +9 -6
  15. data/gemfiles/rails30.gemfile +4 -3
  16. data/gemfiles/rails30.gemfile.lock +9 -6
  17. data/gemfiles/rails31.gemfile +4 -3
  18. data/gemfiles/rails31.gemfile.lock +9 -6
  19. data/gemfiles/rails32.gemfile +4 -3
  20. data/gemfiles/rails32.gemfile.lock +17 -14
  21. data/gemfiles/rails40.gemfile +5 -5
  22. data/gemfiles/rails40.gemfile.lock +17 -69
  23. data/lib/active_record/connection_adapters/firebird_adapter.rb +1 -0
  24. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +1 -0
  25. data/lib/arel/visitors/compat.rb +22 -3
  26. data/lib/arel/visitors/db2.rb +8 -4
  27. data/lib/arel/visitors/derby.rb +14 -13
  28. data/lib/arel/visitors/firebird.rb +5 -4
  29. data/lib/arel/visitors/hsqldb.rb +11 -9
  30. data/lib/arel/visitors/sql_server.rb +89 -61
  31. data/lib/arjdbc.rb +1 -1
  32. data/lib/arjdbc/db2/adapter.rb +181 -212
  33. data/lib/arjdbc/db2/as400.rb +31 -18
  34. data/lib/arjdbc/db2/column.rb +167 -0
  35. data/lib/arjdbc/db2/connection_methods.rb +2 -0
  36. data/lib/arjdbc/derby/adapter.rb +206 -107
  37. data/lib/arjdbc/derby/connection_methods.rb +4 -9
  38. data/lib/arjdbc/firebird.rb +1 -0
  39. data/lib/arjdbc/firebird/adapter.rb +202 -64
  40. data/lib/arjdbc/firebird/connection_methods.rb +20 -0
  41. data/lib/arjdbc/h2/adapter.rb +56 -36
  42. data/lib/arjdbc/hsqldb/adapter.rb +99 -68
  43. data/lib/arjdbc/jdbc/adapter.rb +474 -265
  44. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  45. data/lib/arjdbc/jdbc/adapter_require.rb +8 -7
  46. data/lib/arjdbc/jdbc/arel_support.rb +132 -0
  47. data/lib/arjdbc/jdbc/base_ext.rb +8 -7
  48. data/lib/arjdbc/jdbc/callbacks.rb +16 -10
  49. data/lib/arjdbc/jdbc/column.rb +25 -3
  50. data/lib/arjdbc/jdbc/connection.rb +28 -55
  51. data/lib/arjdbc/jdbc/extension.rb +14 -14
  52. data/lib/arjdbc/jdbc/java.rb +6 -3
  53. data/lib/arjdbc/jdbc/jdbc.rake +1 -1
  54. data/lib/arjdbc/jdbc/quoted_primary_key.rb +2 -2
  55. data/lib/arjdbc/jdbc/rake_tasks.rb +1 -1
  56. data/lib/arjdbc/jdbc/type_converter.rb +5 -2
  57. data/lib/arjdbc/mssql/adapter.rb +160 -280
  58. data/lib/arjdbc/mssql/column.rb +182 -0
  59. data/lib/arjdbc/mssql/connection_methods.rb +37 -4
  60. data/lib/arjdbc/mssql/explain_support.rb +13 -21
  61. data/lib/arjdbc/mssql/limit_helpers.rb +79 -42
  62. data/lib/arjdbc/mssql/lock_methods.rb +77 -0
  63. data/lib/arjdbc/mssql/utils.rb +11 -11
  64. data/lib/arjdbc/mysql/adapter.rb +165 -247
  65. data/lib/arjdbc/mysql/column.rb +123 -0
  66. data/lib/arjdbc/mysql/connection_methods.rb +3 -6
  67. data/lib/arjdbc/oracle/adapter.rb +282 -288
  68. data/lib/arjdbc/oracle/column.rb +122 -0
  69. data/lib/arjdbc/oracle/connection_methods.rb +3 -0
  70. data/lib/arjdbc/postgresql/adapter.rb +336 -574
  71. data/lib/arjdbc/postgresql/column.rb +458 -0
  72. data/lib/arjdbc/postgresql/connection_methods.rb +1 -2
  73. data/lib/arjdbc/postgresql/schema_creation.rb +38 -0
  74. data/lib/arjdbc/sqlite3/adapter.rb +189 -145
  75. data/lib/arjdbc/sqlite3/explain_support.rb +1 -1
  76. data/lib/arjdbc/tasks/oracle/enhanced_structure_dump.rb +8 -8
  77. data/lib/arjdbc/util/quoted_cache.rb +60 -0
  78. data/lib/arjdbc/util/table_copier.rb +110 -0
  79. data/lib/arjdbc/version.rb +6 -7
  80. data/pom.xml +56 -2
  81. data/rakelib/02-test.rake +72 -83
  82. data/rakelib/db.rake +29 -17
  83. data/src/java/arjdbc/ArJdbcModule.java +21 -18
  84. data/src/java/arjdbc/db2/DB2RubyJdbcConnection.java +84 -12
  85. data/src/java/arjdbc/derby/DerbyModule.java +140 -143
  86. data/src/java/arjdbc/derby/DerbyRubyJdbcConnection.java +58 -7
  87. data/src/java/arjdbc/h2/H2Module.java +43 -0
  88. data/src/java/arjdbc/informix/InformixRubyJdbcConnection.java +7 -6
  89. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +1223 -648
  90. data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +24 -23
  91. data/src/java/arjdbc/mysql/MySQLModule.java +33 -32
  92. data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +147 -30
  93. data/src/java/arjdbc/oracle/OracleModule.java +13 -13
  94. data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +114 -6
  95. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +166 -36
  96. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +101 -19
  97. data/src/java/arjdbc/util/QuotingUtils.java +19 -19
  98. metadata +240 -394
  99. data/bench/bench_attributes.rb +0 -13
  100. data/bench/bench_attributes_new.rb +0 -14
  101. data/bench/bench_create.rb +0 -12
  102. data/bench/bench_find_all.rb +0 -12
  103. data/bench/bench_find_all_mt.rb +0 -25
  104. data/bench/bench_model.rb +0 -85
  105. data/bench/bench_new.rb +0 -12
  106. data/bench/bench_new_valid.rb +0 -12
  107. data/bench/bench_valid.rb +0 -13
  108. data/lib/arel/engines/sql/compilers/db2_compiler.rb +0 -9
  109. data/lib/arel/engines/sql/compilers/derby_compiler.rb +0 -6
  110. data/lib/arel/engines/sql/compilers/h2_compiler.rb +0 -6
  111. data/lib/arel/engines/sql/compilers/hsqldb_compiler.rb +0 -15
  112. data/lib/arel/engines/sql/compilers/jdbc_compiler.rb +0 -6
  113. data/lib/arel/engines/sql/compilers/mssql_compiler.rb +0 -46
  114. data/lib/arjdbc/jdbc/missing_functionality_helper.rb +0 -98
  115. data/lib/arjdbc/mssql/lock_helpers.rb +0 -76
  116. data/lib/arjdbc/mssql/tsql_methods.rb +0 -58
  117. data/lib/arjdbc/postgresql/column_cast.rb +0 -134
  118. data/test/activerecord/connections/native_jdbc_mysql/connection.rb +0 -25
  119. data/test/activerecord/jall.sh +0 -7
  120. data/test/activerecord/jtest.sh +0 -3
  121. data/test/assets/flowers.jpg +0 -0
  122. data/test/binary.rb +0 -67
  123. data/test/db/db2.rb +0 -43
  124. data/test/db/db2/binary_test.rb +0 -6
  125. data/test/db/db2/has_many_through_test.rb +0 -6
  126. data/test/db/db2/rake_test.rb +0 -82
  127. data/test/db/db2/rake_test_data.sql +0 -35
  128. data/test/db/db2/reset_column_information_test.rb +0 -5
  129. data/test/db/db2/serialize_test.rb +0 -6
  130. data/test/db/db2/simple_test.rb +0 -81
  131. data/test/db/db2/test_helper.rb +0 -6
  132. data/test/db/db2/unit_test.rb +0 -73
  133. data/test/db/derby.rb +0 -12
  134. data/test/db/derby/binary_test.rb +0 -6
  135. data/test/db/derby/migration_test.rb +0 -74
  136. data/test/db/derby/rake_test.rb +0 -96
  137. data/test/db/derby/reset_column_information_test.rb +0 -6
  138. data/test/db/derby/row_locking_test.rb +0 -20
  139. data/test/db/derby/schema_dump_test.rb +0 -5
  140. data/test/db/derby/serialize_test.rb +0 -6
  141. data/test/db/derby/simple_test.rb +0 -173
  142. data/test/db/derby/test_helper.rb +0 -6
  143. data/test/db/derby/unit_test.rb +0 -32
  144. data/test/db/derby/xml_column_test.rb +0 -17
  145. data/test/db/h2.rb +0 -11
  146. data/test/db/h2/binary_test.rb +0 -6
  147. data/test/db/h2/change_column_test.rb +0 -68
  148. data/test/db/h2/identity_column_test.rb +0 -35
  149. data/test/db/h2/offset_test.rb +0 -49
  150. data/test/db/h2/rake_test.rb +0 -98
  151. data/test/db/h2/schema_dump_test.rb +0 -29
  152. data/test/db/h2/serialize_test.rb +0 -6
  153. data/test/db/h2/simple_test.rb +0 -56
  154. data/test/db/hsqldb.rb +0 -11
  155. data/test/db/hsqldb/binary_test.rb +0 -6
  156. data/test/db/hsqldb/rake_test.rb +0 -101
  157. data/test/db/hsqldb/schema_dump_test.rb +0 -19
  158. data/test/db/hsqldb/serialize_test.rb +0 -6
  159. data/test/db/hsqldb/simple_test.rb +0 -17
  160. data/test/db/informix.rb +0 -13
  161. data/test/db/jdbc.rb +0 -16
  162. data/test/db/jdbc_derby.rb +0 -14
  163. data/test/db/jdbc_h2.rb +0 -17
  164. data/test/db/jdbc_mysql.rb +0 -13
  165. data/test/db/jdbc_postgres.rb +0 -23
  166. data/test/db/jndi_config.rb +0 -32
  167. data/test/db/jndi_pooled_config.rb +0 -32
  168. data/test/db/mssql.rb +0 -11
  169. data/test/db/mssql/binary_test.rb +0 -6
  170. data/test/db/mssql/exec_proc_test.rb +0 -46
  171. data/test/db/mssql/identity_insert_test.rb +0 -18
  172. data/test/db/mssql/ignore_system_views_test.rb +0 -40
  173. data/test/db/mssql/limit_offset_test.rb +0 -190
  174. data/test/db/mssql/multibyte_test.rb +0 -16
  175. data/test/db/mssql/multiple_connections_test.rb +0 -71
  176. data/test/db/mssql/rake_test.rb +0 -143
  177. data/test/db/mssql/reset_column_information_test.rb +0 -6
  178. data/test/db/mssql/row_locking_test.rb +0 -7
  179. data/test/db/mssql/serialize_test.rb +0 -6
  180. data/test/db/mssql/simple_test.rb +0 -140
  181. data/test/db/mssql/transaction_test.rb +0 -6
  182. data/test/db/mssql/types_test.rb +0 -205
  183. data/test/db/mssql/unit_test.rb +0 -249
  184. data/test/db/mysql.rb +0 -4
  185. data/test/db/mysql/_rails_test_mysql.32.out +0 -6585
  186. data/test/db/mysql/binary_test.rb +0 -6
  187. data/test/db/mysql/connection_test.rb +0 -51
  188. data/test/db/mysql/index_length_test.rb +0 -58
  189. data/test/db/mysql/multibyte_test.rb +0 -10
  190. data/test/db/mysql/nonstandard_primary_key_test.rb +0 -39
  191. data/test/db/mysql/rake_test.rb +0 -97
  192. data/test/db/mysql/reset_column_information_test.rb +0 -6
  193. data/test/db/mysql/schema_dump_test.rb +0 -228
  194. data/test/db/mysql/serialize_test.rb +0 -6
  195. data/test/db/mysql/simple_test.rb +0 -187
  196. data/test/db/mysql/statement_escaping_test.rb +0 -46
  197. data/test/db/mysql/transaction_test.rb +0 -6
  198. data/test/db/mysql/types_test.rb +0 -30
  199. data/test/db/mysql/unit_test.rb +0 -93
  200. data/test/db/mysql_config.rb +0 -7
  201. data/test/db/oracle.rb +0 -27
  202. data/test/db/oracle/binary_test.rb +0 -6
  203. data/test/db/oracle/limit_test.rb +0 -24
  204. data/test/db/oracle/multibyte_test.rb +0 -22
  205. data/test/db/oracle/rake_test.rb +0 -100
  206. data/test/db/oracle/reset_column_information_test.rb +0 -6
  207. data/test/db/oracle/serialize_test.rb +0 -6
  208. data/test/db/oracle/simple_test.rb +0 -140
  209. data/test/db/oracle/specific_test.rb +0 -180
  210. data/test/db/oracle/transaction_test.rb +0 -31
  211. data/test/db/oracle/unit_test.rb +0 -31
  212. data/test/db/postgres.rb +0 -11
  213. data/test/db/postgres/_rails_test_postgres.32.out +0 -6405
  214. data/test/db/postgres/a_custom_primary_key_test.rb +0 -50
  215. data/test/db/postgres/active_schema_unit_test.rb +0 -68
  216. data/test/db/postgres/array_type_test.rb +0 -101
  217. data/test/db/postgres/binary_test.rb +0 -6
  218. data/test/db/postgres/connection_test.rb +0 -63
  219. data/test/db/postgres/data_types_test.rb +0 -703
  220. data/test/db/postgres/hstore_test.rb +0 -200
  221. data/test/db/postgres/information_schema_leak_test.rb +0 -30
  222. data/test/db/postgres/json_test.rb +0 -86
  223. data/test/db/postgres/ltree_test.rb +0 -51
  224. data/test/db/postgres/mixed_case_test.rb +0 -29
  225. data/test/db/postgres/native_types_test.rb +0 -124
  226. data/test/db/postgres/rake_test.rb +0 -117
  227. data/test/db/postgres/reserved_test.rb +0 -22
  228. data/test/db/postgres/reset_column_information_test.rb +0 -6
  229. data/test/db/postgres/row_locking_test.rb +0 -21
  230. data/test/db/postgres/schema_dump_test.rb +0 -95
  231. data/test/db/postgres/schema_test.rb +0 -115
  232. data/test/db/postgres/simple_test.rb +0 -260
  233. data/test/db/postgres/table_alias_length_test.rb +0 -16
  234. data/test/db/postgres/transaction_test.rb +0 -6
  235. data/test/db/postgres/unit_test.rb +0 -31
  236. data/test/db/postgres_config.rb +0 -10
  237. data/test/db/sqlite3.rb +0 -6
  238. data/test/db/sqlite3/_rails_test_sqlite3.32.out +0 -6274
  239. data/test/db/sqlite3/has_many_though_test.rb +0 -6
  240. data/test/db/sqlite3/rake_test.rb +0 -71
  241. data/test/db/sqlite3/reset_column_information_test.rb +0 -6
  242. data/test/db/sqlite3/schema_dump_test.rb +0 -6
  243. data/test/db/sqlite3/serialize_test.rb +0 -6
  244. data/test/db/sqlite3/simple_test.rb +0 -268
  245. data/test/db/sqlite3/transaction_test.rb +0 -32
  246. data/test/db/sqlite3/type_conversion_test.rb +0 -104
  247. data/test/has_many_through.rb +0 -61
  248. data/test/informix_simple_test.rb +0 -48
  249. data/test/jdbc/db2.rb +0 -36
  250. data/test/jdbc/oracle.rb +0 -34
  251. data/test/jdbc_column_test.rb +0 -23
  252. data/test/jdbc_common.rb +0 -16
  253. data/test/jdbc_connection_test.rb +0 -196
  254. data/test/jndi_callbacks_test.rb +0 -33
  255. data/test/jndi_test.rb +0 -55
  256. data/test/manualTestDatabase.rb +0 -191
  257. data/test/models/add_not_null_column_to_table.rb +0 -9
  258. data/test/models/auto_id.rb +0 -15
  259. data/test/models/binary.rb +0 -18
  260. data/test/models/custom_pk_name.rb +0 -15
  261. data/test/models/data_types.rb +0 -40
  262. data/test/models/entry.rb +0 -41
  263. data/test/models/mixed_case.rb +0 -22
  264. data/test/models/reserved_word.rb +0 -15
  265. data/test/models/rights_and_roles.rb +0 -57
  266. data/test/models/string_id.rb +0 -17
  267. data/test/models/thing.rb +0 -17
  268. data/test/models/topic.rb +0 -32
  269. data/test/models/validates_uniqueness_of_string.rb +0 -19
  270. data/test/rails/mysql.rb +0 -13
  271. data/test/rails/sqlite3/version.rb +0 -6
  272. data/test/rails_stub.rb +0 -31
  273. data/test/rake_test_support.rb +0 -298
  274. data/test/row_locking.rb +0 -102
  275. data/test/schema_dump.rb +0 -182
  276. data/test/serialize.rb +0 -275
  277. data/test/shared_helper.rb +0 -35
  278. data/test/simple.rb +0 -1317
  279. data/test/sybase_jtds_simple_test.rb +0 -28
  280. data/test/sybase_reset_column_information_test.rb +0 -6
  281. data/test/test_helper.rb +0 -304
  282. data/test/transaction.rb +0 -109
@@ -31,7 +31,6 @@ import java.sql.DatabaseMetaData;
31
31
  import java.sql.ResultSet;
32
32
  import java.sql.SQLException;
33
33
  import java.sql.Types;
34
- import java.util.List;
35
34
 
36
35
  import org.jcodings.specific.UTF8Encoding;
37
36
 
@@ -50,7 +49,7 @@ import org.jruby.util.ByteList;
50
49
  * @author nicksieger
51
50
  */
52
51
  public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
53
-
52
+
54
53
  protected MSSQLRubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
55
54
  super(runtime, metaClass);
56
55
  }
@@ -68,27 +67,27 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
68
67
  return new MSSQLRubyJdbcConnection(runtime, klass);
69
68
  }
70
69
  };
71
-
70
+
72
71
  private static final byte[] EXEC = new byte[] { 'e', 'x', 'e', 'c' };
73
-
72
+
74
73
  @JRubyMethod(name = "exec?", required = 1, meta = true, frame = false)
75
74
  public static IRubyObject exec_p(ThreadContext context, IRubyObject self, IRubyObject sql) {
76
75
  final ByteList sqlBytes = sql.convertToString().getByteList();
77
76
  return context.getRuntime().newBoolean( startsWithIgnoreCase(sqlBytes, EXEC) );
78
77
  }
79
-
78
+
80
79
  @Override
81
- protected RubyArray mapTables(final Ruby runtime, final DatabaseMetaData metaData,
82
- final String catalog, final String schemaPattern, final String tablePattern,
80
+ protected RubyArray mapTables(final Ruby runtime, final DatabaseMetaData metaData,
81
+ final String catalog, final String schemaPattern, final String tablePattern,
83
82
  final ResultSet tablesSet) throws SQLException, IllegalStateException {
84
-
83
+
85
84
  final RubyArray tables = runtime.newArray();
86
-
85
+
87
86
  while ( tablesSet.next() ) {
88
87
  String schema = tablesSet.getString(TABLES_TABLE_SCHEM);
89
88
  if ( schema != null ) schema = schema.toLowerCase();
90
89
  // Under MS-SQL, don't return system tables/views unless explicitly asked for :
91
- if ( schemaPattern == null &&
90
+ if ( schemaPattern == null &&
92
91
  ( "sys".equals(schema) || "information_schema".equals(schema) ) ) {
93
92
  continue;
94
93
  }
@@ -98,9 +97,9 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
98
97
  // EXPLAIN is on (e.g. `SET SHOWPLAN_TEXT ON`) not returning
99
98
  // correct result set with table info (null NAME, invalid CAT)
100
99
  throw new IllegalStateException("got null name while matching table(s): [" +
101
- catalog + "." + schemaPattern + "." + tablePattern + "] check " +
102
- "if this happened during EXPLAIN (SET SHOWPLAN_TEXT ON) if so please try " +
103
- "turning it off using the system property 'arjdbc.mssql.explain_support.disabled=true' " +
100
+ catalog + "." + schemaPattern + "." + tablePattern + "] check " +
101
+ "if this happened during EXPLAIN (SET SHOWPLAN_TEXT ON) if so please try " +
102
+ "turning it off using the system property 'arjdbc.mssql.explain_support.disabled=true' " +
104
103
  "or programatically by changing: `ArJdbc::MSSQL::ExplainSupport::DISABLED`");
105
104
  }
106
105
  name = caseConvertIdentifierForRails(metaData, name);
@@ -108,7 +107,7 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
108
107
  }
109
108
  return tables;
110
109
  }
111
-
110
+
112
111
  /**
113
112
  * Microsoft SQL 2000+ support schemas
114
113
  */
@@ -116,30 +115,32 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
116
115
  protected boolean databaseSupportsSchemas() {
117
116
  return true;
118
117
  }
119
-
118
+
120
119
  /**
121
120
  * Treat LONGVARCHAR as CLOB on MSSQL for purposes of converting a JDBC value to Ruby.
122
121
  */
123
122
  @Override
124
- protected IRubyObject jdbcToRuby(Ruby runtime, int column, int type, ResultSet resultSet)
123
+ protected IRubyObject jdbcToRuby(
124
+ final ThreadContext context, final Ruby runtime,
125
+ final int column, int type, final ResultSet resultSet)
125
126
  throws SQLException {
126
127
  if ( type == Types.LONGVARCHAR || type == Types.LONGNVARCHAR ) type = Types.CLOB;
127
- return super.jdbcToRuby(runtime, column, type, resultSet);
128
+ return super.jdbcToRuby(context, runtime, column, type, resultSet);
128
129
  }
129
130
 
130
131
  @Override
131
- protected ColumnData[] extractColumns(final Ruby runtime,
132
- final DatabaseMetaData metaData, final ResultSet resultSet,
132
+ protected ColumnData[] extractColumns(final Ruby runtime,
133
+ final DatabaseMetaData metaData, final ResultSet resultSet,
133
134
  final boolean downCase) throws SQLException {
134
135
  return filterRowNumFromColumns( super.extractColumns(runtime, metaData, resultSet, downCase) );
135
136
  }
136
-
137
+
137
138
  private static final ByteList _row_num; // "_row_num"
138
139
  static {
139
140
  _row_num = new ByteList(new byte[] { '_','r','o','w','_','n','u','m' }, false);
140
141
  _row_num.setEncoding(UTF8Encoding.INSTANCE);
141
142
  }
142
-
143
+
143
144
  /**
144
145
  * Filter out the <tt>_row_num</tt> column from results.
145
146
  */
@@ -147,7 +148,7 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
147
148
  for ( int i = 0; i < columns.length; i++ ) {
148
149
  if ( _row_num.equal( columns[i].name.getByteList() ) ) {
149
150
  final ColumnData[] filtered = new ColumnData[columns.length - 1];
150
-
151
+
151
152
  if ( i > 0 ) {
152
153
  System.arraycopy(columns, 0, filtered, 0, i);
153
154
  }
@@ -161,5 +162,5 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
161
162
  }
162
163
  return columns;
163
164
  }
164
-
165
+
165
166
  }
@@ -24,11 +24,9 @@
24
24
 
25
25
  package arjdbc.mysql;
26
26
 
27
- import static arjdbc.jdbc.RubyJdbcConnection.debugStackTrace;
28
27
  import static arjdbc.util.QuotingUtils.BYTES_0;
29
28
  import static arjdbc.util.QuotingUtils.BYTES_1;
30
-
31
- import java.sql.Connection;
29
+ import static arjdbc.util.QuotingUtils.quoteCharWith;
32
30
 
33
31
  import org.jcodings.specific.UTF8Encoding;
34
32
 
@@ -41,7 +39,7 @@ import org.jruby.runtime.builtin.IRubyObject;
41
39
  import org.jruby.util.ByteList;
42
40
 
43
41
  public class MySQLModule {
44
-
42
+
45
43
  public static RubyModule load(final RubyModule arJdbc) {
46
44
  RubyModule mysql = arJdbc.defineModuleUnder("MySQL");
47
45
  mysql.defineAnnotatedMethods(MySQLModule.class);
@@ -57,16 +55,16 @@ public class MySQLModule {
57
55
  //private final static byte[] ESCAPE = new byte[] {'\\','\\'};
58
56
 
59
57
  private static final int STRING_QUOTES_OPTIMISTIC_QUESS = 24;
60
-
58
+
61
59
  @JRubyMethod(name = "quote_string", required = 1, frame = false)
62
- public static IRubyObject quote_string(final ThreadContext context,
60
+ public static IRubyObject quote_string(final ThreadContext context,
63
61
  final IRubyObject recv, final IRubyObject string) {
64
-
62
+
65
63
  final ByteList stringBytes = ((RubyString) string).getByteList();
66
64
  final byte[] bytes = stringBytes.unsafeBytes();
67
65
  final int begin = stringBytes.getBegin();
68
66
  final int realSize = stringBytes.getRealSize();
69
-
67
+
70
68
  ByteList quotedBytes = null; int appendFrom = begin;
71
69
  for ( int i = begin; i < begin + realSize; i++ ) {
72
70
  final byte byte2;
@@ -83,7 +81,7 @@ public class MySQLModule {
83
81
  if ( byte2 != 0 ) {
84
82
  if ( quotedBytes == null ) {
85
83
  quotedBytes = new ByteList(
86
- new byte[realSize + STRING_QUOTES_OPTIMISTIC_QUESS],
84
+ new byte[realSize + STRING_QUOTES_OPTIMISTIC_QUESS],
87
85
  stringBytes.getEncoding()
88
86
  );
89
87
  quotedBytes.setBegin(0);
@@ -109,37 +107,40 @@ public class MySQLModule {
109
107
 
110
108
  @JRubyMethod(name = "quoted_true", required = 0, frame = false)
111
109
  public static IRubyObject quoted_true(
112
- final ThreadContext context,
110
+ final ThreadContext context,
113
111
  final IRubyObject self) {
114
112
  return RubyString.newString(context.getRuntime(), BYTES_1);
115
113
  }
116
-
114
+
117
115
  @JRubyMethod(name = "quoted_false", required = 0, frame = false)
118
116
  public static IRubyObject quoted_false(
119
- final ThreadContext context,
117
+ final ThreadContext context,
120
118
  final IRubyObject self) {
121
119
  return RubyString.newString(context.getRuntime(), BYTES_0);
122
120
  }
123
-
124
- /**
125
- * HACK HACK HACK See http://bugs.mysql.com/bug.php?id=36565
126
- * MySQL's statement cancel timer can cause memory leaks, so cancel it
127
- * if we loaded MySQL classes from the same classloader as JRuby
128
- */
129
- @JRubyMethod(module = true, frame = false)
130
- public static IRubyObject kill_cancel_timer(final ThreadContext context,
131
- final IRubyObject self, final IRubyObject raw_connection) {
132
-
133
- final Connection conn = (Connection) raw_connection.dataGetStruct();
134
- if (conn != null && conn.getClass().getClassLoader() == self.getRuntime().getJRubyClassLoader()) {
135
- try {
136
- java.lang.reflect.Field f = conn.getClass().getDeclaredField("cancelTimer");
137
- f.setAccessible(true);
138
- java.util.Timer timer = (java.util.Timer) f.get(null);
139
- timer.cancel();
140
- }
141
- catch (Exception e) { debugStackTrace(context, e); }
121
+
122
+ @JRubyMethod(name = "quote_column_name", required = 1, frame = false)
123
+ public static IRubyObject quote_column_name(
124
+ final ThreadContext context,
125
+ final IRubyObject self,
126
+ final IRubyObject string) { // "`#{name.to_s.gsub('`', '``')}`"
127
+ RubyString origin = string.asString();
128
+ RubyString quoted = quoteCharWith(context, origin, '`', '`');
129
+ final ByteList bytes = quoted.getByteList();
130
+ if ( origin == quoted ) {
131
+ final RubyString result =
132
+ RubyString.newStringLight(context.getRuntime(), bytes.getRealSize() + 2, bytes.getEncoding());
133
+ final ByteList resultBytes = result.getByteList();
134
+ resultBytes.append( (byte) '`' );
135
+ resultBytes.append( bytes );
136
+ resultBytes.append( (byte) '`' );
137
+ return result;
138
+ }
139
+ else {
140
+ bytes.append( (byte) '`' );
141
+ bytes.prepend( (byte) '`' );
142
+ return quoted;
142
143
  }
143
- return self.getRuntime().getNil();
144
144
  }
145
+
145
146
  }
@@ -28,20 +28,26 @@ package arjdbc.mysql;
28
28
  import arjdbc.jdbc.RubyJdbcConnection;
29
29
  import arjdbc.jdbc.Callable;
30
30
 
31
+ import java.lang.reflect.Field;
31
32
  import java.sql.Connection;
32
33
  import java.sql.DatabaseMetaData;
33
34
  import java.sql.PreparedStatement;
34
35
  import java.sql.SQLException;
35
36
  import java.sql.ResultSet;
36
37
  import java.sql.Statement;
38
+ import java.sql.Time;
39
+ import java.sql.Timestamp;
37
40
  import java.sql.Types;
38
41
  import java.util.ArrayList;
39
42
  import java.util.List;
43
+ import java.util.TimeZone;
40
44
 
41
45
  import org.jruby.Ruby;
42
46
  import org.jruby.RubyClass;
47
+ import org.jruby.RubyFloat;
43
48
  import org.jruby.RubyModule;
44
49
  import org.jruby.RubyString;
50
+ import org.jruby.exceptions.RaiseException;
45
51
  import org.jruby.runtime.ObjectAllocator;
46
52
  import org.jruby.runtime.ThreadContext;
47
53
  import org.jruby.runtime.builtin.IRubyObject;
@@ -56,48 +62,95 @@ public class MySQLRubyJdbcConnection extends RubyJdbcConnection {
56
62
  super(runtime, metaClass);
57
63
  }
58
64
 
65
+ private static ObjectAllocator MYSQL_JDBCCONNECTION_ALLOCATOR = new ObjectAllocator() {
66
+ public IRubyObject allocate(Ruby runtime, RubyClass klass) {
67
+ return new MySQLRubyJdbcConnection(runtime, klass);
68
+ }
69
+ };
70
+
71
+ public static RubyClass createMySQLJdbcConnectionClass(Ruby runtime, RubyClass jdbcConnection) {
72
+ RubyClass clazz = getConnectionAdapters(runtime).
73
+ defineClassUnder("MySQLJdbcConnection", jdbcConnection, MYSQL_JDBCCONNECTION_ALLOCATOR);
74
+ clazz.defineAnnotatedMethods(MySQLRubyJdbcConnection.class);
75
+ return clazz;
76
+ }
77
+
59
78
  @Override
60
- protected boolean doExecute(final Statement statement,
61
- final String query) throws SQLException {
79
+ protected boolean doExecute(final Statement statement, final String query)
80
+ throws SQLException {
62
81
  return statement.execute(query, Statement.RETURN_GENERATED_KEYS);
63
82
  }
64
83
 
65
84
  @Override
66
- protected IRubyObject unmarshalKeysOrUpdateCount(final ThreadContext context,
85
+ protected IRubyObject mapGeneratedKeysOrUpdateCount(final ThreadContext context,
67
86
  final Connection connection, final Statement statement) throws SQLException {
68
87
  final Ruby runtime = context.getRuntime();
69
- final IRubyObject key = unmarshalIdResult(runtime, statement);
70
- return key.isNil() ? runtime.newFixnum(statement.getUpdateCount()) : key;
88
+ final IRubyObject key = mapGeneratedKeys(runtime, connection, statement);
89
+ return ( key == null || key.isNil() ) ? runtime.newFixnum( statement.getUpdateCount() ) : key;
71
90
  }
72
91
 
73
92
  @Override
74
- protected IRubyObject jdbcToRuby(Ruby runtime, int column, int type, ResultSet resultSet)
93
+ protected IRubyObject jdbcToRuby(
94
+ final ThreadContext context, final Ruby runtime,
95
+ final int column, final int type, final ResultSet resultSet)
75
96
  throws SQLException {
76
97
  if ( Types.BOOLEAN == type || Types.BIT == type ) {
77
98
  final boolean value = resultSet.getBoolean(column);
78
99
  return resultSet.wasNull() ? runtime.getNil() : runtime.newFixnum(value ? 1 : 0);
79
100
  }
80
- return super.jdbcToRuby(runtime, column, type, resultSet);
101
+ return super.jdbcToRuby(context, runtime, column, type, resultSet);
81
102
  }
82
103
 
83
- private static ObjectAllocator MYSQL_JDBCCONNECTION_ALLOCATOR = new ObjectAllocator() {
84
- public IRubyObject allocate(Ruby runtime, RubyClass klass) {
85
- return new MySQLRubyJdbcConnection(runtime, klass);
104
+ @Override // can not use statement.setTimestamp( int, Timestamp, Calendar )
105
+ protected void setTimestampParameter(final ThreadContext context,
106
+ final Connection connection, final PreparedStatement statement,
107
+ final int index, IRubyObject value,
108
+ final IRubyObject column, final int type) throws SQLException {
109
+ if ( value.isNil() ) statement.setNull(index, Types.TIMESTAMP);
110
+ else {
111
+ value = getTimeInDefaultTimeZone(context, value);
112
+ if ( value instanceof RubyString ) { // yyyy-[m]m-[d]d hh:mm:ss[.f...]
113
+ final Timestamp timestamp = Timestamp.valueOf( value.toString() );
114
+ statement.setTimestamp( index, timestamp ); // assume local time-zone
115
+ }
116
+ else { // Time or DateTime ( ActiveSupport::TimeWithZone.to_time )
117
+ final double time = adjustTimeFromDefaultZone(value);
118
+ final RubyFloat timeValue = context.getRuntime().newFloat( time );
119
+ statement.setTimestamp( index, convertToTimestamp(timeValue) );
120
+ }
86
121
  }
87
- };
88
-
89
- public static RubyClass createMySQLJdbcConnectionClass(Ruby runtime, RubyClass jdbcConnection) {
90
- RubyClass clazz = getConnectionAdapters(runtime).
91
- defineClassUnder("MySQLJdbcConnection", jdbcConnection, MYSQL_JDBCCONNECTION_ALLOCATOR);
92
- clazz.defineAnnotatedMethods(MySQLRubyJdbcConnection.class);
93
- return clazz;
94
122
  }
95
123
 
96
- /*
97
- public static RubyClass getMySQLJdbcConnectionClass(final Ruby runtime) {
98
- return getConnectionAdapters(runtime).getClass("MySQLJdbcConnection");
99
- } */
100
-
124
+ @Override // can not use statement.setTime( int, Time, Calendar )
125
+ protected void setTimeParameter(final ThreadContext context,
126
+ final Connection connection, final PreparedStatement statement,
127
+ final int index, IRubyObject value,
128
+ final IRubyObject column, final int type) throws SQLException {
129
+ if ( value.isNil() ) statement.setNull(index, Types.TIME);
130
+ else {
131
+ value = getTimeInDefaultTimeZone(context, value);
132
+ if ( value instanceof RubyString ) {
133
+ final Time time = Time.valueOf( value.toString() );
134
+ statement.setTime( index, time ); // assume local time-zone
135
+ }
136
+ else { // Time or DateTime ( ActiveSupport::TimeWithZone.to_time )
137
+ final double timeValue = adjustTimeFromDefaultZone(value);
138
+ final Time time = new Time(( (long) timeValue ) * 1000); // millis
139
+ // java.sql.Time is expected to be only up to second precision
140
+ statement.setTime( index, time );
141
+ }
142
+ }
143
+ }
144
+
145
+ private static double adjustTimeFromDefaultZone(final IRubyObject value) {
146
+ // Time's to_f is : ( millis * 1000 + usec ) / 1_000_000.0
147
+ final double time = value.convertToFloat().getDoubleValue(); // to_f
148
+ // NOTE: MySQL assumes default TZ thus need to adjust to match :
149
+ final int offset = TimeZone.getDefault().getOffset((long) time * 1000);
150
+ // Time's to_f is : ( millis * 1000 + usec ) / 1_000_000.0
151
+ return time - ( offset / 1000.0 );
152
+ }
153
+
101
154
  @Override
102
155
  protected IRubyObject indexes(final ThreadContext context, final String tableName, final String name, final String schemaName) {
103
156
  return withConnection(context, new Callable<IRubyObject>() {
@@ -110,14 +163,14 @@ public class MySQLRubyJdbcConnection extends RubyJdbcConnection {
110
163
  final IRubyObject rubyTableName = RubyString.newUnicodeString(
111
164
  runtime, caseConvertIdentifierForJdbc(metaData, tableName)
112
165
  );
113
-
166
+
114
167
  StringBuilder query = new StringBuilder("SHOW KEYS FROM ");
115
168
  if (jdbcSchemaName != null) {
116
169
  query.append(jdbcSchemaName).append(".");
117
170
  }
118
171
  query.append(jdbcTableName);
119
172
  query.append(" WHERE key_name != 'PRIMARY'");
120
-
173
+
121
174
  final List<IRubyObject> indexes = new ArrayList<IRubyObject>();
122
175
  PreparedStatement statement = null;
123
176
  ResultSet keySet = null;
@@ -125,7 +178,7 @@ public class MySQLRubyJdbcConnection extends RubyJdbcConnection {
125
178
  try {
126
179
  statement = connection.prepareStatement(query.toString());
127
180
  keySet = statement.executeQuery();
128
-
181
+
129
182
  String currentKeyName = null;
130
183
 
131
184
  while ( keySet.next() ) {
@@ -135,7 +188,7 @@ public class MySQLRubyJdbcConnection extends RubyJdbcConnection {
135
188
  currentKeyName = keyName;
136
189
 
137
190
  final boolean nonUnique = keySet.getBoolean("non_unique");
138
-
191
+
139
192
  IRubyObject[] args = new IRubyObject[] {
140
193
  rubyTableName, // table_name
141
194
  RubyString.newUnicodeString(runtime, keyName), // index_name
@@ -153,14 +206,14 @@ public class MySQLRubyJdbcConnection extends RubyJdbcConnection {
153
206
  final int length = keySet.getInt("sub_part");
154
207
  final boolean nullLength = keySet.wasNull();
155
208
 
156
- lastIndexDef.callMethod(context, "columns").callMethod(context,
209
+ lastIndexDef.callMethod(context, "columns").callMethod(context,
157
210
  "<<", RubyString.newUnicodeString(runtime, columnName));
158
- lastIndexDef.callMethod(context, "lengths").callMethod(context,
211
+ lastIndexDef.callMethod(context, "lengths").callMethod(context,
159
212
  "<<", nullLength ? runtime.getNil() : runtime.newFixnum(length));
160
213
  }
161
214
  }
162
-
163
- return runtime.newArray(indexes);
215
+
216
+ return runtime.newArray(indexes);
164
217
  }
165
218
  finally {
166
219
  close(keySet);
@@ -169,4 +222,68 @@ public class MySQLRubyJdbcConnection extends RubyJdbcConnection {
169
222
  }
170
223
  });
171
224
  }
225
+
226
+ @Override
227
+ protected Connection newConnection() throws RaiseException, SQLException {
228
+ final Connection connection = super.newConnection();
229
+ killCancelTimer(connection);
230
+ return connection;
231
+ }
232
+
233
+ /**
234
+ * HACK HACK HACK See http://bugs.mysql.com/bug.php?id=36565
235
+ * MySQL's statement cancel timer can cause memory leaks, so cancel it
236
+ * if we loaded MySQL classes from the same class-loader as JRuby
237
+ *
238
+ * NOTE: this will likely do nothing on a recent driver esp. since MySQL's
239
+ * Connector/J supports JDBC 4.0 (Java 6+) which we now require at minimum
240
+ */
241
+ private void killCancelTimer(final Connection connection) {
242
+ if (connection.getClass().getClassLoader() == getRuntime().getJRubyClassLoader()) {
243
+ Field field = cancelTimerField();
244
+ if ( field != null ) {
245
+ java.util.Timer timer = null;
246
+ try {
247
+ // connection likely: com.mysql.jdbc.JDBC4Connection
248
+ // or (for 3.0) super class: com.mysql.jdbc.ConnectionImpl
249
+ timer = (java.util.Timer)
250
+ field.get( connection.unwrap(Connection.class) );
251
+ }
252
+ catch (SQLException e) {
253
+ debugMessage( e.toString() );
254
+ }
255
+ catch (IllegalAccessException e) {
256
+ debugMessage( e.toString() );
257
+ }
258
+ if ( timer != null ) timer.cancel();
259
+ }
260
+ }
261
+ }
262
+
263
+ private static Field cancelTimer = null;
264
+ private static boolean cancelTimerChecked = false;
265
+
266
+ private static Field cancelTimerField() {
267
+ if ( cancelTimerChecked ) return cancelTimer;
268
+ try {
269
+ Class klass = Class.forName("com.mysql.jdbc.ConnectionImpl");
270
+ Field field = klass.getDeclaredField("cancelTimer");
271
+ field.setAccessible(true);
272
+ synchronized(MySQLRubyJdbcConnection.class) {
273
+ if ( cancelTimer == null ) cancelTimer = field;
274
+ }
275
+ }
276
+ catch (ClassNotFoundException e) {
277
+ debugMessage("INFO: missing MySQL JDBC connection impl: " + e);
278
+ }
279
+ catch (NoSuchFieldException e) {
280
+ debugMessage("INFO: MySQL's cancel timer seems to have changed: " + e);
281
+ }
282
+ catch (SecurityException e) {
283
+ debugMessage( e.toString() );
284
+ }
285
+ finally { cancelTimerChecked = true; }
286
+ return cancelTimer;
287
+ }
288
+
172
289
  }