activerecord-jdbc-adapter 0.9.0.1 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/History.txt +31 -0
  2. data/Manifest.txt +7 -0
  3. data/README.txt +15 -2
  4. data/Rakefile +28 -30
  5. data/lib/active_record/connection_adapters/h2_adapter.rb +13 -1
  6. data/lib/active_record/connection_adapters/jdbc_adapter.rb +78 -96
  7. data/lib/jdbc_adapter/jdbc.rake +15 -5
  8. data/lib/jdbc_adapter/jdbc_adapter_internal.jar +0 -0
  9. data/lib/jdbc_adapter/jdbc_cachedb.rb +4 -4
  10. data/lib/jdbc_adapter/jdbc_db2.rb +5 -7
  11. data/lib/jdbc_adapter/jdbc_derby.rb +57 -30
  12. data/lib/jdbc_adapter/jdbc_firebird.rb +2 -2
  13. data/lib/jdbc_adapter/jdbc_hsqldb.rb +53 -46
  14. data/lib/jdbc_adapter/jdbc_informix.rb +4 -5
  15. data/lib/jdbc_adapter/jdbc_mimer.rb +2 -2
  16. data/lib/jdbc_adapter/jdbc_mssql.rb +25 -23
  17. data/lib/jdbc_adapter/jdbc_mysql.rb +20 -22
  18. data/lib/jdbc_adapter/jdbc_oracle.rb +115 -117
  19. data/lib/jdbc_adapter/jdbc_postgre.rb +129 -59
  20. data/lib/jdbc_adapter/jdbc_sqlite3.rb +149 -28
  21. data/lib/jdbc_adapter/jdbc_sybase.rb +13 -2
  22. data/lib/jdbc_adapter/missing_functionality_helper.rb +12 -3
  23. data/lib/jdbc_adapter/version.rb +1 -1
  24. data/src/java/jdbc_adapter/JdbcAdapterInternalService.java +6 -1101
  25. data/src/java/jdbc_adapter/JdbcDerbySpec.java +26 -23
  26. data/src/java/jdbc_adapter/JdbcMySQLSpec.java +79 -28
  27. data/src/java/jdbc_adapter/PostgresRubyJdbcConnection.java +35 -0
  28. data/src/java/jdbc_adapter/RubyJdbcConnection.java +1149 -0
  29. data/src/java/jdbc_adapter/SQLBlock.java +12 -3
  30. data/src/java/jdbc_adapter/Sqlite3RubyJdbcConnection.java +41 -0
  31. data/test/activerecord/connection_adapters/type_conversion_test.rb +1 -1
  32. data/test/db/derby.rb +0 -3
  33. data/test/db/h2.rb +0 -3
  34. data/test/db/hsqldb.rb +1 -4
  35. data/test/db/mysql.rb +1 -0
  36. data/test/db/oracle.rb +5 -0
  37. data/test/db/sqlite3.rb +7 -3
  38. data/test/derby_migration_test.rb +21 -0
  39. data/test/has_many_through.rb +11 -4
  40. data/test/jdbc_common.rb +13 -1
  41. data/test/models/data_types.rb +11 -1
  42. data/test/models/mixed_case.rb +20 -0
  43. data/test/mysql_multibyte_test.rb +4 -0
  44. data/test/oracle_simple_test.rb +1 -1
  45. data/test/postgres_mixed_case_test.rb +19 -0
  46. data/test/simple.rb +220 -41
  47. data/test/sqlite3_simple_test.rb +83 -0
  48. data/test/sybase_jtds_simple_test.rb +6 -0
  49. metadata +12 -10
@@ -71,8 +71,10 @@ public class JdbcDerbySpec {
71
71
  case 't': //text, timestamp, time
72
72
  if (type.equals("text")) {
73
73
  return value;
74
- } else {
75
- return rubyApi.callMethod(recv, "cast_to_time", value);
74
+ } else if (type.equals("timestamp")) {
75
+ return rubyApi.callMethod(recv.getMetaClass(), "string_to_time", value);
76
+ } else { //time
77
+ return rubyApi.callMethod(recv.getMetaClass(), "string_to_dummy_time", value);
76
78
  }
77
79
  case 'i': //integer
78
80
  case 'p': //primary key
@@ -83,7 +85,7 @@ public class JdbcDerbySpec {
83
85
  }
84
86
  case 'd': //decimal, datetime, date
85
87
  if (type.equals("datetime")) {
86
- return rubyApi.callMethod(recv, "cast_to_date_or_time", value);
88
+ return rubyApi.callMethod(recv.getMetaClass(), "string_to_time", value);
87
89
  } else if (type.equals("date")) {
88
90
  return rubyApi.callMethod(recv.getMetaClass(), "string_to_date", value);
89
91
  } else {
@@ -93,7 +95,7 @@ public class JdbcDerbySpec {
93
95
  return rubyApi.callMethod(value, "to_f");
94
96
  case 'b': //binary, boolean
95
97
  if (type.equals("binary")) {
96
- return rubyApi.callMethod(recv, "value_to_binary", value);
98
+ return rubyApi.callMethod(recv.getMetaClass(), "binary_to_string", value);
97
99
  } else {
98
100
  return rubyApi.callMethod(recv.getMetaClass(), "value_to_boolean", value);
99
101
  }
@@ -108,14 +110,14 @@ public class JdbcDerbySpec {
108
110
  IRubyObject value = args[0];
109
111
  if (args.length > 1) {
110
112
  IRubyObject col = args[1];
111
- IRubyObject type = rubyApi.callMethod(col, "type");
113
+ String type = rubyApi.callMethod(col, "type").toString();
112
114
  if (value instanceof RubyString) {
113
- if (type == runtime.newSymbol("string")) {
115
+ if (type.equals("string")) {
114
116
  return quote_string_with_surround(runtime, "'", (RubyString)value, "'");
115
- } else if (type == runtime.newSymbol("text")) {
117
+ } else if (type.equals("text")) {
116
118
  return quote_string_with_surround(runtime, "CAST('", (RubyString)value, "' AS CLOB)");
117
- } else if (type == runtime.newSymbol("binary")) {
118
- return hexquote_string_with_surround(runtime, "CAST('", (RubyString)value, "' AS BLOB)");
119
+ } else if (type.equals("binary")) {
120
+ return hexquote_string_with_surround(runtime, "CAST(X'", (RubyString)value, "' AS BLOB)");
119
121
  } else {
120
122
  // column type :integer or other numeric or date version
121
123
  if (only_digits((RubyString)value)) {
@@ -125,7 +127,7 @@ public class JdbcDerbySpec {
125
127
  }
126
128
  }
127
129
  } else if ((value instanceof RubyFloat) || (value instanceof RubyFixnum) || (value instanceof RubyBignum)) {
128
- if (type == runtime.newSymbol("string")) {
130
+ if (type.equals("string")) {
129
131
  return quote_string_with_surround(runtime, "'", RubyString.objAsString(context, value), "'");
130
132
  }
131
133
  }
@@ -155,7 +157,7 @@ public class JdbcDerbySpec {
155
157
  return quote_string_with_surround(runtime, "'", svalue, "'");
156
158
  }
157
159
  } else if (value.isNil()) {
158
- return runtime.newStringShared(NULL);
160
+ return runtime.newString(NULL);
159
161
  } else if (value instanceof RubyBoolean) {
160
162
  return (value.isTrue() ?
161
163
  (type == runtime.newSymbol(":integer")) ? runtime.newString("1") : rubyApi.callMethod(recv, "quoted_true") :
@@ -164,9 +166,7 @@ public class JdbcDerbySpec {
164
166
  return RubyString.objAsString(context, value);
165
167
  } else if(value instanceof RubyBigDecimal) {
166
168
  return rubyApi.callMethod(value, "to_s", runtime.newString("F"));
167
- } else if (rubyApi.isKindOf(value, runtime.getModule("Date"))) {
168
- return quote_string_with_surround(runtime, "'", RubyString.objAsString(context, value), "'");
169
- } else if (rubyApi.isKindOf(value, runtime.getModule("Time")) || rubyApi.isKindOf(value, runtime.getModule("DateTime"))) {
169
+ } else if (rubyApi.callMethod(value, "acts_like?", runtime.newString("date")).isTrue() || rubyApi.callMethod(value, "acts_like?", runtime.newString("time")).isTrue()) {
170
170
  return quote_string_with_surround(runtime, "'", (RubyString)(rubyApi.callMethod(recv, "quoted_date", value)), "'");
171
171
  } else {
172
172
  return quote_string_with_surround(runtime, "'", (RubyString)(rubyApi.callMethod(value, "to_yaml")), "'");
@@ -191,7 +191,7 @@ public class JdbcDerbySpec {
191
191
 
192
192
  output.append(after.getBytes());
193
193
 
194
- return runtime.newStringShared(output);
194
+ return runtime.newString(output);
195
195
  }
196
196
 
197
197
  private final static byte[] HEX = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
@@ -199,16 +199,18 @@ public class JdbcDerbySpec {
199
199
  private static IRubyObject hexquote_string_with_surround(Ruby runtime, String before, RubyString string, String after) {
200
200
  ByteList input = string.getByteList();
201
201
  ByteList output = new ByteList(before.getBytes());
202
+ int written = 0;
202
203
  for(int i = input.begin; i< input.begin + input.realSize; i++) {
203
204
  byte b1 = input.bytes[i];
204
205
  byte higher = HEX[(((char)b1)>>4)%16];
205
206
  byte lower = HEX[((char)b1)%16];
206
- if(b1 == '\'') {
207
- output.append(higher);
208
- output.append(lower);
209
- }
210
207
  output.append(higher);
211
208
  output.append(lower);
209
+ written += 2;
210
+ if(written >= 16334) { // max hex length = 16334
211
+ output.append("'||X'".getBytes());
212
+ written = 0;
213
+ }
212
214
  }
213
215
 
214
216
  output.append(after.getBytes());
@@ -284,13 +286,14 @@ public class JdbcDerbySpec {
284
286
  public static IRubyObject _execute(ThreadContext context, IRubyObject recv, IRubyObject[] args) throws SQLException, java.io.IOException {
285
287
  Ruby runtime = recv.getRuntime();
286
288
  try {
287
- IRubyObject conn = rubyApi.getInstanceVariable(recv, "@connection");
289
+ // TODO: Ouch....this looks fragile
290
+ RubyJdbcConnection conn = (RubyJdbcConnection) rubyApi.getInstanceVariable(recv, "@connection");
288
291
  String sql = args[0].toString().trim().toLowerCase();
289
292
  if (sql.charAt(0) == '(') {
290
293
  sql = sql.substring(1).trim();
291
294
  }
292
295
  if (sql.startsWith("insert")) {
293
- return JdbcAdapterInternalService.execute_insert(conn, args[0]);
296
+ return conn.execute_insert(context, args[0]);
294
297
  } else if (sql.startsWith("select") || sql.startsWith("show")) {
295
298
  IRubyObject offset = rubyApi.getInstanceVariable(recv, "@offset");
296
299
  if(offset == null || offset.isNil()) {
@@ -307,7 +310,7 @@ public class JdbcDerbySpec {
307
310
  range = RubyRange.newRange(runtime, context, offset, v1, true);
308
311
  max = rubyApi.callMethod(v1, "+", RubyFixnum.one(runtime));
309
312
  }
310
- IRubyObject result = JdbcAdapterInternalService.execute_query(conn, new IRubyObject[]{args[0], max});
313
+ IRubyObject result = conn.execute_query(context, args[0], max);
311
314
  IRubyObject ret = rubyApi.callMethod(result, "[]", range);
312
315
  if (ret.isNil()) {
313
316
  return runtime.newArray();
@@ -315,7 +318,7 @@ public class JdbcDerbySpec {
315
318
  return ret;
316
319
  }
317
320
  } else {
318
- return JdbcAdapterInternalService.execute_update(conn, args[0]);
321
+ return conn.execute_update(context, args[0]);
319
322
  }
320
323
  } finally {
321
324
  rubyApi.setInstanceVariable(recv, "@limit", runtime.getNil());
@@ -1,5 +1,5 @@
1
1
  /***** BEGIN LICENSE BLOCK *****
2
- * Copyright (c) 2006-2007 Nick Sieger <nick@nicksieger.com>
2
+ * Copyright (c) 2006-2009 Nick Sieger <nick@nicksieger.com>
3
3
  * Copyright (c) 2006-2007 Ola Bini <ola.bini@gmail.com>
4
4
  *
5
5
  * Permission is hereby granted, free of charge, to any person obtaining
@@ -24,11 +24,13 @@
24
24
 
25
25
  package jdbc_adapter;
26
26
 
27
- import org.jruby.Ruby;
27
+ import java.sql.Connection;
28
+
28
29
  import org.jruby.RubyModule;
29
30
  import org.jruby.RubyString;
30
31
 
31
32
  import org.jruby.anno.JRubyMethod;
33
+ import org.jruby.runtime.ThreadContext;
32
34
  import org.jruby.runtime.builtin.IRubyObject;
33
35
 
34
36
  import org.jruby.util.ByteList;
@@ -39,23 +41,27 @@ public class JdbcMySQLSpec {
39
41
  mysql.defineAnnotatedMethods(JdbcMySQLSpec.class);
40
42
  }
41
43
 
42
- private final static ByteList ZERO = new ByteList(new byte[]{'\\','0'});
43
- private final static ByteList NEWLINE = new ByteList(new byte[]{'\\','n'});
44
- private final static ByteList CARRIAGE = new ByteList(new byte[]{'\\','r'});
45
- private final static ByteList ZED = new ByteList(new byte[]{'\\','Z'});
46
- private final static ByteList DBL = new ByteList(new byte[]{'\\','"'});
47
- private final static ByteList SINGLE = new ByteList(new byte[]{'\\','\''});
48
- private final static ByteList ESCAPE = new ByteList(new byte[]{'\\','\\'});
49
-
50
- @JRubyMethod(name = "quote_string", required = 1)
51
- public static IRubyObject quote_string(IRubyObject recv, IRubyObject string) {
52
- ByteList bl = ((RubyString) string).getByteList();
53
- ByteList blNew = new ByteList();
54
- int startOfExtend = bl.begin;
44
+ private final static byte BACKQUOTE = '`';
45
+ private final static byte[] QUOTED_DOT = new byte[] {'`', '.', '`'};
46
+
47
+ private final static byte[] ZERO = new byte[] {'\\','0'};
48
+ private final static byte[] NEWLINE = new byte[] {'\\','n'};
49
+ private final static byte[] CARRIAGE = new byte[] {'\\','r'};
50
+ private final static byte[] ZED = new byte[] {'\\','Z'};
51
+ private final static byte[] DBL = new byte[] {'\\','"'};
52
+ private final static byte[] SINGLE = new byte[] {'\\','\''};
53
+ private final static byte[] ESCAPE = new byte[] {'\\','\\'};
54
+
55
+ @JRubyMethod(name = "quote_string", required = 1, frame=false)
56
+ public static IRubyObject quote_string(ThreadContext context, IRubyObject recv, IRubyObject string) {
57
+ ByteList bytes = ((RubyString) string).getByteList();
58
+ ByteList newBytes = new ByteList();
55
59
 
56
- for(int i = bl.begin; i < bl.begin + bl.realSize; i++) {
57
- ByteList rep = null;
58
- switch (bl.bytes[i]) {
60
+ newBytes.append(bytes);
61
+
62
+ for(int i = newBytes.begin; i < newBytes.begin + newBytes.realSize; i++) {
63
+ byte[] rep = null;
64
+ switch (newBytes.bytes[i]) {
59
65
  case 0: rep = ZERO; break;
60
66
  case '\n': rep = NEWLINE; break;
61
67
  case '\r': rep = CARRIAGE; break;
@@ -63,20 +69,65 @@ public class JdbcMySQLSpec {
63
69
  case '"': rep = DBL; break;
64
70
  case '\'': rep = SINGLE; break;
65
71
  case '\\': rep = ESCAPE; break;
66
- default: continue;
67
72
  }
68
- if(i > startOfExtend)
69
- blNew.append(bl, startOfExtend-bl.begin, i-startOfExtend);
70
- blNew.append(rep, 0, 2);
71
- startOfExtend = i+1;
73
+
74
+ if (rep != null) {
75
+ newBytes.replace(i, 1, rep);
76
+ i += rep.length - 1; // We subtract one since for loop already adds one
77
+ }
72
78
  }
79
+
73
80
  // Nothing changed, can return original
74
- if (startOfExtend == bl.begin) {
75
- return string;
81
+ if (newBytes.length() == bytes.length()) return string;
82
+
83
+ return context.getRuntime().newString(newBytes);
84
+ }
85
+
86
+ @JRubyMethod(name = "quote_column_name", frame=false)
87
+ public static IRubyObject quote_column_name(ThreadContext context, IRubyObject recv, IRubyObject arg) {
88
+ ByteList bytes = arg.asString().getByteList();
89
+ ByteList newBytes = new ByteList();
90
+
91
+ newBytes.insert(0, BACKQUOTE);
92
+ newBytes.append(bytes);
93
+ newBytes.append(BACKQUOTE);
94
+
95
+ return context.getRuntime().newString(newBytes);
96
+ }
97
+
98
+ @JRubyMethod(name = "quote_table_name", frame=false)
99
+ public static IRubyObject quote_table_name(ThreadContext context, IRubyObject recv, IRubyObject arg) {
100
+ ByteList bytes = arg.asString().getByteList();
101
+ ByteList newBytes = new ByteList();
102
+
103
+ newBytes.insert(0, BACKQUOTE);
104
+ newBytes.append(bytes);
105
+ int i = 0;
106
+ while ((i = newBytes.indexOf('.')) != -1) {
107
+ newBytes.replace(i, 1, QUOTED_DOT);
76
108
  }
77
- if (bl.begin + bl.realSize > startOfExtend)
78
- blNew.append(bl, startOfExtend-bl.begin, bl.begin + bl.realSize - startOfExtend);
109
+ newBytes.append(BACKQUOTE);
79
110
 
80
- return recv.getRuntime().newStringShared(blNew);
111
+ return context.getRuntime().newString(newBytes);
112
+ }
113
+
114
+ /**
115
+ * HACK HACK HACK See http://bugs.mysql.com/bug.php?id=36565
116
+ * MySQL's statement cancel timer can cause memory leaks, so cancel it
117
+ * if we loaded MySQL classes from the same classloader as JRuby
118
+ */
119
+ @JRubyMethod(module = true, frame = false)
120
+ public static IRubyObject kill_cancel_timer(ThreadContext context, IRubyObject recv, IRubyObject raw_connection) {
121
+ Connection conn = (Connection) raw_connection.dataGetStruct();
122
+ if (conn != null && conn.getClass().getClassLoader() == recv.getRuntime().getJRubyClassLoader()) {
123
+ try {
124
+ java.lang.reflect.Field f = conn.getClass().getDeclaredField("cancelTimer");
125
+ f.setAccessible(true);
126
+ java.util.Timer timer = (java.util.Timer) f.get(null);
127
+ timer.cancel();
128
+ } catch (Exception e) {
129
+ }
130
+ }
131
+ return recv.getRuntime().getNil();
81
132
  }
82
133
  }
@@ -0,0 +1,35 @@
1
+ /*
2
+ * To change this template, choose Tools | Templates
3
+ * and open the template in the editor.
4
+ */
5
+
6
+ package jdbc_adapter;
7
+
8
+ import org.jruby.Ruby;
9
+ import org.jruby.RubyClass;
10
+ import org.jruby.runtime.ObjectAllocator;
11
+ import org.jruby.runtime.builtin.IRubyObject;
12
+
13
+ /**
14
+ *
15
+ * @author enebo
16
+ */
17
+ public class PostgresRubyJdbcConnection extends RubyJdbcConnection {
18
+ protected PostgresRubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
19
+ super(runtime, metaClass);
20
+ }
21
+
22
+ public static RubyClass createPostgresJdbcConnectionClass(Ruby runtime, RubyClass jdbcConnection) {
23
+ RubyClass clazz = RubyJdbcConnection.getConnectionAdapters(runtime).defineClassUnder("PostgresJdbcConnection",
24
+ jdbcConnection, POSTGRES_JDBCCONNECTION_ALLOCATOR);
25
+ clazz.defineAnnotatedMethods(PostgresRubyJdbcConnection.class);
26
+
27
+ return clazz;
28
+ }
29
+
30
+ private static ObjectAllocator POSTGRES_JDBCCONNECTION_ALLOCATOR = new ObjectAllocator() {
31
+ public IRubyObject allocate(Ruby runtime, RubyClass klass) {
32
+ return new PostgresRubyJdbcConnection(runtime, klass);
33
+ }
34
+ };
35
+ }
@@ -0,0 +1,1149 @@
1
+ /*
2
+ **** BEGIN LICENSE BLOCK *****
3
+ * Copyright (c) 2006-2009 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 jdbc_adapter;
27
+
28
+ import java.io.ByteArrayInputStream;
29
+ import java.io.IOException;
30
+ import java.io.InputStream;
31
+ import java.io.Reader;
32
+ import java.io.StringReader;
33
+ import org.jruby.Ruby;
34
+ import org.jruby.RubyClass;
35
+ import org.jruby.RubyModule;
36
+ import org.jruby.RubyObject;
37
+ import org.jruby.RubyObjectAdapter;
38
+ import org.jruby.anno.JRubyMethod;
39
+ import org.jruby.javasupport.JavaEmbedUtils;
40
+ import org.jruby.runtime.ObjectAllocator;
41
+ import org.jruby.runtime.ThreadContext;
42
+ import org.jruby.runtime.builtin.IRubyObject;
43
+ import org.jruby.util.ByteList;
44
+
45
+ import java.sql.Connection;
46
+ import java.sql.DatabaseMetaData;
47
+ import java.sql.PreparedStatement;
48
+ import java.sql.ResultSet;
49
+ import java.sql.ResultSetMetaData;
50
+ import java.sql.SQLException;
51
+ import java.sql.Statement;
52
+ import java.sql.Timestamp;
53
+ import java.sql.Types;
54
+ import java.text.DateFormat;
55
+ import java.text.SimpleDateFormat;
56
+ import java.util.ArrayList;
57
+ import java.util.Calendar;
58
+ import java.util.Date;
59
+ import java.util.List;
60
+ import org.jruby.RubyArray;
61
+ import org.jruby.RubyHash;
62
+ import org.jruby.RubyNumeric;
63
+ import org.jruby.RubyString;
64
+ import org.jruby.RubySymbol;
65
+ import org.jruby.RubyTime;
66
+ import org.jruby.exceptions.RaiseException;
67
+ import org.jruby.javasupport.Java;
68
+ import org.jruby.javasupport.JavaObject;
69
+ import org.jruby.runtime.Arity;
70
+ import org.jruby.runtime.Block;
71
+
72
+ /**
73
+ * Part of our ActiveRecord::ConnectionAdapters::Connection impl.
74
+ */
75
+ public class RubyJdbcConnection extends RubyObject {
76
+ private static final String[] TABLE_TYPE = new String[]{"TABLE"};
77
+
78
+ private static RubyObjectAdapter rubyApi;
79
+
80
+ protected RubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
81
+ super(runtime, metaClass);
82
+ }
83
+
84
+ public static RubyClass createJdbcConnectionClass(Ruby runtime) {
85
+ RubyClass jdbcConnection = getConnectionAdapters(runtime).defineClassUnder("JdbcConnection",
86
+ runtime.getObject(), JDBCCONNECTION_ALLOCATOR);
87
+ jdbcConnection.defineAnnotatedMethods(RubyJdbcConnection.class);
88
+
89
+ rubyApi = JavaEmbedUtils.newObjectAdapter();
90
+
91
+ return jdbcConnection;
92
+ }
93
+
94
+ private static ObjectAllocator JDBCCONNECTION_ALLOCATOR = new ObjectAllocator() {
95
+ public IRubyObject allocate(Ruby runtime, RubyClass klass) {
96
+ return new RubyJdbcConnection(runtime, klass);
97
+ }
98
+ };
99
+
100
+ protected static RubyModule getConnectionAdapters(Ruby runtime) {
101
+ return (RubyModule) runtime.getModule("ActiveRecord").getConstant("ConnectionAdapters");
102
+ }
103
+
104
+ @JRubyMethod(name = "begin")
105
+ public IRubyObject begin(ThreadContext context) throws SQLException {
106
+ getConnection(true).setAutoCommit(false);
107
+
108
+ return context.getRuntime().getNil();
109
+ }
110
+
111
+ @JRubyMethod(name = {"columns", "columns_internal"}, required = 1, optional = 2)
112
+ public IRubyObject columns_internal(final ThreadContext context, final IRubyObject[] args)
113
+ throws SQLException, IOException {
114
+ return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
115
+ public Object call(Connection c) throws SQLException {
116
+ ResultSet results = null;
117
+ try {
118
+ String table_name = rubyApi.convertToRubyString(args[0]).getUnicodeValue();
119
+ String schemaName = null;
120
+
121
+ int index = table_name.indexOf(".");
122
+ if(index != -1) {
123
+ schemaName = table_name.substring(0, index);
124
+ table_name = table_name.substring(index + 1);
125
+ }
126
+
127
+ DatabaseMetaData metadata = c.getMetaData();
128
+
129
+ if(args.length > 2) schemaName = args[2].toString();
130
+
131
+ if (schemaName != null) schemaName = caseConvertIdentifierForJdbc(metadata, schemaName);
132
+ table_name = caseConvertIdentifierForJdbc(metadata, table_name);
133
+
134
+ String[] tableTypes = new String[]{"TABLE","VIEW","SYNONYM"};
135
+ RubyArray matchingTables = (RubyArray) tableLookupBlock(context.getRuntime(),
136
+ c.getCatalog(), schemaName, table_name, tableTypes, false).call(c);
137
+ if (matchingTables.isEmpty()) {
138
+ throw new SQLException("Table " + table_name + " does not exist");
139
+ }
140
+
141
+ results = metadata.getColumns(c.getCatalog(),schemaName,table_name,null);
142
+ return unmarshal_columns(context, metadata, results);
143
+ } finally {
144
+ close(results);
145
+ }
146
+ }
147
+ });
148
+ }
149
+
150
+ @JRubyMethod(name = "commit")
151
+ public IRubyObject commit(ThreadContext context) throws SQLException {
152
+ Connection connection = getConnection(true);
153
+
154
+ if (!connection.getAutoCommit()) {
155
+ try {
156
+ connection.commit();
157
+ } finally {
158
+ connection.setAutoCommit(true);
159
+ }
160
+ }
161
+
162
+ return context.getRuntime().getNil();
163
+ }
164
+
165
+ @JRubyMethod(name = "connection", frame = false)
166
+ public IRubyObject connection() {
167
+ if (getConnection() == null) reconnect();
168
+
169
+ return getInstanceVariable("@connection");
170
+ }
171
+
172
+ @JRubyMethod(name = "database_name", frame = false)
173
+ public IRubyObject database_name(ThreadContext context) throws SQLException {
174
+ Connection connection = getConnection(true);
175
+ String name = connection.getCatalog();
176
+
177
+ if (null == name) {
178
+ name = connection.getMetaData().getUserName();
179
+
180
+ if (null == name) name = "db1";
181
+ }
182
+
183
+ return context.getRuntime().newString(name);
184
+ }
185
+
186
+ @JRubyMethod(name = "disconnect!", frame = false)
187
+ public IRubyObject disconnect() {
188
+ return setConnection(null);
189
+ }
190
+
191
+ @JRubyMethod(name = "execute_id_insert", required = 2)
192
+ public IRubyObject execute_id_insert(ThreadContext context, final IRubyObject sql,
193
+ final IRubyObject id) throws SQLException {
194
+ return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
195
+ public Object call(Connection c) throws SQLException {
196
+ PreparedStatement ps = c.prepareStatement(rubyApi.convertToRubyString(sql).getUnicodeValue());
197
+ try {
198
+ ps.setLong(1, RubyNumeric.fix2long(id));
199
+ ps.executeUpdate();
200
+ } finally {
201
+ close(ps);
202
+ }
203
+ return id;
204
+ }
205
+ });
206
+ }
207
+
208
+ @JRubyMethod(name = "execute_insert", required = 1)
209
+ public IRubyObject execute_insert(final ThreadContext context, final IRubyObject sql)
210
+ throws SQLException {
211
+ return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
212
+ public Object call(Connection c) throws SQLException {
213
+ Statement stmt = null;
214
+ try {
215
+ stmt = c.createStatement();
216
+ stmt.executeUpdate(rubyApi.convertToRubyString(sql).getUnicodeValue(), Statement.RETURN_GENERATED_KEYS);
217
+ return unmarshal_id_result(context.getRuntime(), stmt.getGeneratedKeys());
218
+ } finally {
219
+ close(stmt);
220
+ }
221
+ }
222
+ });
223
+ }
224
+
225
+ @JRubyMethod(name = "execute_query", required = 1)
226
+ public IRubyObject execute_query(final ThreadContext context, IRubyObject _sql)
227
+ throws SQLException, IOException {
228
+ String sql = rubyApi.convertToRubyString(_sql).getUnicodeValue();
229
+
230
+ return executeQuery(context, sql, 0);
231
+ }
232
+
233
+ @JRubyMethod(name = "execute_query", required = 2)
234
+ public IRubyObject execute_query(final ThreadContext context, IRubyObject _sql,
235
+ IRubyObject _maxRows) throws SQLException, IOException {
236
+ String sql = rubyApi.convertToRubyString(_sql).getUnicodeValue();
237
+ int maxrows = RubyNumeric.fix2int(_maxRows);
238
+
239
+ return executeQuery(context, sql, maxrows);
240
+ }
241
+
242
+ protected IRubyObject executeQuery(final ThreadContext context, final String query, final int maxRows) {
243
+ return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
244
+ public Object call(Connection c) throws SQLException {
245
+ Statement stmt = null;
246
+ try {
247
+ DatabaseMetaData metadata = c.getMetaData();
248
+ stmt = c.createStatement();
249
+ stmt.setMaxRows(maxRows);
250
+ return unmarshalResult(context, metadata, stmt.executeQuery(query), false);
251
+ } finally {
252
+ close(stmt);
253
+ }
254
+ }
255
+ });
256
+ }
257
+
258
+ @JRubyMethod(name = "execute_update", required = 1)
259
+ public IRubyObject execute_update(final ThreadContext context, final IRubyObject sql)
260
+ throws SQLException {
261
+ return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
262
+ public Object call(Connection c) throws SQLException {
263
+ Statement stmt = null;
264
+ try {
265
+ stmt = c.createStatement();
266
+ return context.getRuntime().newFixnum((long)stmt.executeUpdate(rubyApi.convertToRubyString(sql).getUnicodeValue()));
267
+ } finally {
268
+ close(stmt);
269
+ }
270
+ }
271
+ });
272
+ }
273
+
274
+ @JRubyMethod(name = "indexes")
275
+ public IRubyObject indexes(ThreadContext context, IRubyObject tableName, IRubyObject name, IRubyObject schemaName) {
276
+ return indexes(context, toStringOrNull(tableName), toStringOrNull(name), toStringOrNull(schemaName));
277
+ }
278
+
279
+ private static final int INDEX_TABLE_NAME = 3;
280
+ private static final int INDEX_NON_UNIQUE = 4;
281
+ private static final int INDEX_NAME = 6;
282
+ private static final int INDEX_COLUMN_NAME = 9;
283
+
284
+ /**
285
+ * Default JDBC introspection for index metadata on the JdbcConnection.
286
+ *
287
+ * JDBC index metadata is denormalized (multiple rows may be returned for
288
+ * one index, one row per column in the index), so a simple block-based
289
+ * filter like that used for tables doesn't really work here. Callers
290
+ * should filter the return from this method instead.
291
+ */
292
+ protected IRubyObject indexes(final ThreadContext context, final String tableNameArg, final String name, final String schemaNameArg) {
293
+ return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
294
+ public Object call(Connection c) throws SQLException {
295
+ Ruby runtime = context.getRuntime();
296
+ DatabaseMetaData metadata = c.getMetaData();
297
+ String tableName = caseConvertIdentifierForJdbc(metadata, tableNameArg);
298
+ String schemaName = caseConvertIdentifierForJdbc(metadata, schemaNameArg);
299
+
300
+ ResultSet resultSet = null;
301
+ List indexes = new ArrayList();
302
+ try {
303
+ resultSet = metadata.getIndexInfo(null, schemaName, tableName, false, false);
304
+ List primaryKeys = primaryKeys(context, tableName);
305
+ String currentIndex = null;
306
+ RubyModule indexDefinitionClass = getConnectionAdapters(runtime).getClass("IndexDefinition");
307
+
308
+ while (resultSet.next()) {
309
+ String indexName = resultSet.getString(INDEX_NAME);
310
+
311
+ if (indexName == null) continue;
312
+
313
+ indexName = caseConvertIdentifierForRails(metadata, indexName);
314
+
315
+ RubyString columnName = RubyString.newUnicodeString(runtime, caseConvertIdentifierForRails(metadata, resultSet.getString(INDEX_COLUMN_NAME)));
316
+
317
+ if (primaryKeys.contains(columnName)) continue;
318
+
319
+ // We are working on a new index
320
+ if (!indexName.equals(currentIndex)) {
321
+ currentIndex = indexName;
322
+
323
+ tableName = caseConvertIdentifierForRails(metadata, resultSet.getString(INDEX_TABLE_NAME));
324
+ boolean nonUnique = resultSet.getBoolean(INDEX_NON_UNIQUE);
325
+
326
+ IRubyObject indexDefinition = indexDefinitionClass.callMethod(context, "new",
327
+ new IRubyObject[] {
328
+ RubyString.newUnicodeString(runtime, tableName),
329
+ RubyString.newUnicodeString(runtime, indexName),
330
+ runtime.newBoolean(!nonUnique),
331
+ runtime.newArray()
332
+ });
333
+
334
+ // empty list for column names, we'll add to that in just a bit
335
+ indexes.add(indexDefinition);
336
+ }
337
+
338
+ // One or more columns can be associated with an index
339
+ IRubyObject lastIndex = (IRubyObject) indexes.get(indexes.size() - 1);
340
+
341
+ if (lastIndex != null) {
342
+ lastIndex.callMethod(context, "columns").callMethod(context, "<<", columnName);
343
+ }
344
+ }
345
+
346
+ return runtime.newArray(indexes);
347
+ } finally {
348
+ close(resultSet);
349
+ }
350
+ }
351
+ });
352
+ }
353
+
354
+ @JRubyMethod(name = "insert?", required = 1, meta = true, frame = false)
355
+ public static IRubyObject insert_p(ThreadContext context, IRubyObject recv, IRubyObject _sql) {
356
+ ByteList sql = rubyApi.convertToRubyString(_sql).getByteList();
357
+
358
+ return context.getRuntime().newBoolean(startsWithNoCaseCmp(sql, INSERT));
359
+ }
360
+
361
+ /*
362
+ * sql, values, types, name = nil, pk = nil, id_value = nil, sequence_name = nil
363
+ */
364
+ @JRubyMethod(name = "insert_bind", required = 3, rest = true)
365
+ public IRubyObject insert_bind(final ThreadContext context, final IRubyObject[] args) throws SQLException {
366
+ final Ruby runtime = context.getRuntime();
367
+ return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
368
+ public Object call(Connection c) throws SQLException {
369
+ PreparedStatement ps = null;
370
+ try {
371
+ ps = c.prepareStatement(rubyApi.convertToRubyString(args[0]).toString(), Statement.RETURN_GENERATED_KEYS);
372
+ setValuesOnPS(ps, context, args[1], args[2]);
373
+ ps.executeUpdate();
374
+ return unmarshal_id_result(runtime, ps.getGeneratedKeys());
375
+ } finally {
376
+ close(ps);
377
+ }
378
+ }
379
+ });
380
+ }
381
+
382
+ @JRubyMethod(name = "native_database_types", frame = false)
383
+ public IRubyObject native_database_types() {
384
+ return getInstanceVariable("@native_database_types");
385
+ }
386
+
387
+
388
+ @JRubyMethod(name = "primary_keys", required = 1)
389
+ public IRubyObject primary_keys(ThreadContext context, IRubyObject tableName) throws SQLException {
390
+ return context.getRuntime().newArray(primaryKeys(context, tableName.toString()));
391
+ }
392
+
393
+ protected List primaryKeys(final ThreadContext context, final String tableNameArg) {
394
+ return (List) withConnectionAndRetry(context, new SQLBlock() {
395
+ public Object call(Connection c) throws SQLException {
396
+ Ruby runtime = context.getRuntime();
397
+ DatabaseMetaData metadata = c.getMetaData();
398
+ String tableName = caseConvertIdentifierForJdbc(metadata, tableNameArg);
399
+ ResultSet resultSet = null;
400
+ List keyNames = new ArrayList();
401
+ try {
402
+ resultSet = metadata.getPrimaryKeys(null, null, tableName);
403
+
404
+ while (resultSet.next()) {
405
+ keyNames.add(RubyString.newUnicodeString(runtime,
406
+ caseConvertIdentifierForRails(metadata, resultSet.getString(4))));
407
+ }
408
+ } finally {
409
+ close(resultSet);
410
+ }
411
+
412
+ return keyNames;
413
+ }
414
+ });
415
+ }
416
+
417
+ @JRubyMethod(name = "reconnect!")
418
+ public IRubyObject reconnect() {
419
+ return setConnection(getConnectionFactory().newConnection());
420
+ }
421
+
422
+ @JRubyMethod(name = "rollback")
423
+ public IRubyObject rollback(ThreadContext context) throws SQLException {
424
+ Connection connection = getConnection(true);
425
+
426
+ if (!connection.getAutoCommit()) {
427
+ try {
428
+ connection.rollback();
429
+ } finally {
430
+ connection.setAutoCommit(true);
431
+ }
432
+ }
433
+
434
+ return context.getRuntime().getNil();
435
+ }
436
+
437
+ @JRubyMethod(name = "select?", required = 1, meta = true, frame = false)
438
+ public static IRubyObject select_p(ThreadContext context, IRubyObject recv, IRubyObject _sql) {
439
+ ByteList sql = rubyApi.convertToRubyString(_sql).getByteList();
440
+
441
+ return context.getRuntime().newBoolean(startsWithNoCaseCmp(sql, SELECT) ||
442
+ startsWithNoCaseCmp(sql, SHOW) || startsWithNoCaseCmp(sql, CALL));
443
+ }
444
+
445
+ @JRubyMethod(name = "set_native_database_types")
446
+ public IRubyObject set_native_database_types(ThreadContext context) throws SQLException, IOException {
447
+ Ruby runtime = context.getRuntime();
448
+ DatabaseMetaData metadata = getConnection(true).getMetaData();
449
+ IRubyObject types = unmarshalResult(context, metadata, metadata.getTypeInfo(), true);
450
+ IRubyObject typeConverter = getConnectionAdapters(runtime).getConstant("JdbcTypeConverter");
451
+ IRubyObject value = rubyApi.callMethod(rubyApi.callMethod(typeConverter, "new", types), "choose_best_types");
452
+ setInstanceVariable("@native_types", value);
453
+
454
+ return runtime.getNil();
455
+ }
456
+
457
+ @JRubyMethod(name = "tables")
458
+ public IRubyObject tables(ThreadContext context) {
459
+ return tables(context, null, null, null, TABLE_TYPE);
460
+ }
461
+
462
+ @JRubyMethod(name = "tables")
463
+ public IRubyObject tables(ThreadContext context, IRubyObject catalog) {
464
+ return tables(context, toStringOrNull(catalog), null, null, TABLE_TYPE);
465
+ }
466
+
467
+ @JRubyMethod(name = "tables")
468
+ public IRubyObject tables(ThreadContext context, IRubyObject catalog, IRubyObject schemaPattern) {
469
+ return tables(context, toStringOrNull(catalog), toStringOrNull(schemaPattern), null, TABLE_TYPE);
470
+ }
471
+
472
+ @JRubyMethod(name = "tables")
473
+ public IRubyObject tables(ThreadContext context, IRubyObject catalog, IRubyObject schemaPattern, IRubyObject tablePattern) {
474
+ return tables(context, toStringOrNull(catalog), toStringOrNull(schemaPattern), toStringOrNull(tablePattern), TABLE_TYPE);
475
+ }
476
+
477
+ @JRubyMethod(name = "tables", required = 4, rest = true)
478
+ public IRubyObject tables(ThreadContext context, IRubyObject[] args) {
479
+ return tables(context, toStringOrNull(args[0]), toStringOrNull(args[1]), toStringOrNull(args[2]), getTypes(args[3]));
480
+ }
481
+
482
+ protected IRubyObject tables(ThreadContext context, String catalog, String schemaPattern, String tablePattern, String[] types) {
483
+ return (IRubyObject) withConnectionAndRetry(context, tableLookupBlock(context.getRuntime(), catalog, schemaPattern, tablePattern, types, false));
484
+ }
485
+
486
+ /*
487
+ * sql, values, types, name = nil
488
+ */
489
+ @JRubyMethod(name = "update_bind", required = 3, rest = true)
490
+ public IRubyObject update_bind(final ThreadContext context, final IRubyObject[] args) throws SQLException {
491
+ final Ruby runtime = context.getRuntime();
492
+ Arity.checkArgumentCount(runtime, args, 3, 4);
493
+ return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
494
+ public Object call(Connection c) throws SQLException {
495
+ PreparedStatement ps = null;
496
+ try {
497
+ ps = c.prepareStatement(rubyApi.convertToRubyString(args[0]).toString());
498
+ setValuesOnPS(ps, context, args[1], args[2]);
499
+ ps.executeUpdate();
500
+ } finally {
501
+ close(ps);
502
+ }
503
+ return runtime.getNil();
504
+ }
505
+ });
506
+ }
507
+
508
+ @JRubyMethod(name = "with_connection_retry_guard", frame = true)
509
+ public IRubyObject with_connection_retry_guard(final ThreadContext context, final Block block) {
510
+ return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
511
+ public Object call(Connection c) throws SQLException {
512
+ return block.call(context, new IRubyObject[] { wrappedConnection(c) });
513
+ }
514
+ });
515
+ }
516
+
517
+ /*
518
+ * (is binary?, colname, tablename, primary key, id, value)
519
+ */
520
+ @JRubyMethod(name = "write_large_object", required = 6)
521
+ public IRubyObject write_large_object(ThreadContext context, final IRubyObject[] args)
522
+ throws SQLException, IOException {
523
+ final Ruby runtime = context.getRuntime();
524
+ return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
525
+ public Object call(Connection c) throws SQLException {
526
+ String sql = "UPDATE " + rubyApi.convertToRubyString(args[2])
527
+ + " SET " + rubyApi.convertToRubyString(args[1])
528
+ + " = ? WHERE " + rubyApi.convertToRubyString(args[3])
529
+ + "=" + rubyApi.convertToRubyString(args[4]);
530
+ PreparedStatement ps = null;
531
+ try {
532
+ ps = c.prepareStatement(sql);
533
+ if (args[0].isTrue()) { // binary
534
+ ByteList outp = rubyApi.convertToRubyString(args[5]).getByteList();
535
+ ps.setBinaryStream(1, new ByteArrayInputStream(outp.bytes,
536
+ outp.begin, outp.realSize), outp.realSize);
537
+ } else { // clob
538
+ String ss = rubyApi.convertToRubyString(args[5]).getUnicodeValue();
539
+ ps.setCharacterStream(1, new StringReader(ss), ss.length());
540
+ }
541
+ ps.executeUpdate();
542
+ } finally {
543
+ close(ps);
544
+ }
545
+ return runtime.getNil();
546
+ }
547
+ });
548
+ }
549
+
550
+ /**
551
+ * Convert an identifier coming back from the database to a case which Rails is expecting.
552
+ *
553
+ * Assumption: Rails identifiers will be quoted for mixed or will stay mixed
554
+ * as identifier names in Rails itself. Otherwise, they expect identifiers to
555
+ * be lower-case. Databases which store identifiers uppercase should be made
556
+ * lower-case.
557
+ *
558
+ * Assumption 2: It is always safe to convert all upper case names since it appears that
559
+ * some adapters do not report StoresUpper/Lower/Mixed correctly (am I right postgres/mysql?).
560
+ */
561
+ public static String caseConvertIdentifierForRails(DatabaseMetaData metadata, String value)
562
+ throws SQLException {
563
+ if (value == null) return null;
564
+
565
+ return metadata.storesUpperCaseIdentifiers() ? value.toLowerCase() : value;
566
+ }
567
+
568
+ /**
569
+ * Convert an identifier destined for a method which cares about the databases internal
570
+ * storage case. Methods like DatabaseMetaData.getPrimaryKeys() needs the table name to match
571
+ * the internal storage name. Arbtrary queries and the like DO NOT need to do this.
572
+ */
573
+ public static String caseConvertIdentifierForJdbc(DatabaseMetaData metadata, String value)
574
+ throws SQLException {
575
+ if (value == null) return null;
576
+
577
+ if (metadata.storesUpperCaseIdentifiers()) {
578
+ return value.toUpperCase();
579
+ } else if (metadata.storesLowerCaseIdentifiers()) {
580
+ return value.toLowerCase();
581
+ }
582
+
583
+ return value;
584
+ }
585
+
586
+ // helpers
587
+ protected static void close(Connection connection) {
588
+ if (connection != null) {
589
+ try {
590
+ connection.close();
591
+ } catch(Exception e) {}
592
+ }
593
+ }
594
+
595
+ public static void close(ResultSet resultSet) {
596
+ if (resultSet != null) {
597
+ try {
598
+ resultSet.close();
599
+ } catch(Exception e) {}
600
+ }
601
+ }
602
+
603
+ public static void close(Statement statement) {
604
+ if (statement != null) {
605
+ try {
606
+ statement.close();
607
+ } catch(Exception e) {}
608
+ }
609
+ }
610
+
611
+ protected IRubyObject config_value(ThreadContext context, String key) {
612
+ IRubyObject config_hash = getInstanceVariable("@config");
613
+
614
+ return config_hash.callMethod(context, "[]", context.getRuntime().newSymbol(key));
615
+ }
616
+
617
+ private static String toStringOrNull(IRubyObject arg) {
618
+ return arg.isNil() ? null : arg.toString();
619
+ }
620
+
621
+ private static IRubyObject floatToRuby(Ruby runtime, ResultSet resultSet, float floatValue)
622
+ throws SQLException, IOException {
623
+ if (floatValue == 0 && resultSet.wasNull()) return runtime.getNil();
624
+
625
+ return runtime.newFloat(floatValue);
626
+ }
627
+
628
+ protected Connection getConnection() {
629
+ return getConnection(false);
630
+ }
631
+
632
+ protected Connection getConnection(boolean error) {
633
+ Connection conn = (Connection) dataGetStruct();
634
+ if(error && conn == null) {
635
+ RubyClass err = getRuntime().getModule("ActiveRecord").getClass("ConnectionNotEstablished");
636
+ throw new RaiseException(getRuntime(), err, "no connection available", false);
637
+ }
638
+ return conn;
639
+ }
640
+
641
+ protected JdbcConnectionFactory getConnectionFactory() throws RaiseException {
642
+ IRubyObject connection_factory = getInstanceVariable("@connection_factory");
643
+ JdbcConnectionFactory factory = null;
644
+ try {
645
+ factory = (JdbcConnectionFactory) ((JavaObject) rubyApi.getInstanceVariable(connection_factory, "@java_object")).getValue();
646
+ } catch (Exception e) {
647
+ factory = null;
648
+ }
649
+ if (factory == null) {
650
+ throw getRuntime().newRuntimeError("@connection_factory not set properly");
651
+ }
652
+ return factory;
653
+ }
654
+
655
+ private static String[] getTypes(IRubyObject typeArg) {
656
+ if (!(typeArg instanceof RubyArray)) return new String[] { typeArg.toString() };
657
+
658
+ IRubyObject[] arr = rubyApi.convertToJavaArray(typeArg);
659
+ String[] types = new String[arr.length];
660
+ for (int i = 0; i < types.length; i++) {
661
+ types[i] = arr[i].toString();
662
+ }
663
+
664
+ return types;
665
+ }
666
+
667
+ private static int getTypeValueFor(Ruby runtime, IRubyObject type) throws SQLException {
668
+ // How could this ever yield anything useful?
669
+ if (!(type instanceof RubySymbol)) type = rubyApi.callMethod(type, "class");
670
+
671
+ // Assumption; If this is a symbol then it will be backed by an interned string. (enebo)
672
+ String internedValue = type.asJavaString();
673
+
674
+ if(internedValue == "string") {
675
+ return Types.VARCHAR;
676
+ } else if(internedValue == "text") {
677
+ return Types.CLOB;
678
+ } else if(internedValue == "integer") {
679
+ return Types.INTEGER;
680
+ } else if(internedValue == "decimal") {
681
+ return Types.DECIMAL;
682
+ } else if(internedValue == "float") {
683
+ return Types.FLOAT;
684
+ } else if(internedValue == "datetime") {
685
+ return Types.TIMESTAMP;
686
+ } else if(internedValue == "timestamp") {
687
+ return Types.TIMESTAMP;
688
+ } else if(internedValue == "time") {
689
+ return Types.TIME;
690
+ } else if(internedValue == "date") {
691
+ return Types.DATE;
692
+ } else if(internedValue == "binary") {
693
+ return Types.BLOB;
694
+ } else if(internedValue == "boolean") {
695
+ return Types.BOOLEAN;
696
+ } else {
697
+ return -1;
698
+ }
699
+ }
700
+
701
+ private boolean isConnectionBroken(ThreadContext context, Connection c) {
702
+ try {
703
+ IRubyObject alive = config_value(context, "connection_alive_sql");
704
+ if (select_p(context, this, alive).isTrue()) {
705
+ String connectionSQL = rubyApi.convertToRubyString(alive).toString();
706
+ Statement s = c.createStatement();
707
+ try {
708
+ s.execute(connectionSQL);
709
+ } finally {
710
+ close(s);
711
+ }
712
+ return false;
713
+ } else {
714
+ return !c.isClosed();
715
+ }
716
+ } catch (SQLException sx) {
717
+ return true;
718
+ }
719
+ }
720
+
721
+ private static IRubyObject integerToRuby(Ruby runtime, ResultSet resultSet, long longValue)
722
+ throws SQLException, IOException {
723
+ if (longValue == 0 && resultSet.wasNull()) return runtime.getNil();
724
+
725
+ return runtime.newFixnum(longValue);
726
+ }
727
+
728
+ private static IRubyObject jdbcToRuby(Ruby runtime, int column, int type, ResultSet resultSet)
729
+ throws SQLException {
730
+ try {
731
+ switch (type) {
732
+ case Types.BINARY: case Types.BLOB: case Types.LONGVARBINARY: case Types.VARBINARY:
733
+ case Types.LONGVARCHAR:
734
+ return streamToRuby(runtime, resultSet, resultSet.getBinaryStream(column));
735
+ case Types.CLOB:
736
+ return readerToRuby(runtime, resultSet, resultSet.getCharacterStream(column));
737
+ case Types.TIMESTAMP:
738
+ return timestampToRuby(runtime, resultSet, resultSet.getTimestamp(column));
739
+ case Types.INTEGER: case Types.SMALLINT: case Types.TINYINT:
740
+ return integerToRuby(runtime, resultSet, resultSet.getLong(column));
741
+ case Types.REAL:
742
+ return floatToRuby(runtime, resultSet, resultSet.getFloat(column));
743
+ default:
744
+ return stringToRuby(runtime, resultSet, resultSet.getString(column));
745
+ }
746
+ } catch (IOException ioe) {
747
+ throw (SQLException) new SQLException(ioe.getMessage()).initCause(ioe);
748
+ }
749
+ }
750
+
751
+ private static void populateFromResultSet(ThreadContext context, Ruby runtime, List results,
752
+ ResultSet resultSet, ColumnData[] columns) throws SQLException {
753
+ int columnCount = columns.length;
754
+
755
+ while (resultSet.next()) {
756
+ RubyHash row = RubyHash.newHash(runtime);
757
+
758
+ for (int i = 0; i < columnCount; i++) {
759
+ row.op_aset(context, columns[i].name, jdbcToRuby(runtime, i + 1, columns[i].type, resultSet));
760
+ }
761
+ results.add(row);
762
+ }
763
+ }
764
+
765
+
766
+ private static IRubyObject readerToRuby(Ruby runtime, ResultSet resultSet, Reader reader)
767
+ throws SQLException, IOException {
768
+ if (reader == null && resultSet.wasNull()) return runtime.getNil();
769
+
770
+ StringBuffer str = new StringBuffer(2048);
771
+ try {
772
+ char[] buf = new char[2048];
773
+
774
+ for (int n = reader.read(buf); n != -1; n = reader.read(buf)) {
775
+ str.append(buf, 0, n);
776
+ }
777
+ } finally {
778
+ reader.close();
779
+ }
780
+
781
+ return RubyString.newUnicodeString(runtime, str.toString());
782
+ }
783
+
784
+ private IRubyObject setConnection(Connection c) {
785
+ close(getConnection()); // Close previously open connection if there is one
786
+
787
+ IRubyObject rubyconn = c != null ? wrappedConnection(c) : getRuntime().getNil();
788
+ setInstanceVariable("@connection", rubyconn);
789
+ dataWrapStruct(c);
790
+ return this;
791
+ }
792
+
793
+ private final static DateFormat FORMAT = new SimpleDateFormat("%y-%M-%d %H:%m:%s");
794
+
795
+ private static void setValue(PreparedStatement ps, int index, ThreadContext context,
796
+ IRubyObject value, IRubyObject type) throws SQLException {
797
+ final int tp = getTypeValueFor(context.getRuntime(), type);
798
+ if(value.isNil()) {
799
+ ps.setNull(index, tp);
800
+ return;
801
+ }
802
+
803
+ switch(tp) {
804
+ case Types.VARCHAR:
805
+ case Types.CLOB:
806
+ ps.setString(index, RubyString.objAsString(context, value).toString());
807
+ break;
808
+ case Types.INTEGER:
809
+ ps.setLong(index, RubyNumeric.fix2long(value));
810
+ break;
811
+ case Types.FLOAT:
812
+ ps.setDouble(index, ((RubyNumeric)value).getDoubleValue());
813
+ break;
814
+ case Types.TIMESTAMP:
815
+ case Types.TIME:
816
+ case Types.DATE:
817
+ if(!(value instanceof RubyTime)) {
818
+ try {
819
+ Date dd = FORMAT.parse(RubyString.objAsString(context, value).toString());
820
+ ps.setTimestamp(index, new java.sql.Timestamp(dd.getTime()), Calendar.getInstance());
821
+ } catch(Exception e) {
822
+ ps.setString(index, RubyString.objAsString(context, value).toString());
823
+ }
824
+ } else {
825
+ RubyTime rubyTime = (RubyTime) value;
826
+ java.util.Date date = rubyTime.getJavaDate();
827
+ long millis = date.getTime();
828
+ long micros = rubyTime.microseconds() - millis / 1000;
829
+ java.sql.Timestamp ts = new java.sql.Timestamp(millis);
830
+ java.util.Calendar cal = Calendar.getInstance();
831
+ cal.setTime(date);
832
+ ts.setNanos((int)(micros * 1000));
833
+ ps.setTimestamp(index, ts, cal);
834
+ }
835
+ break;
836
+ case Types.BOOLEAN:
837
+ ps.setBoolean(index, value.isTrue());
838
+ break;
839
+ default: throw new RuntimeException("type " + type + " not supported in _bind yet");
840
+ }
841
+ }
842
+
843
+ private static void setValuesOnPS(PreparedStatement ps, ThreadContext context,
844
+ IRubyObject valuesArg, IRubyObject typesArg) throws SQLException {
845
+ RubyArray values = (RubyArray) valuesArg;
846
+ RubyArray types = (RubyArray) typesArg;
847
+
848
+ for(int i=0, j=values.getLength(); i<j; i++) {
849
+ setValue(ps, i+1, context, values.eltInternal(i), types.eltInternal(i));
850
+ }
851
+ }
852
+
853
+ private static IRubyObject streamToRuby(Ruby runtime, ResultSet resultSet, InputStream is)
854
+ throws SQLException, IOException {
855
+ if (is == null && resultSet.wasNull()) return runtime.getNil();
856
+
857
+ ByteList str = new ByteList(2048);
858
+ try {
859
+ byte[] buf = new byte[2048];
860
+
861
+ for (int n = is.read(buf); n != -1; n = is.read(buf)) {
862
+ str.append(buf, 0, n);
863
+ }
864
+ } finally {
865
+ is.close();
866
+ }
867
+
868
+ return runtime.newString(str);
869
+ }
870
+
871
+ private static IRubyObject stringToRuby(Ruby runtime, ResultSet resultSet, String string)
872
+ throws SQLException, IOException {
873
+ if (string == null && resultSet.wasNull()) return runtime.getNil();
874
+
875
+ return RubyString.newUnicodeString(runtime, string);
876
+ }
877
+
878
+ private static final int TABLE_NAME = 3;
879
+
880
+ protected SQLBlock tableLookupBlock(final Ruby runtime,
881
+ final String catalog, final String schemapat,
882
+ final String tablepat, final String[] types, final boolean downCase) {
883
+ return new SQLBlock() {
884
+ public Object call(Connection c) throws SQLException {
885
+ ResultSet rs = null;
886
+ try {
887
+ DatabaseMetaData metadata = c.getMetaData();
888
+ String clzName = metadata.getClass().getName().toLowerCase();
889
+ boolean isOracle = clzName.indexOf("oracle") != -1 || clzName.indexOf("oci") != -1;
890
+
891
+ String realschema = schemapat;
892
+ String realtablepat = tablepat;
893
+
894
+ if (realtablepat != null) realtablepat = caseConvertIdentifierForJdbc(metadata, realtablepat);
895
+ if (realschema != null) realschema = caseConvertIdentifierForJdbc(metadata, realschema);
896
+
897
+ rs = metadata.getTables(catalog, realschema, realtablepat, types);
898
+ List arr = new ArrayList();
899
+ while (rs.next()) {
900
+ String name;
901
+
902
+ if (downCase) {
903
+ name = rs.getString(TABLE_NAME).toLowerCase();
904
+ } else {
905
+ name = caseConvertIdentifierForRails(metadata, rs.getString(TABLE_NAME));
906
+ }
907
+ // Handle stupid Oracle 10g RecycleBin feature
908
+ if (!isOracle || !name.startsWith("bin$")) {
909
+ arr.add(RubyString.newUnicodeString(runtime, name));
910
+ }
911
+ }
912
+ return runtime.newArray(arr);
913
+ } finally {
914
+ close(rs);
915
+ }
916
+ }
917
+ };
918
+ }
919
+
920
+ private static IRubyObject timestampToRuby(Ruby runtime, ResultSet resultSet, Timestamp time)
921
+ throws SQLException, IOException {
922
+ if (time == null && resultSet.wasNull()) return runtime.getNil();
923
+
924
+ String str = time.toString();
925
+ if (str.endsWith(" 00:00:00.0")) {
926
+ str = str.substring(0, str.length() - (" 00:00:00.0".length()));
927
+ }
928
+
929
+ return RubyString.newUnicodeString(runtime, str);
930
+ }
931
+
932
+ private static final int COLUMN_NAME = 4;
933
+ private static final int DATA_TYPE = 5;
934
+ private static final int TYPE_NAME = 6;
935
+ private static final int COLUMN_SIZE = 7;
936
+ private static final int DECIMAL_DIGITS = 9;
937
+ private static final int COLUMN_DEF = 13;
938
+ private static final int IS_NULLABLE = 18;
939
+
940
+ private int intFromResultSet(ResultSet resultSet, int column) throws SQLException {
941
+ int precision = resultSet.getInt(column);
942
+
943
+ return precision == 0 && resultSet.wasNull() ? -1 : precision;
944
+ }
945
+
946
+ /**
947
+ * Create a string which represents a sql type usable by Rails from the resultSet column
948
+ * metadata object.
949
+ *
950
+ * @param numberAsBoolean the database uses decimal as a boolean data type
951
+ * because it does not support optional SQL92 type or mandatory SQL99
952
+ * booleans.
953
+ */
954
+ private String typeFromResultSet(ResultSet resultSet, boolean numberAsBoolean) throws SQLException {
955
+ int precision = intFromResultSet(resultSet, COLUMN_SIZE);
956
+ int scale = intFromResultSet(resultSet, DECIMAL_DIGITS);
957
+
958
+ // Assume db's which use decimal for boolean will not also specify a
959
+ // valid precision 1 decimal also. Seems sketchy to me...
960
+ if (numberAsBoolean && precision != 1 &&
961
+ resultSet.getInt(DATA_TYPE) == java.sql.Types.DECIMAL) precision = -1;
962
+
963
+ String type = resultSet.getString(TYPE_NAME);
964
+ if (precision > 0) {
965
+ type += "(" + precision;
966
+ if(scale > 0) type += "," + scale;
967
+ type += ")";
968
+ }
969
+
970
+ return type;
971
+ }
972
+
973
+ private IRubyObject defaultValueFromResultSet(Ruby runtime, ResultSet resultSet)
974
+ throws SQLException {
975
+ String defaultValue = resultSet.getString(COLUMN_DEF);
976
+
977
+ return defaultValue == null ? runtime.getNil() : RubyString.newUnicodeString(runtime, defaultValue);
978
+ }
979
+
980
+ private IRubyObject unmarshal_columns(ThreadContext context, DatabaseMetaData metadata,
981
+ ResultSet rs) throws SQLException {
982
+ try {
983
+ Ruby runtime = context.getRuntime();
984
+ List columns = new ArrayList();
985
+ String clzName = metadata.getClass().getName().toLowerCase();
986
+ boolean isOracle = clzName.indexOf("oracle") != -1 || clzName.indexOf("oci") != -1;
987
+
988
+ RubyHash types = (RubyHash) native_database_types();
989
+ IRubyObject jdbcCol = getConnectionAdapters(runtime).getConstant("JdbcColumn");
990
+
991
+ while(rs.next()) {
992
+ IRubyObject column = jdbcCol.callMethod(context, "new",
993
+ new IRubyObject[] {
994
+ getInstanceVariable("@config"),
995
+ RubyString.newUnicodeString(runtime,
996
+ caseConvertIdentifierForRails(metadata, rs.getString(COLUMN_NAME))),
997
+ defaultValueFromResultSet(runtime, rs),
998
+ RubyString.newUnicodeString(runtime, typeFromResultSet(rs, isOracle)),
999
+ runtime.newBoolean(!rs.getString(IS_NULLABLE).trim().equals("NO"))
1000
+ });
1001
+ columns.add(column);
1002
+
1003
+ IRubyObject tp = (IRubyObject)types.fastARef(column.callMethod(context,"type"));
1004
+ if(tp != null && !tp.isNil() && tp.callMethod(context, "[]", runtime.newSymbol("limit")).isNil()) {
1005
+ column.callMethod(context, "limit=", runtime.getNil());
1006
+ if(!column.callMethod(context, "type").equals(runtime.newSymbol("decimal"))) {
1007
+ column.callMethod(context, "precision=", runtime.getNil());
1008
+ }
1009
+ }
1010
+ }
1011
+ return runtime.newArray(columns);
1012
+ } finally {
1013
+ close(rs);
1014
+ }
1015
+ }
1016
+
1017
+
1018
+ public static IRubyObject unmarshal_id_result(Ruby runtime, ResultSet rs) throws SQLException {
1019
+ try {
1020
+ if (rs.next() && rs.getMetaData().getColumnCount() > 0) {
1021
+ return runtime.newFixnum(rs.getLong(1));
1022
+ }
1023
+ return runtime.getNil();
1024
+ } finally {
1025
+ close(rs);
1026
+ }
1027
+ }
1028
+
1029
+ /**
1030
+ * Converts a jdbc resultset into an array (rows) of hashes (row) that AR expects.
1031
+ *
1032
+ * @param downCase should column names only be in lower case?
1033
+ */
1034
+ protected static IRubyObject unmarshalResult(ThreadContext context, DatabaseMetaData metadata,
1035
+ ResultSet resultSet, boolean downCase) throws SQLException {
1036
+ Ruby runtime = context.getRuntime();
1037
+ List results = new ArrayList();
1038
+
1039
+ try {
1040
+ ColumnData[] columns = ColumnData.setup(runtime, metadata, resultSet.getMetaData(), downCase);
1041
+
1042
+ populateFromResultSet(context, runtime, results, resultSet, columns);
1043
+ } finally {
1044
+ close(resultSet);
1045
+ }
1046
+
1047
+ return runtime.newArray(results);
1048
+ }
1049
+
1050
+ protected Object withConnectionAndRetry(ThreadContext context, SQLBlock block) {
1051
+ int tries = 1;
1052
+ int i = 0;
1053
+ Throwable toWrap = null;
1054
+ boolean autoCommit = false;
1055
+ while (i < tries) {
1056
+ Connection c = getConnection(true);
1057
+ try {
1058
+ autoCommit = c.getAutoCommit();
1059
+ return block.call(c);
1060
+ } catch (Exception e) {
1061
+ toWrap = e;
1062
+ while (toWrap.getCause() != null && toWrap.getCause() != toWrap) {
1063
+ toWrap = toWrap.getCause();
1064
+ }
1065
+ i++;
1066
+ if (autoCommit) {
1067
+ if (i == 1) {
1068
+ tries = (int) rubyApi.convertToRubyInteger(config_value(context, "retry_count")).getLongValue();
1069
+ if (tries <= 0) {
1070
+ tries = 1;
1071
+ }
1072
+ }
1073
+ if (isConnectionBroken(context, c)) {
1074
+ reconnect();
1075
+ } else {
1076
+ throw wrap(context, toWrap);
1077
+ }
1078
+ }
1079
+ }
1080
+ }
1081
+ throw wrap(context, toWrap);
1082
+ }
1083
+
1084
+ private static RuntimeException wrap(ThreadContext context, Throwable exception) {
1085
+ RubyClass err = context.getRuntime().getModule("ActiveRecord").getClass("ActiveRecordError");
1086
+ return (RuntimeException) new RaiseException(context.getRuntime(), err, exception.getMessage(), false).initCause(exception);
1087
+ }
1088
+
1089
+ private IRubyObject wrappedConnection(Connection c) {
1090
+ return Java.java_to_ruby(this, JavaObject.wrap(getRuntime(), c), Block.NULL_BLOCK);
1091
+ }
1092
+
1093
+ private static int whitespace(int start, ByteList bl) {
1094
+ int end = bl.begin + bl.realSize;
1095
+
1096
+ for (int i = start; i < end; i++) {
1097
+ if (!Character.isWhitespace(bl.bytes[i])) return i;
1098
+ }
1099
+
1100
+ return end;
1101
+ }
1102
+
1103
+ private static byte[] CALL = new byte[]{'c', 'a', 'l', 'l'};
1104
+ private static byte[] INSERT = new byte[] {'i', 'n', 's', 'e', 'r', 't'};
1105
+ private static byte[] SELECT = new byte[] {'s', 'e', 'l', 'e', 'c', 't'};
1106
+ private static byte[] SHOW = new byte[] {'s', 'h', 'o', 'w'};
1107
+
1108
+ private static boolean startsWithNoCaseCmp(ByteList bytelist, byte[] compare) {
1109
+ int p = whitespace(bytelist.begin, bytelist);
1110
+
1111
+ // What the hell is this for?
1112
+ if (bytelist.bytes[p] == '(') p = whitespace(p, bytelist);
1113
+
1114
+ for (int i = 0; i < bytelist.realSize && i < compare.length; i++) {
1115
+ if (Character.toLowerCase(bytelist.bytes[p + i]) != compare[i]) return false;
1116
+ }
1117
+
1118
+ return true;
1119
+ }
1120
+
1121
+ public static class ColumnData {
1122
+ public IRubyObject name;
1123
+ public int type;
1124
+
1125
+ public ColumnData(IRubyObject name, int type) {
1126
+ this.name = name;
1127
+ this.type = type;
1128
+ }
1129
+
1130
+ public static ColumnData[] setup(Ruby runtime, DatabaseMetaData databaseMetadata,
1131
+ ResultSetMetaData metadata, boolean downCase) throws SQLException {
1132
+ int columnsCount = metadata.getColumnCount();
1133
+ ColumnData[] columns = new ColumnData[columnsCount];
1134
+
1135
+ for (int i = 1; i <= columnsCount; i++) { // metadata is one-based
1136
+ String name;
1137
+ if (downCase) {
1138
+ name = metadata.getColumnLabel(i).toLowerCase();
1139
+ } else {
1140
+ name = RubyJdbcConnection.caseConvertIdentifierForRails(databaseMetadata, metadata.getColumnLabel(i));
1141
+ }
1142
+
1143
+ columns[i - 1] = new ColumnData(RubyString.newUnicodeString(runtime, name), metadata.getColumnType(i));
1144
+ }
1145
+
1146
+ return columns;
1147
+ }
1148
+ }
1149
+ }