activerecord-jdbc-adapter 0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/History.txt +61 -0
  2. data/LICENSE +21 -0
  3. data/Manifest.txt +64 -0
  4. data/README.txt +116 -0
  5. data/Rakefile +146 -0
  6. data/lib/active_record/connection_adapters/derby_adapter.rb +13 -0
  7. data/lib/active_record/connection_adapters/h2_adapter.rb +1 -0
  8. data/lib/active_record/connection_adapters/hsqldb_adapter.rb +13 -0
  9. data/lib/active_record/connection_adapters/jdbc_adapter.rb +575 -0
  10. data/lib/active_record/connection_adapters/jdbc_adapter_spec.rb +10 -0
  11. data/lib/active_record/connection_adapters/jndi_adapter.rb +1 -0
  12. data/lib/active_record/connection_adapters/mysql_adapter.rb +13 -0
  13. data/lib/active_record/connection_adapters/oracle_adapter.rb +1 -0
  14. data/lib/active_record/connection_adapters/postgresql_adapter.rb +13 -0
  15. data/lib/jdbc_adapter.rb +32 -0
  16. data/lib/jdbc_adapter/jdbc_db2.rb +104 -0
  17. data/lib/jdbc_adapter/jdbc_derby.rb +362 -0
  18. data/lib/jdbc_adapter/jdbc_firebird.rb +109 -0
  19. data/lib/jdbc_adapter/jdbc_hsqldb.rb +168 -0
  20. data/lib/jdbc_adapter/jdbc_mimer.rb +134 -0
  21. data/lib/jdbc_adapter/jdbc_mssql.rb +356 -0
  22. data/lib/jdbc_adapter/jdbc_mysql.rb +168 -0
  23. data/lib/jdbc_adapter/jdbc_oracle.rb +340 -0
  24. data/lib/jdbc_adapter/jdbc_postgre.rb +347 -0
  25. data/lib/jdbc_adapter/missing_functionality_helper.rb +72 -0
  26. data/lib/jdbc_adapter/version.rb +5 -0
  27. data/lib/jdbc_adapter_internal.jar +0 -0
  28. data/lib/tasks/jdbc_databases.rake +72 -0
  29. data/src/java/JDBCDerbySpec.java +323 -0
  30. data/src/java/JDBCMySQLSpec.java +89 -0
  31. data/src/java/JdbcAdapterInternalService.java +953 -0
  32. data/test/activerecord/connection_adapters/type_conversion_test.rb +31 -0
  33. data/test/activerecord/connections/native_jdbc_mysql/connection.rb +25 -0
  34. data/test/db/derby.rb +18 -0
  35. data/test/db/h2.rb +11 -0
  36. data/test/db/hsqldb.rb +15 -0
  37. data/test/db/jdbc.rb +11 -0
  38. data/test/db/jndi_config.rb +30 -0
  39. data/test/db/logger.rb +3 -0
  40. data/test/db/mysql.rb +9 -0
  41. data/test/db/postgres.rb +9 -0
  42. data/test/derby_multibyte_test.rb +12 -0
  43. data/test/derby_simple_test.rb +12 -0
  44. data/test/generic_jdbc_connection_test.rb +9 -0
  45. data/test/h2_simple_test.rb +7 -0
  46. data/test/hsqldb_simple_test.rb +6 -0
  47. data/test/jdbc_adapter/jdbc_db2_test.rb +21 -0
  48. data/test/jdbc_common.rb +6 -0
  49. data/test/jndi_test.rb +37 -0
  50. data/test/manualTestDatabase.rb +195 -0
  51. data/test/minirunit.rb +109 -0
  52. data/test/minirunit/testConnect.rb +14 -0
  53. data/test/minirunit/testH2.rb +73 -0
  54. data/test/minirunit/testHsqldb.rb +73 -0
  55. data/test/minirunit/testLoadActiveRecord.rb +3 -0
  56. data/test/minirunit/testMysql.rb +83 -0
  57. data/test/minirunit/testRawSelect.rb +24 -0
  58. data/test/models/auto_id.rb +18 -0
  59. data/test/models/data_types.rb +18 -0
  60. data/test/models/entry.rb +20 -0
  61. data/test/mysql_multibyte_test.rb +6 -0
  62. data/test/mysql_simple_test.rb +13 -0
  63. data/test/postgres_simple_test.rb +12 -0
  64. data/test/simple.rb +157 -0
  65. metadata +112 -0
@@ -0,0 +1,323 @@
1
+ /***** BEGIN LICENSE BLOCK *****
2
+ * Version: CPL 1.0/GPL 2.0/LGPL 2.1
3
+ *
4
+ * The contents of this file are subject to the Common Public
5
+ * License Version 1.0 (the "License"); you may not use this file
6
+ * except in compliance with the License. You may obtain a copy of
7
+ * the License at http://www.eclipse.org/legal/cpl-v10.html
8
+ *
9
+ * Software distributed under the License is distributed on an "AS
10
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
11
+ * implied. See the License for the specific language governing
12
+ * rights and limitations under the License.
13
+ *
14
+ * Copyright (C) 2007 Ola Bini <ola.bini@gmail.com>
15
+ *
16
+ * Alternatively, the contents of this file may be used under the terms of
17
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
18
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
19
+ * in which case the provisions of the GPL or the LGPL are applicable instead
20
+ * of those above. If you wish to allow use of your version of this file only
21
+ * under the terms of either the GPL or the LGPL, and not to allow others to
22
+ * use your version of this file under the terms of the CPL, indicate your
23
+ * decision by deleting the provisions above and replace them with the notice
24
+ * and other provisions required by the GPL or the LGPL. If you do not delete
25
+ * the provisions above, a recipient may use your version of this file under
26
+ * the terms of any one of the CPL, the GPL or the LGPL.
27
+ ***** END LICENSE BLOCK *****/
28
+
29
+ import org.jruby.Ruby;
30
+ import org.jruby.RubyModule;
31
+ import org.jruby.RubyString;
32
+ import org.jruby.RubyFloat;
33
+ import org.jruby.RubyFixnum;
34
+ import org.jruby.RubyBignum;
35
+ import org.jruby.RubyBoolean;
36
+ import org.jruby.RubyBigDecimal;
37
+ import org.jruby.RubyRange;
38
+ import org.jruby.RubyNumeric;
39
+
40
+ import org.jruby.runtime.Arity;
41
+ import org.jruby.runtime.CallbackFactory;
42
+ import org.jruby.runtime.MethodIndex;
43
+ import org.jruby.runtime.ThreadContext;
44
+ import org.jruby.runtime.builtin.IRubyObject;
45
+
46
+ import org.jruby.util.ByteList;
47
+
48
+ import java.sql.SQLException;
49
+
50
+ public class JDBCDerbySpec {
51
+ public static void load(Ruby runtime, RubyModule jdbcSpec) {
52
+ RubyModule derby = jdbcSpec.defineModuleUnder("Derby");
53
+ CallbackFactory cf = runtime.callbackFactory(JDBCDerbySpec.class);
54
+ derby.defineFastMethod("quote_string",cf.getFastSingletonMethod("quote_string",IRubyObject.class));
55
+ derby.defineFastMethod("quote",cf.getFastOptSingletonMethod("quote"));
56
+ derby.defineFastMethod("_execute",cf.getFastOptSingletonMethod("_execute"));
57
+ derby.defineFastMethod("add_limit_offset!",cf.getFastSingletonMethod("add_limit_offset", IRubyObject.class, IRubyObject.class));
58
+ derby.defineFastMethod("select_all",cf.getFastOptSingletonMethod("select_all"));
59
+ derby.defineFastMethod("select_one",cf.getFastOptSingletonMethod("select_one"));
60
+ RubyModule col = derby.defineModuleUnder("Column");
61
+ col.defineFastMethod("type_cast",cf.getFastSingletonMethod("type_cast", IRubyObject.class));
62
+ }
63
+
64
+ /*
65
+ * JdbcSpec::Derby::Column.type_cast(value)
66
+ */
67
+ public static IRubyObject type_cast(IRubyObject recv, IRubyObject value) {
68
+ Ruby runtime = recv.getRuntime();
69
+
70
+ if(value.isNil() || ((value instanceof RubyString) && value.toString().trim().equalsIgnoreCase("null"))) {
71
+ return runtime.getNil();
72
+ }
73
+
74
+ String type = recv.getInstanceVariable("@type").toString();
75
+
76
+ switch(type.charAt(0)) {
77
+ case 's': //string
78
+ return value;
79
+ case 't': //text, timestamp, time
80
+ if(type.equals("text")) {
81
+ return value;
82
+ } else {
83
+ return recv.callMethod(runtime.getCurrentContext(), "cast_to_time", value);
84
+ }
85
+ case 'i': //integer
86
+ case 'p': //primary key
87
+ if(value.respondsTo("to_i")) {
88
+ return value.callMethod(runtime.getCurrentContext(), "to_i");
89
+ } else {
90
+ return runtime.newFixnum( value.isTrue() ? 1 : 0 );
91
+ }
92
+ case 'd': //decimal, datetime, date
93
+ if(type.equals("datetime")) {
94
+ return recv.callMethod(runtime.getCurrentContext(), "cast_to_date_or_time", value);
95
+ } else if(type.equals("date")) {
96
+ return recv.getMetaClass().callMethod(runtime.getCurrentContext(), "string_to_date", value);
97
+ } else {
98
+ return recv.getMetaClass().callMethod(runtime.getCurrentContext(), "value_to_decimal", value);
99
+ }
100
+ case 'f': //float
101
+ return value.callMethod(runtime.getCurrentContext(),"to_f");
102
+ case 'b': //binary, boolean
103
+ if(type.equals("binary")) {
104
+ return recv.callMethod(runtime.getCurrentContext(), "value_to_binary", value);
105
+ } else {
106
+ return recv.getMetaClass().callMethod(runtime.getCurrentContext(), "value_to_boolean", value);
107
+ }
108
+ }
109
+ return value;
110
+ }
111
+
112
+ public static IRubyObject quote(IRubyObject recv, IRubyObject[] args) {
113
+ Ruby runtime = recv.getRuntime();
114
+ Arity.checkArgumentCount(runtime, args, 1, 2);
115
+ IRubyObject value = args[0];
116
+ if(args.length > 1) {
117
+ IRubyObject col = args[1];
118
+ IRubyObject type = col.callMethod(runtime.getCurrentContext(),"type");
119
+ if(value instanceof RubyString) {
120
+ if(type == runtime.newSymbol("string")) {
121
+ return quote_string_with_surround(runtime, "'", (RubyString)value, "'");
122
+ } else if(type == runtime.newSymbol("text")) {
123
+ return quote_string_with_surround(runtime, "CAST('", (RubyString)value, "' AS CLOB)");
124
+ } else if(type == runtime.newSymbol("binary")) {
125
+ return hexquote_string_with_surround(runtime, "CAST('", (RubyString)value, "' AS BLOB)");
126
+ } else {
127
+ // column type :integer or other numeric or date version
128
+ if(only_digits((RubyString)value)) {
129
+ return value;
130
+ } else {
131
+ return super_quote(recv, runtime, value, col);
132
+ }
133
+ }
134
+ } else if((value instanceof RubyFloat) || (value instanceof RubyFixnum) || (value instanceof RubyBignum)) {
135
+ if(type == runtime.newSymbol("string")) {
136
+ return quote_string_with_surround(runtime, "'", RubyString.objAsString(value), "'");
137
+ }
138
+ }
139
+ }
140
+ return super_quote(recv, runtime, value, runtime.getNil());
141
+ }
142
+
143
+ private final static ByteList NULL = new ByteList("NULL".getBytes());
144
+
145
+ public static IRubyObject super_quote(IRubyObject recv, Ruby runtime, IRubyObject value, IRubyObject col) {
146
+ if(value.respondsTo("quoted_id")) {
147
+ return value.callMethod(runtime.getCurrentContext(), "quoted_id");
148
+ }
149
+
150
+ IRubyObject type = (col.isNil()) ? col : col.callMethod(runtime.getCurrentContext(),"type");
151
+ if(value instanceof RubyString ||
152
+ value.isKindOf((RubyModule)(((RubyModule)((RubyModule)runtime.getModule("ActiveSupport")).getConstant("Multibyte")).getConstantAt("Chars")))) {
153
+ RubyString svalue = RubyString.objAsString(value);
154
+ if(type == runtime.newSymbol("binary") && col.getType().respondsTo("string_to_binary")) {
155
+ return quote_string_with_surround(runtime, "'", (RubyString)(col.getType().callMethod(runtime.getCurrentContext(), "string_to_binary", svalue)), "'");
156
+ } else if(type == runtime.newSymbol("integer") || type == runtime.newSymbol("float")) {
157
+ return RubyString.objAsString(((type == runtime.newSymbol("integer")) ?
158
+ svalue.callMethod(runtime.getCurrentContext(), "to_i") :
159
+ svalue.callMethod(runtime.getCurrentContext(), "to_f")));
160
+ } else {
161
+ return quote_string_with_surround(runtime, "'", svalue, "'");
162
+ }
163
+ } else if(value.isNil()) {
164
+ return runtime.newStringShared(NULL);
165
+ } else if(value instanceof RubyBoolean) {
166
+ return (value.isTrue() ?
167
+ (type == runtime.newSymbol(":integer")) ? runtime.newString("1") : recv.callMethod(runtime.getCurrentContext(),"quoted_true") :
168
+ (type == runtime.newSymbol(":integer")) ? runtime.newString("0") : recv.callMethod(runtime.getCurrentContext(),"quoted_false"));
169
+ } else if((value instanceof RubyFloat) || (value instanceof RubyFixnum) || (value instanceof RubyBignum)) {
170
+ return RubyString.objAsString(value);
171
+ } else if(value instanceof RubyBigDecimal) {
172
+ return value.callMethod(runtime.getCurrentContext(), "to_s", runtime.newString("F"));
173
+ } else if(value.isKindOf(runtime.getModule("Date"))) {
174
+ return quote_string_with_surround(runtime, "'", RubyString.objAsString(value), "'");
175
+ } else if(value.isKindOf(runtime.getModule("Time")) || value.isKindOf(runtime.getModule("DateTime"))) {
176
+ return quote_string_with_surround(runtime, "'", (RubyString)(recv.callMethod(runtime.getCurrentContext(), "quoted_date", value)), "'");
177
+ } else {
178
+ return quote_string_with_surround(runtime, "'", (RubyString)(value.callMethod(runtime.getCurrentContext(), "to_yaml")), "'");
179
+ }
180
+ }
181
+
182
+ private final static ByteList TWO_SINGLE = new ByteList(new byte[]{'\'','\''});
183
+
184
+ public static IRubyObject quote_string_with_surround(Ruby runtime, String before, RubyString string, String after) {
185
+ ByteList input = string.getByteList();
186
+ ByteList output = new ByteList(before.getBytes());
187
+ for(int i = input.begin; i< input.begin + input.realSize; i++) {
188
+ switch(input.bytes[i]) {
189
+ case '\'':
190
+ output.append(input.bytes[i]);
191
+ //FALLTHROUGH
192
+ default:
193
+ output.append(input.bytes[i]);
194
+ }
195
+
196
+ }
197
+
198
+ output.append(after.getBytes());
199
+
200
+ return runtime.newStringShared(output);
201
+ }
202
+
203
+ private final static byte[] HEX = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
204
+
205
+ public static IRubyObject hexquote_string_with_surround(Ruby runtime, String before, RubyString string, String after) {
206
+ ByteList input = string.getByteList();
207
+ ByteList output = new ByteList(before.getBytes());
208
+ for(int i = input.begin; i< input.begin + input.realSize; i++) {
209
+ byte b1 = input.bytes[i];
210
+ byte higher = HEX[(((char)b1)>>4)%16];
211
+ byte lower = HEX[((char)b1)%16];
212
+ if(b1 == '\'') {
213
+ output.append(higher);
214
+ output.append(lower);
215
+ }
216
+ output.append(higher);
217
+ output.append(lower);
218
+ }
219
+
220
+ output.append(after.getBytes());
221
+ return runtime.newStringShared(output);
222
+ }
223
+
224
+ private static boolean only_digits(RubyString inp) {
225
+ ByteList input = inp.getByteList();
226
+ for(int i = input.begin; i< input.begin + input.realSize; i++) {
227
+ if(input.bytes[i] < '0' || input.bytes[i] > '9') {
228
+ return false;
229
+ }
230
+ }
231
+ return true;
232
+ }
233
+
234
+ public static IRubyObject quote_string(IRubyObject recv, IRubyObject string) {
235
+ boolean replacementFound = false;
236
+ ByteList bl = ((RubyString) string).getByteList();
237
+
238
+ for(int i = bl.begin; i < bl.begin + bl.realSize; i++) {
239
+ switch (bl.bytes[i]) {
240
+ case '\'': break;
241
+ default: continue;
242
+ }
243
+
244
+ // On first replacement allocate a different bytelist so we don't manip original
245
+ if(!replacementFound) {
246
+ i-= bl.begin;
247
+ bl = new ByteList(bl);
248
+ replacementFound = true;
249
+ }
250
+
251
+ bl.replace(i, 1, TWO_SINGLE);
252
+ i+=1;
253
+ }
254
+ if(replacementFound) {
255
+ return recv.getRuntime().newStringShared(bl);
256
+ } else {
257
+ return string;
258
+ }
259
+ }
260
+
261
+ public static IRubyObject select_all(IRubyObject recv, IRubyObject[] args) {
262
+ return recv.callMethod(recv.getRuntime().getCurrentContext(),"execute",args);
263
+ }
264
+
265
+ public static IRubyObject select_one(IRubyObject recv, IRubyObject[] args) {
266
+ IRubyObject limit = recv.getInstanceVariable("@limit");
267
+ if(limit == null || limit.isNil()) {
268
+ recv.setInstanceVariable("@limit", recv.getRuntime().newFixnum(1));
269
+ }
270
+ try {
271
+ return recv.callMethod(recv.getRuntime().getCurrentContext(),"execute",args).callMethod(recv.getRuntime().getCurrentContext(), "first");
272
+ } finally {
273
+ recv.setInstanceVariable("@limit", recv.getRuntime().getNil());
274
+ }
275
+ }
276
+
277
+ public static IRubyObject add_limit_offset(IRubyObject recv, IRubyObject sql, IRubyObject options) {
278
+ recv.setInstanceVariable("@limit", options.callMethod(recv.getRuntime().getCurrentContext(), MethodIndex.AREF, "[]", recv.getRuntime().newSymbol("limit")));
279
+ return recv.setInstanceVariable("@offset", options.callMethod(recv.getRuntime().getCurrentContext(), MethodIndex.AREF, "[]", recv.getRuntime().newSymbol("offset")));
280
+ }
281
+
282
+ public static IRubyObject _execute(IRubyObject recv, IRubyObject[] args) throws SQLException, java.io.IOException {
283
+ Ruby runtime = recv.getRuntime();
284
+ try {
285
+ IRubyObject conn = recv.getInstanceVariable("@connection");
286
+ String sql = args[0].toString().trim().toLowerCase();
287
+ if(sql.charAt(0) == '(') {
288
+ sql = sql.substring(1).trim();
289
+ }
290
+ if(sql.startsWith("insert")) {
291
+ return JdbcAdapterInternalService.execute_insert(conn, args[0]);
292
+ } else if(sql.startsWith("select") || sql.startsWith("show")) {
293
+ IRubyObject offset = recv.getInstanceVariable("@offset");
294
+ if(offset == null || offset.isNil()) {
295
+ offset = RubyFixnum.zero(runtime);
296
+ }
297
+ IRubyObject limit = recv.getInstanceVariable("@limit");
298
+ IRubyObject range;
299
+ IRubyObject max;
300
+ if(limit == null || limit.isNil() || RubyNumeric.fix2int(limit) == -1) {
301
+ range = RubyRange.newRange(runtime, offset, runtime.newFixnum(-1), false);
302
+ max = RubyFixnum.zero(runtime);
303
+ } else {
304
+ IRubyObject v1 = offset.callMethod(runtime.getCurrentContext(), MethodIndex.OP_PLUS, "+", limit);
305
+ range = RubyRange.newRange(runtime, offset, v1, true);
306
+ max = v1.callMethod(runtime.getCurrentContext(), MethodIndex.OP_PLUS, "+", RubyFixnum.one(runtime));
307
+ }
308
+ IRubyObject ret = JdbcAdapterInternalService.execute_query(conn, new IRubyObject[]{args[0], max}).
309
+ callMethod(runtime.getCurrentContext(), MethodIndex.AREF, "[]", range);
310
+ if(ret.isNil()) {
311
+ return runtime.newArray();
312
+ } else {
313
+ return ret;
314
+ }
315
+ } else {
316
+ return JdbcAdapterInternalService.execute_update(conn, args[0]);
317
+ }
318
+ } finally {
319
+ recv.setInstanceVariable("@limit",runtime.getNil());
320
+ recv.setInstanceVariable("@offset",runtime.getNil());
321
+ }
322
+ }
323
+ }
@@ -0,0 +1,89 @@
1
+ /***** BEGIN LICENSE BLOCK *****
2
+ * Version: CPL 1.0/GPL 2.0/LGPL 2.1
3
+ *
4
+ * The contents of this file are subject to the Common Public
5
+ * License Version 1.0 (the "License"); you may not use this file
6
+ * except in compliance with the License. You may obtain a copy of
7
+ * the License at http://www.eclipse.org/legal/cpl-v10.html
8
+ *
9
+ * Software distributed under the License is distributed on an "AS
10
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
11
+ * implied. See the License for the specific language governing
12
+ * rights and limitations under the License.
13
+ *
14
+ * Copyright (C) 2007 Ola Bini <ola.bini@gmail.com>
15
+ *
16
+ * Alternatively, the contents of this file may be used under the terms of
17
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
18
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
19
+ * in which case the provisions of the GPL or the LGPL are applicable instead
20
+ * of those above. If you wish to allow use of your version of this file only
21
+ * under the terms of either the GPL or the LGPL, and not to allow others to
22
+ * use your version of this file under the terms of the CPL, indicate your
23
+ * decision by deleting the provisions above and replace them with the notice
24
+ * and other provisions required by the GPL or the LGPL. If you do not delete
25
+ * the provisions above, a recipient may use your version of this file under
26
+ * the terms of any one of the CPL, the GPL or the LGPL.
27
+ ***** END LICENSE BLOCK *****/
28
+
29
+ import org.jruby.Ruby;
30
+ import org.jruby.RubyModule;
31
+ import org.jruby.RubyString;
32
+
33
+ import org.jruby.runtime.CallbackFactory;
34
+ import org.jruby.runtime.ThreadContext;
35
+ import org.jruby.runtime.builtin.IRubyObject;
36
+
37
+ import org.jruby.util.ByteList;
38
+
39
+ public class JDBCMySQLSpec {
40
+ public static void load(Ruby runtime, RubyModule jdbcSpec) {
41
+ RubyModule mysql = jdbcSpec.defineModuleUnder("MySQL");
42
+ CallbackFactory cf = runtime.callbackFactory(JDBCMySQLSpec.class);
43
+ mysql.defineFastMethod("quote_string",cf.getFastSingletonMethod("quote_string",IRubyObject.class));
44
+ }
45
+
46
+ private final static ByteList ZERO = new ByteList(new byte[]{'\\','0'});
47
+ private final static ByteList NEWLINE = new ByteList(new byte[]{'\\','n'});
48
+ private final static ByteList CARRIAGE = new ByteList(new byte[]{'\\','r'});
49
+ private final static ByteList ZED = new ByteList(new byte[]{'\\','Z'});
50
+ private final static ByteList DBL = new ByteList(new byte[]{'\\','"'});
51
+ private final static ByteList SINGLE = new ByteList(new byte[]{'\\','\''});
52
+ private final static ByteList ESCAPE = new ByteList(new byte[]{'\\','\\'});
53
+
54
+ public static IRubyObject quote_string(IRubyObject recv, IRubyObject string) {
55
+ boolean replacementFound = false;
56
+ ByteList bl = ((RubyString) string).getByteList();
57
+
58
+ for(int i = bl.begin; i < bl.begin + bl.realSize; i++) {
59
+ ByteList rep = null;
60
+ switch (bl.bytes[i]) {
61
+ case 0: rep = ZERO; break;
62
+ case '\n': rep = NEWLINE; break;
63
+ case '\r': rep = CARRIAGE; break;
64
+ case 26: rep = ZED; break;
65
+ case '"': rep = DBL; break;
66
+ case '\'': rep = SINGLE; break;
67
+ case '\\': rep = ESCAPE; break;
68
+ default: continue;
69
+ }
70
+
71
+ // On first replacement allocate a different bytelist so we don't manip original
72
+ if(!replacementFound) {
73
+ i-= bl.begin;
74
+ bl = new ByteList(bl);
75
+ replacementFound = true;
76
+ }
77
+
78
+ bl.replace(i, 1, rep);
79
+ i+=1;
80
+ }
81
+
82
+ if(!replacementFound) {
83
+ return string;
84
+ }
85
+
86
+
87
+ return recv.getRuntime().newStringShared(bl);
88
+ }
89
+ }
@@ -0,0 +1,953 @@
1
+ /***** BEGIN LICENSE BLOCK *****
2
+ * Version: CPL 1.0/GPL 2.0/LGPL 2.1
3
+ *
4
+ * The contents of this file are subject to the Common Public
5
+ * License Version 1.0 (the "License"); you may not use this file
6
+ * except in compliance with the License. You may obtain a copy of
7
+ * the License at http://www.eclipse.org/legal/cpl-v10.html
8
+ *
9
+ * Software distributed under the License is distributed on an "AS
10
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
11
+ * implied. See the License for the specific language governing
12
+ * rights and limitations under the License.
13
+ *
14
+ * Copyright (C) 2007 Ola Bini <ola@ologix.com>
15
+ *
16
+ * Alternatively, the contents of this file may be used under the terms of
17
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
18
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
19
+ * in which case the provisions of the GPL or the LGPL are applicable instead
20
+ * of those above. If you wish to allow use of your version of this file only
21
+ * under the terms of either the GPL or the LGPL, and not to allow others to
22
+ * use your version of this file under the terms of the CPL, indicate your
23
+ * decision by deleting the provisions above and replace them with the notice
24
+ * and other provisions required by the GPL or the LGPL. If you do not delete
25
+ * the provisions above, a recipient may use your version of this file under
26
+ * the terms of any one of the CPL, the GPL or the LGPL.
27
+ ***** END LICENSE BLOCK *****/
28
+ import java.io.IOException;
29
+ import java.io.Reader;
30
+ import java.io.InputStream;
31
+ import java.io.ByteArrayInputStream;
32
+ import java.io.StringReader;
33
+
34
+ import java.sql.Connection;
35
+ import java.sql.DatabaseMetaData;
36
+ import java.sql.PreparedStatement;
37
+ import java.sql.ResultSetMetaData;
38
+ import java.sql.ResultSet;
39
+ import java.sql.SQLException;
40
+ import java.sql.Statement;
41
+ import java.sql.PreparedStatement;
42
+ import java.sql.Timestamp;
43
+ import java.sql.Types;
44
+
45
+ import java.text.DateFormat;
46
+ import java.text.SimpleDateFormat;
47
+
48
+ import java.util.ArrayList;
49
+ import java.util.Calendar;
50
+ import java.util.Date;
51
+ import java.util.List;
52
+ import java.util.Map;
53
+ import java.util.Iterator;
54
+ import java.util.HashMap;
55
+
56
+ import org.jruby.Ruby;
57
+ import org.jruby.RubyArray;
58
+ import org.jruby.RubyBigDecimal;
59
+ import org.jruby.RubyClass;
60
+ import org.jruby.RubyHash;
61
+ import org.jruby.RubyModule;
62
+ import org.jruby.RubyNumeric;
63
+ import org.jruby.RubyObject;
64
+ import org.jruby.RubyString;
65
+ import org.jruby.RubySymbol;
66
+ import org.jruby.RubyTime;
67
+ import org.jruby.javasupport.JavaObject;
68
+ import org.jruby.runtime.Arity;
69
+ import org.jruby.runtime.Block;
70
+ import org.jruby.runtime.CallbackFactory;
71
+ import org.jruby.runtime.ThreadContext;
72
+ import org.jruby.runtime.builtin.IRubyObject;
73
+ import org.jruby.runtime.load.BasicLibraryService;
74
+ import org.jruby.util.ByteList;
75
+
76
+ public class JdbcAdapterInternalService implements BasicLibraryService {
77
+ public boolean basicLoad(final Ruby runtime) throws IOException {
78
+ RubyClass cJdbcConn = ((RubyModule)(runtime.getModule("ActiveRecord").getConstant("ConnectionAdapters"))).
79
+ defineClassUnder("JdbcConnection",runtime.getObject(),runtime.getObject().getAllocator());
80
+
81
+ CallbackFactory cf = runtime.callbackFactory(JdbcAdapterInternalService.class);
82
+ cJdbcConn.defineMethod("unmarshal_result",cf.getSingletonMethod("unmarshal_result", IRubyObject.class));
83
+ cJdbcConn.defineFastMethod("set_connection",cf.getFastSingletonMethod("set_connection", IRubyObject.class));
84
+ cJdbcConn.defineFastMethod("execute_update",cf.getFastSingletonMethod("execute_update", IRubyObject.class));
85
+ cJdbcConn.defineFastMethod("execute_query",cf.getFastOptSingletonMethod("execute_query"));
86
+ cJdbcConn.defineFastMethod("execute_insert",cf.getFastSingletonMethod("execute_insert", IRubyObject.class));
87
+ cJdbcConn.defineFastMethod("execute_id_insert",cf.getFastSingletonMethod("execute_id_insert", IRubyObject.class, IRubyObject.class));
88
+ cJdbcConn.defineFastMethod("primary_keys",cf.getFastSingletonMethod("primary_keys", IRubyObject.class));
89
+ cJdbcConn.defineFastMethod("set_native_database_types",cf.getFastSingletonMethod("set_native_database_types"));
90
+ cJdbcConn.defineFastMethod("native_database_types",cf.getFastSingletonMethod("native_database_types"));
91
+ cJdbcConn.defineFastMethod("begin",cf.getFastSingletonMethod("begin"));
92
+ cJdbcConn.defineFastMethod("commit",cf.getFastSingletonMethod("commit"));
93
+ cJdbcConn.defineFastMethod("rollback",cf.getFastSingletonMethod("rollback"));
94
+ cJdbcConn.defineFastMethod("database_name",cf.getFastSingletonMethod("database_name"));
95
+ cJdbcConn.defineFastMethod("columns",cf.getFastOptSingletonMethod("columns_internal"));
96
+ cJdbcConn.defineFastMethod("columns_internal",cf.getFastOptSingletonMethod("columns_internal"));
97
+ cJdbcConn.defineFastMethod("tables",cf.getFastOptSingletonMethod("tables"));
98
+
99
+ cJdbcConn.defineFastMethod("insert_bind",cf.getFastOptSingletonMethod("insert_bind"));
100
+ cJdbcConn.defineFastMethod("update_bind",cf.getFastOptSingletonMethod("update_bind"));
101
+
102
+ cJdbcConn.defineFastMethod("write_large_object",cf.getFastOptSingletonMethod("write_large_object"));
103
+
104
+ cJdbcConn.getMetaClass().defineFastMethod("insert?",cf.getFastSingletonMethod("insert_p", IRubyObject.class));
105
+ cJdbcConn.getMetaClass().defineFastMethod("select?",cf.getFastSingletonMethod("select_p", IRubyObject.class));
106
+
107
+ RubyModule jdbcSpec = runtime.getOrCreateModule("JdbcSpec");
108
+ JDBCMySQLSpec.load(runtime, jdbcSpec);
109
+ JDBCDerbySpec.load(runtime, jdbcSpec);
110
+
111
+ return true;
112
+ }
113
+
114
+ private static int whitespace(int p, final int pend, ByteList bl) {
115
+ while(p < pend) {
116
+ switch(bl.bytes[p]) {
117
+ case ' ':
118
+ case '\n':
119
+ case '\r':
120
+ case '\t':
121
+ p++;
122
+ break;
123
+ default:
124
+ return p;
125
+ }
126
+ }
127
+ return p;
128
+ }
129
+
130
+ public static IRubyObject insert_p(IRubyObject recv, IRubyObject _sql) {
131
+ ByteList bl = _sql.convertToString().getByteList();
132
+
133
+ int p = bl.begin;
134
+ int pend = p + bl.realSize;
135
+
136
+ p = whitespace(p, pend, bl);
137
+
138
+ if(pend - p >= 6) {
139
+ switch(bl.bytes[p++]) {
140
+ case 'i':
141
+ case 'I':
142
+ switch(bl.bytes[p++]) {
143
+ case 'n':
144
+ case 'N':
145
+ switch(bl.bytes[p++]) {
146
+ case 's':
147
+ case 'S':
148
+ switch(bl.bytes[p++]) {
149
+ case 'e':
150
+ case 'E':
151
+ switch(bl.bytes[p++]) {
152
+ case 'r':
153
+ case 'R':
154
+ switch(bl.bytes[p++]) {
155
+ case 't':
156
+ case 'T':
157
+ return recv.getRuntime().getTrue();
158
+ }
159
+ }
160
+ }
161
+ }
162
+ }
163
+ }
164
+ }
165
+ return recv.getRuntime().getFalse();
166
+ }
167
+
168
+ public static IRubyObject select_p(IRubyObject recv, IRubyObject _sql) {
169
+ ByteList bl = _sql.convertToString().getByteList();
170
+
171
+ int p = bl.begin;
172
+ int pend = p + bl.realSize;
173
+
174
+ p = whitespace(p, pend, bl);
175
+
176
+ if(pend - p >= 6) {
177
+ if(bl.bytes[p] == '(') {
178
+ p++;
179
+ p = whitespace(p, pend, bl);
180
+ }
181
+ if(pend - p >= 6) {
182
+ switch(bl.bytes[p++]) {
183
+ case 's':
184
+ case 'S':
185
+ switch(bl.bytes[p++]) {
186
+ case 'e':
187
+ case 'E':
188
+ switch(bl.bytes[p++]) {
189
+ case 'l':
190
+ case 'L':
191
+ switch(bl.bytes[p++]) {
192
+ case 'e':
193
+ case 'E':
194
+ switch(bl.bytes[p++]) {
195
+ case 'c':
196
+ case 'C':
197
+ switch(bl.bytes[p++]) {
198
+ case 't':
199
+ case 'T':
200
+ return recv.getRuntime().getTrue();
201
+ }
202
+ }
203
+ }
204
+ }
205
+ case 'h':
206
+ case 'H':
207
+ switch(bl.bytes[p++]) {
208
+ case 'o':
209
+ case 'O':
210
+ switch(bl.bytes[p++]) {
211
+ case 'w':
212
+ case 'W':
213
+ return recv.getRuntime().getTrue();
214
+ }
215
+ }
216
+ }
217
+ }
218
+ }
219
+ }
220
+ return recv.getRuntime().getFalse();
221
+ }
222
+
223
+ private static ResultSet intoResultSet(IRubyObject inp) {
224
+ return (ResultSet)((inp instanceof JavaObject ? ((JavaObject)inp) : (((JavaObject)(inp.getInstanceVariable("@java_object"))))).getValue());
225
+ }
226
+
227
+ public static IRubyObject set_connection(IRubyObject recv, IRubyObject conn) {
228
+ Connection c = (Connection)recv.dataGetStruct();
229
+ if(c != null) {
230
+ try {
231
+ c.close();
232
+ } catch(Exception e) {}
233
+ }
234
+ recv.setInstanceVariable("@connection",conn);
235
+ c = (Connection)(((JavaObject)conn.getInstanceVariable("@java_object")).getValue());
236
+ recv.dataWrapStruct(c);
237
+ return recv;
238
+ }
239
+
240
+ public static IRubyObject tables(IRubyObject recv, IRubyObject[] args) throws SQLException, IOException {
241
+ Ruby runtime = recv.getRuntime();
242
+ String catalog = null, schemapat = null, tablepat = null;
243
+ String[] types = new String[]{"TABLE"};
244
+ if (args != null) {
245
+ if (args.length > 0) {
246
+ catalog = convertToStringOrNull(args[0]);
247
+ }
248
+ if (args.length > 1) {
249
+ schemapat = convertToStringOrNull(args[1]);
250
+ }
251
+ if (args.length > 2) {
252
+ tablepat = convertToStringOrNull(args[2]);
253
+ }
254
+ if (args.length > 3) {
255
+ IRubyObject typearr = args[3];
256
+ if (typearr instanceof RubyArray) {
257
+ IRubyObject[] arr = ((RubyArray) typearr).toJavaArray();
258
+ types = new String[arr.length];
259
+ for (int i = 0; i < types.length; i++) {
260
+ types[i] = arr[i].toString();
261
+ }
262
+ } else {
263
+ types = new String[] {types.toString()};
264
+ }
265
+ }
266
+ }
267
+ while (true) {
268
+ Connection c = (Connection) recv.dataGetStruct();
269
+ ResultSet rs = null;
270
+ try {
271
+ DatabaseMetaData metadata = c.getMetaData();
272
+ String clzName = metadata.getClass().getName().toLowerCase();
273
+ boolean isOracle = clzName.indexOf("oracle") != -1 || clzName.indexOf("oci") != -1;
274
+
275
+ if(schemapat == null && isOracle) {
276
+ ResultSet schemas = metadata.getSchemas();
277
+ String username = metadata.getUserName();
278
+ while(schemas.next()) {
279
+ if(schemas.getString(1).equalsIgnoreCase(username)) {
280
+ schemapat = schemas.getString(1);
281
+ break;
282
+ }
283
+ }
284
+ schemas.close();
285
+ }
286
+ rs = metadata.getTables(catalog, schemapat, tablepat, types);
287
+ List arr = new ArrayList();
288
+ while (rs.next()) {
289
+ String name = rs.getString(3).toLowerCase();
290
+ if(!isOracle || !name.startsWith("bin$")) { //Handle stupid Oracle 10g RecycleBin feature
291
+ arr.add(runtime.newString(name));
292
+ }
293
+ }
294
+ return runtime.newArray(arr);
295
+ } catch (SQLException e) {
296
+ if(c.isClosed()) {
297
+ recv = recv.callMethod(recv.getRuntime().getCurrentContext(),"reconnect!");
298
+ if(!((Connection)recv.dataGetStruct()).isClosed()) {
299
+ continue;
300
+ }
301
+ }
302
+ throw wrap(recv, e);
303
+ } finally {
304
+ try {
305
+ rs.close();
306
+ } catch(Exception e) {}
307
+ }
308
+
309
+ }
310
+ }
311
+
312
+ public static IRubyObject native_database_types(IRubyObject recv) {
313
+ return recv.getInstanceVariable("@tps");
314
+ }
315
+
316
+ public static IRubyObject set_native_database_types(IRubyObject recv) throws SQLException, IOException {
317
+ Ruby runtime = recv.getRuntime();
318
+ ThreadContext ctx = runtime.getCurrentContext();
319
+ IRubyObject types = unmarshal_result_downcase(recv, ((Connection)recv.dataGetStruct()).getMetaData().getTypeInfo());
320
+ recv.setInstanceVariable("@native_types",
321
+ ((RubyModule)(runtime.getModule("ActiveRecord").getConstant("ConnectionAdapters"))).
322
+ getConstant("JdbcTypeConverter").callMethod(ctx,"new", types).
323
+ callMethod(ctx, "choose_best_types"));
324
+ return runtime.getNil();
325
+ }
326
+
327
+ public static IRubyObject database_name(IRubyObject recv) throws SQLException {
328
+ String name = ((Connection)recv.dataGetStruct()).getCatalog();
329
+ if(null == name) {
330
+ name = ((Connection)recv.dataGetStruct()).getMetaData().getUserName();
331
+ if(null == name) {
332
+ name = "db1";
333
+ }
334
+ }
335
+ return recv.getRuntime().newString(name);
336
+ }
337
+
338
+ public static IRubyObject begin(IRubyObject recv) throws SQLException {
339
+ ((Connection)recv.dataGetStruct()).setAutoCommit(false);
340
+ return recv.getRuntime().getNil();
341
+ }
342
+
343
+ public static IRubyObject commit(IRubyObject recv) throws SQLException {
344
+ try {
345
+ ((Connection)recv.dataGetStruct()).commit();
346
+ return recv.getRuntime().getNil();
347
+ } finally {
348
+ ((Connection)recv.dataGetStruct()).setAutoCommit(true);
349
+ }
350
+ }
351
+
352
+ public static IRubyObject rollback(IRubyObject recv) throws SQLException {
353
+ try {
354
+ ((Connection)recv.dataGetStruct()).rollback();
355
+ return recv.getRuntime().getNil();
356
+ } finally {
357
+ ((Connection)recv.dataGetStruct()).setAutoCommit(true);
358
+ }
359
+ }
360
+
361
+ public static IRubyObject columns_internal(IRubyObject recv, IRubyObject[] args) throws SQLException, IOException {
362
+ String table_name = args[0].convertToString().getUnicodeValue();
363
+ int tries = 10;
364
+ while(true) {
365
+ Connection c = (Connection)recv.dataGetStruct();
366
+ try {
367
+ DatabaseMetaData metadata = c.getMetaData();
368
+ String clzName = metadata.getClass().getName().toLowerCase();
369
+ boolean isDerby = clzName.indexOf("derby") != -1;
370
+ boolean isOracle = clzName.indexOf("oracle") != -1 || clzName.indexOf("oci") != -1;
371
+ String schemaName = null;
372
+ if(args.length>2) {
373
+ schemaName = args[2].toString();
374
+ }
375
+ if(metadata.storesUpperCaseIdentifiers()) {
376
+ table_name = table_name.toUpperCase();
377
+ } else if(metadata.storesLowerCaseIdentifiers()) {
378
+ table_name = table_name.toLowerCase();
379
+ }
380
+ if(schemaName == null && (isDerby || isOracle)) {
381
+ ResultSet schemas = metadata.getSchemas();
382
+ String username = metadata.getUserName();
383
+ while(schemas.next()) {
384
+ if(schemas.getString(1).equalsIgnoreCase(username)) {
385
+ schemaName = schemas.getString(1);
386
+ break;
387
+ }
388
+ }
389
+ schemas.close();
390
+ }
391
+
392
+ ResultSet results = metadata.getColumns(c.getCatalog(),schemaName,table_name,null);
393
+ return unmarshal_columns(recv, metadata, results);
394
+ } catch(SQLException e) {
395
+ if(c.isClosed()) {
396
+ recv = recv.callMethod(recv.getRuntime().getCurrentContext(),"reconnect!");
397
+ if(!((Connection)recv.dataGetStruct()).isClosed() && --tries > 0) {
398
+ continue;
399
+ }
400
+ }
401
+ throw wrap(recv, e);
402
+ }
403
+ }
404
+ }
405
+
406
+ private static final java.util.regex.Pattern HAS_SMALL = java.util.regex.Pattern.compile("[a-z]");
407
+ private static final java.util.regex.Pattern HAS_LARGE = java.util.regex.Pattern.compile("[A-Z]");
408
+ private static IRubyObject unmarshal_columns(IRubyObject recv, DatabaseMetaData metadata, ResultSet rs) throws SQLException, IOException {
409
+ try {
410
+ List columns = new ArrayList();
411
+ String clzName = metadata.getClass().getName().toLowerCase();
412
+ boolean isDerby = clzName.indexOf("derby") != -1;
413
+ boolean isOracle = clzName.indexOf("oracle") != -1 || clzName.indexOf("oci") != -1;
414
+ Ruby runtime = recv.getRuntime();
415
+ ThreadContext ctx = runtime.getCurrentContext();
416
+
417
+ RubyHash tps = ((RubyHash)(recv.callMethod(ctx, "adapter").callMethod(ctx,"native_database_types")));
418
+
419
+ IRubyObject jdbcCol = ((RubyModule)(runtime.getModule("ActiveRecord").getConstant("ConnectionAdapters"))).getConstant("JdbcColumn");
420
+
421
+ while(rs.next()) {
422
+ String column_name = rs.getString(4);
423
+ if(metadata.storesUpperCaseIdentifiers() && !HAS_SMALL.matcher(column_name).find()) {
424
+ column_name = column_name.toLowerCase();
425
+ }
426
+
427
+ String prec = rs.getString(7);
428
+ String scal = rs.getString(9);
429
+ int precision = -1;
430
+ int scale = -1;
431
+ if(prec != null) {
432
+ precision = Integer.parseInt(prec);
433
+ if(scal != null) {
434
+ scale = Integer.parseInt(scal);
435
+ }
436
+ }
437
+ String type = rs.getString(6);
438
+ if(prec != null && precision > 0) {
439
+ type += "(" + precision;
440
+ if(scal != null && scale > 0) {
441
+ type += "," + scale;
442
+ }
443
+ type += ")";
444
+ }
445
+ String def = rs.getString(13);
446
+ IRubyObject _def;
447
+ if(def == null || (isOracle && def.toLowerCase().trim().equals("null"))) {
448
+ _def = runtime.getNil();
449
+ } else {
450
+ if(isOracle) {
451
+ def = def.trim();
452
+ }
453
+ if((isDerby || isOracle) && def.length() > 0 && def.charAt(0) == '\'') {
454
+ def = def.substring(1, def.length()-1);
455
+ }
456
+ _def = runtime.newString(def);
457
+ }
458
+ IRubyObject c = jdbcCol.callMethod(ctx,"new", new IRubyObject[]{recv.getInstanceVariable("@config"), runtime.newString(column_name),
459
+ _def, runtime.newString(type),
460
+ runtime.newBoolean(!rs.getString(18).trim().equals("NO"))});
461
+ columns.add(c);
462
+
463
+ IRubyObject tp = (IRubyObject)tps.fastARef(c.callMethod(ctx,"type"));
464
+ if(tp != null && !tp.isNil() && tp.callMethod(ctx,"[]",runtime.newSymbol("limit")).isNil()) {
465
+ c.callMethod(ctx,"limit=", runtime.getNil());
466
+ if(!c.callMethod(ctx,"type").equals(runtime.newSymbol("decimal"))) {
467
+ c.callMethod(ctx,"precision=", runtime.getNil());
468
+ }
469
+ }
470
+ }
471
+ return runtime.newArray(columns);
472
+ } finally {
473
+ try {
474
+ rs.close();
475
+ } catch(Exception e) {}
476
+ }
477
+ }
478
+
479
+ public static IRubyObject primary_keys(IRubyObject recv, IRubyObject _table_name) throws SQLException {
480
+ Connection c = (Connection)recv.dataGetStruct();
481
+ DatabaseMetaData metadata = c.getMetaData();
482
+ String table_name = _table_name.toString();
483
+ if(metadata.storesUpperCaseIdentifiers()) {
484
+ table_name = table_name.toUpperCase();
485
+ } else if(metadata.storesLowerCaseIdentifiers()) {
486
+ table_name = table_name.toLowerCase();
487
+ }
488
+ ResultSet result_set = metadata.getPrimaryKeys(null,null,table_name);
489
+ List keyNames = new ArrayList();
490
+ Ruby runtime = recv.getRuntime();
491
+ while(result_set.next()) {
492
+ String s1 = result_set.getString(4);
493
+ if(metadata.storesUpperCaseIdentifiers() && !HAS_SMALL.matcher(s1).find()) {
494
+ s1 = s1.toLowerCase();
495
+ }
496
+ keyNames.add(runtime.newString(s1));
497
+ }
498
+
499
+ try {
500
+ result_set.close();
501
+ } catch(Exception e) {}
502
+
503
+ return runtime.newArray(keyNames);
504
+ }
505
+
506
+ public static IRubyObject execute_id_insert(IRubyObject recv, IRubyObject sql, IRubyObject id) throws SQLException {
507
+ Connection c = (Connection)recv.dataGetStruct();
508
+ PreparedStatement ps = c.prepareStatement(sql.convertToString().getUnicodeValue());
509
+ try {
510
+ ps.setLong(1,RubyNumeric.fix2long(id));
511
+ ps.executeUpdate();
512
+ } finally {
513
+ ps.close();
514
+ }
515
+ return id;
516
+ }
517
+
518
+ private static RuntimeException wrap(IRubyObject recv, Throwable exception) {
519
+ return recv.getRuntime().newArgumentError(exception.getMessage());
520
+ }
521
+
522
+ public static IRubyObject execute_update(IRubyObject recv, IRubyObject sql) throws SQLException {
523
+ int tries = 10;
524
+ while(true) {
525
+ Connection c = (Connection)recv.dataGetStruct();
526
+ Statement stmt = null;
527
+ try {
528
+ stmt = c.createStatement();
529
+ return recv.getRuntime().newFixnum(stmt.executeUpdate(sql.convertToString().getUnicodeValue()));
530
+ } catch(SQLException e) {
531
+ if(c.isClosed()) {
532
+ recv = recv.callMethod(recv.getRuntime().getCurrentContext(),"reconnect!");
533
+ if(!((Connection)recv.dataGetStruct()).isClosed() && --tries > 0) {
534
+ continue;
535
+ }
536
+ }
537
+
538
+ throw wrap(recv, e);
539
+ } finally {
540
+ if(null != stmt) {
541
+ try {
542
+ stmt.close();
543
+ } catch(Exception e) {}
544
+ }
545
+ }
546
+ }
547
+ }
548
+
549
+ public static IRubyObject execute_query(IRubyObject recv, IRubyObject[] args) throws SQLException, IOException {
550
+ IRubyObject sql = args[0];
551
+ int maxrows = 0;
552
+ if(args.length > 1) {
553
+ maxrows = RubyNumeric.fix2int(args[1]);
554
+ }
555
+ int tries = 10;
556
+ while(true) {
557
+ Connection c = (Connection)recv.dataGetStruct();
558
+ Statement stmt = null;
559
+ try {
560
+ stmt = c.createStatement();
561
+ stmt.setMaxRows(maxrows);
562
+ return unmarshal_result(recv, stmt.executeQuery(sql.convertToString().getUnicodeValue()));
563
+ } catch(SQLException e) {
564
+ if(c.isClosed()) {
565
+ recv = recv.callMethod(recv.getRuntime().getCurrentContext(),"reconnect!");
566
+ if(!((Connection)recv.dataGetStruct()).isClosed() && --tries > 0) {
567
+ continue;
568
+ }
569
+ }
570
+ throw wrap(recv, e);
571
+ } finally {
572
+ if(null != stmt) {
573
+ try {
574
+ stmt.close();
575
+ } catch(Exception e) {}
576
+ }
577
+ }
578
+ }
579
+ }
580
+
581
+ public static IRubyObject execute_insert(IRubyObject recv, IRubyObject sql) throws SQLException {
582
+ int tries = 10;
583
+ while(true) {
584
+ Connection c = (Connection)recv.dataGetStruct();
585
+ Statement stmt = null;
586
+ try {
587
+ stmt = c.createStatement();
588
+ stmt.executeUpdate(sql.convertToString().getUnicodeValue(), Statement.RETURN_GENERATED_KEYS);
589
+ return unmarshal_id_result(recv.getRuntime(), stmt.getGeneratedKeys());
590
+ } catch(SQLException e) {
591
+ if(c.isClosed()) {
592
+ recv = recv.callMethod(recv.getRuntime().getCurrentContext(),"reconnect!");
593
+ if(!((Connection)recv.dataGetStruct()).isClosed() && --tries > 0) {
594
+ continue;
595
+ }
596
+ }
597
+ throw wrap(recv, e);
598
+ } finally {
599
+ if(null != stmt) {
600
+ try {
601
+ stmt.close();
602
+ } catch(Exception e) {}
603
+ }
604
+ }
605
+ }
606
+ }
607
+
608
+ public static IRubyObject unmarshal_result_downcase(IRubyObject recv, ResultSet rs) throws SQLException, IOException {
609
+ List results = new ArrayList();
610
+ Ruby runtime = recv.getRuntime();
611
+ try {
612
+ ResultSetMetaData metadata = rs.getMetaData();
613
+ int col_count = metadata.getColumnCount();
614
+ IRubyObject[] col_names = new IRubyObject[col_count];
615
+ int[] col_types = new int[col_count];
616
+ int[] col_scale = new int[col_count];
617
+
618
+ for(int i=0;i<col_count;i++) {
619
+ col_names[i] = runtime.newString(metadata.getColumnName(i+1).toLowerCase());
620
+ col_types[i] = metadata.getColumnType(i+1);
621
+ col_scale[i] = metadata.getScale(i+1);
622
+ }
623
+
624
+ while(rs.next()) {
625
+ RubyHash row = RubyHash.newHash(runtime);
626
+ for(int i=0;i<col_count;i++) {
627
+ row.aset(col_names[i], jdbc_to_ruby(runtime, i+1, col_types[i], col_scale[i], rs));
628
+ }
629
+ results.add(row);
630
+ }
631
+ } finally {
632
+ try {
633
+ rs.close();
634
+ } catch(Exception e) {}
635
+ }
636
+
637
+ return runtime.newArray(results);
638
+ }
639
+
640
+ public static IRubyObject unmarshal_result(IRubyObject recv, ResultSet rs) throws SQLException, IOException {
641
+ Ruby runtime = recv.getRuntime();
642
+ List results = new ArrayList();
643
+ try {
644
+ ResultSetMetaData metadata = rs.getMetaData();
645
+ boolean storesUpper = rs.getStatement().getConnection().getMetaData().storesUpperCaseIdentifiers();
646
+ int col_count = metadata.getColumnCount();
647
+ IRubyObject[] col_names = new IRubyObject[col_count];
648
+ int[] col_types = new int[col_count];
649
+ int[] col_scale = new int[col_count];
650
+
651
+ for(int i=0;i<col_count;i++) {
652
+ String s1 = metadata.getColumnName(i+1);
653
+ if(storesUpper && !HAS_SMALL.matcher(s1).find()) {
654
+ s1 = s1.toLowerCase();
655
+ }
656
+ col_names[i] = runtime.newString(s1);
657
+ col_types[i] = metadata.getColumnType(i+1);
658
+ col_scale[i] = metadata.getScale(i+1);
659
+ }
660
+
661
+ while(rs.next()) {
662
+ RubyHash row = RubyHash.newHash(runtime);
663
+ for(int i=0;i<col_count;i++) {
664
+ row.aset(col_names[i], jdbc_to_ruby(runtime, i+1, col_types[i], col_scale[i], rs));
665
+ }
666
+ results.add(row);
667
+ }
668
+ } finally {
669
+ try {
670
+ rs.close();
671
+ } catch(Exception e) {}
672
+ }
673
+ return runtime.newArray(results);
674
+ }
675
+
676
+ public static IRubyObject unmarshal_result(IRubyObject recv, IRubyObject resultset, Block row_filter) throws SQLException, IOException {
677
+ Ruby runtime = recv.getRuntime();
678
+ ResultSet rs = intoResultSet(resultset);
679
+ List results = new ArrayList();
680
+ try {
681
+ ResultSetMetaData metadata = rs.getMetaData();
682
+ int col_count = metadata.getColumnCount();
683
+ IRubyObject[] col_names = new IRubyObject[col_count];
684
+ int[] col_types = new int[col_count];
685
+ int[] col_scale = new int[col_count];
686
+
687
+ for(int i=0;i<col_count;i++) {
688
+ col_names[i] = runtime.newString(metadata.getColumnName(i+1));
689
+ col_types[i] = metadata.getColumnType(i+1);
690
+ col_scale[i] = metadata.getScale(i+1);
691
+ }
692
+
693
+ if(row_filter.isGiven()) {
694
+ while(rs.next()) {
695
+ if(row_filter.yield(runtime.getCurrentContext(),resultset).isTrue()) {
696
+ RubyHash row = RubyHash.newHash(runtime);
697
+ for(int i=0;i<col_count;i++) {
698
+ row.aset(col_names[i], jdbc_to_ruby(runtime, i+1, col_types[i], col_scale[i], rs));
699
+ }
700
+ results.add(row);
701
+ }
702
+ }
703
+ } else {
704
+ while(rs.next()) {
705
+ RubyHash row = RubyHash.newHash(runtime);
706
+ for(int i=0;i<col_count;i++) {
707
+ row.aset(col_names[i], jdbc_to_ruby(runtime, i+1, col_types[i], col_scale[i], rs));
708
+ }
709
+ results.add(row);
710
+ }
711
+ }
712
+
713
+ } finally {
714
+ try {
715
+ rs.close();
716
+ } catch(Exception e) {}
717
+ }
718
+
719
+ return runtime.newArray(results);
720
+ }
721
+
722
+ private static IRubyObject jdbc_to_ruby(Ruby runtime, int row, int type, int scale, ResultSet rs) throws SQLException, IOException {
723
+ int n;
724
+ switch(type) {
725
+ case Types.BINARY:
726
+ case Types.BLOB:
727
+ case Types.LONGVARBINARY:
728
+ case Types.VARBINARY:
729
+ InputStream is = rs.getBinaryStream(row);
730
+ if(is == null || rs.wasNull()) {
731
+ return runtime.getNil();
732
+ }
733
+ ByteList str = new ByteList(2048);
734
+ byte[] buf = new byte[2048];
735
+ while((n = is.read(buf)) != -1) {
736
+ str.append(buf, 0, n);
737
+ }
738
+ is.close();
739
+ return runtime.newString(str);
740
+ case Types.LONGVARCHAR:
741
+ case Types.CLOB:
742
+ Reader rss = rs.getCharacterStream(row);
743
+ if(rss == null || rs.wasNull()) {
744
+ return runtime.getNil();
745
+ }
746
+ StringBuffer str2 = new StringBuffer(2048);
747
+ char[] cuf = new char[2048];
748
+ while((n = rss.read(cuf)) != -1) {
749
+ str2.append(cuf, 0, n);
750
+ }
751
+ rss.close();
752
+ return RubyString.newUnicodeString(runtime, str2.toString());
753
+ case Types.TIMESTAMP:
754
+ Timestamp time = rs.getTimestamp(row);
755
+ if (time == null || rs.wasNull()) {
756
+ return runtime.getNil();
757
+ }
758
+ String sttr = time.toString();
759
+ if(sttr.endsWith(" 00:00:00.0")) {
760
+ sttr = sttr.substring(0, sttr.length()-(" 00:00:00.0".length()));
761
+ }
762
+ return RubyString.newUnicodeString(runtime, sttr);
763
+ default:
764
+ String vs = rs.getString(row);
765
+ if(vs == null || rs.wasNull()) {
766
+ return runtime.getNil();
767
+ }
768
+
769
+ return RubyString.newUnicodeString(runtime, vs);
770
+ }
771
+ }
772
+
773
+ public static IRubyObject unmarshal_id_result(Ruby runtime, ResultSet rs) throws SQLException {
774
+ try {
775
+ if(rs.next()) {
776
+ if(rs.getMetaData().getColumnCount() > 0) {
777
+ return runtime.newFixnum(rs.getLong(1));
778
+ }
779
+ }
780
+ return runtime.getNil();
781
+ } finally {
782
+ try {
783
+ rs.close();
784
+ } catch(Exception e) {}
785
+ }
786
+ }
787
+
788
+ private static String convertToStringOrNull(IRubyObject obj) {
789
+ if (obj.isNil()) {
790
+ return null;
791
+ }
792
+ return obj.toString();
793
+ }
794
+
795
+ private static int getTypeValueFor(Ruby runtime, IRubyObject type) throws SQLException {
796
+ if(!(type instanceof RubySymbol)) {
797
+ type = type.callMethod(runtime.getCurrentContext(),"type");
798
+ }
799
+ if(type == runtime.newSymbol("string")) {
800
+ return Types.VARCHAR;
801
+ } else if(type == runtime.newSymbol("text")) {
802
+ return Types.CLOB;
803
+ } else if(type == runtime.newSymbol("integer")) {
804
+ return Types.INTEGER;
805
+ } else if(type == runtime.newSymbol("decimal")) {
806
+ return Types.DECIMAL;
807
+ } else if(type == runtime.newSymbol("float")) {
808
+ return Types.FLOAT;
809
+ } else if(type == runtime.newSymbol("datetime")) {
810
+ return Types.TIMESTAMP;
811
+ } else if(type == runtime.newSymbol("timestamp")) {
812
+ return Types.TIMESTAMP;
813
+ } else if(type == runtime.newSymbol("time")) {
814
+ return Types.TIME;
815
+ } else if(type == runtime.newSymbol("date")) {
816
+ return Types.DATE;
817
+ } else if(type == runtime.newSymbol("binary")) {
818
+ return Types.BLOB;
819
+ } else if(type == runtime.newSymbol("boolean")) {
820
+ return Types.BOOLEAN;
821
+ } else {
822
+ return -1;
823
+ }
824
+ }
825
+
826
+ private final static DateFormat FORMAT = new SimpleDateFormat("%y-%M-%d %H:%m:%s");
827
+
828
+ private static void setValue(PreparedStatement ps, int index, Ruby runtime, IRubyObject value, IRubyObject type) throws SQLException {
829
+ final int tp = getTypeValueFor(runtime, type);
830
+ if(value.isNil()) {
831
+ ps.setNull(index, tp);
832
+ return;
833
+ }
834
+
835
+ switch(tp) {
836
+ case Types.VARCHAR:
837
+ case Types.CLOB:
838
+ ps.setString(index, RubyString.objAsString(value).toString());
839
+ break;
840
+ case Types.INTEGER:
841
+ ps.setLong(index, RubyNumeric.fix2long(value));
842
+ break;
843
+ case Types.FLOAT:
844
+ ps.setDouble(index, ((RubyNumeric)value).getDoubleValue());
845
+ break;
846
+ case Types.TIMESTAMP:
847
+ case Types.TIME:
848
+ case Types.DATE:
849
+ if(!(value instanceof RubyTime)) {
850
+ try {
851
+ Date dd = FORMAT.parse(RubyString.objAsString(value).toString());
852
+ ps.setTimestamp(index, new java.sql.Timestamp(dd.getTime()), Calendar.getInstance());
853
+ } catch(Exception e) {
854
+ ps.setString(index, RubyString.objAsString(value).toString());
855
+ }
856
+ } else {
857
+ RubyTime rubyTime = (RubyTime) value;
858
+ java.util.Date date = rubyTime.getJavaDate();
859
+ long millis = date.getTime();
860
+ long micros = rubyTime.microseconds() - millis / 1000;
861
+ java.sql.Timestamp ts = new java.sql.Timestamp(millis);
862
+ java.util.Calendar cal = Calendar.getInstance();
863
+ cal.setTime(date);
864
+ ts.setNanos((int)(micros * 1000));
865
+ ps.setTimestamp(index, ts, cal);
866
+ }
867
+ break;
868
+ case Types.BOOLEAN:
869
+ ps.setBoolean(index, value.isTrue());
870
+ break;
871
+ default: throw new RuntimeException("type " + type + " not supported in _bind yet");
872
+ }
873
+ }
874
+
875
+ private static void setValuesOnPS(PreparedStatement ps, Ruby runtime, IRubyObject values, IRubyObject types) throws SQLException {
876
+ RubyArray vals = (RubyArray)values;
877
+ RubyArray tps = (RubyArray)types;
878
+
879
+ for(int i=0, j=vals.getLength(); i<j; i++) {
880
+ setValue(ps, i+1, runtime, vals.eltInternal(i), tps.eltInternal(i));
881
+ }
882
+ }
883
+
884
+ /*
885
+ * sql, values, types, name = nil, pk = nil, id_value = nil, sequence_name = nil
886
+ */
887
+ public static IRubyObject insert_bind(IRubyObject recv, IRubyObject[] args) throws SQLException {
888
+ Ruby runtime = recv.getRuntime();
889
+ Arity.checkArgumentCount(runtime, args, 3, 7);
890
+ Connection c = (Connection)recv.dataGetStruct();
891
+ PreparedStatement ps = null;
892
+ try {
893
+ ps = c.prepareStatement(RubyString.objAsString(args[0]).toString(), Statement.RETURN_GENERATED_KEYS);
894
+ setValuesOnPS(ps, runtime, args[1], args[2]);
895
+ ps.executeUpdate();
896
+ return unmarshal_id_result(runtime, ps.getGeneratedKeys());
897
+ } finally {
898
+ try {
899
+ ps.close();
900
+ } catch(Exception e) {}
901
+ }
902
+ }
903
+
904
+ /*
905
+ * sql, values, types, name = nil
906
+ */
907
+ public static IRubyObject update_bind(IRubyObject recv, IRubyObject[] args) throws SQLException {
908
+ Ruby runtime = recv.getRuntime();
909
+ Arity.checkArgumentCount(runtime, args, 3, 4);
910
+ Connection c = (Connection)recv.dataGetStruct();
911
+ PreparedStatement ps = null;
912
+ try {
913
+ ps = c.prepareStatement(RubyString.objAsString(args[0]).toString());
914
+ setValuesOnPS(ps, runtime, args[1], args[2]);
915
+ ps.executeUpdate();
916
+ } finally {
917
+ try {
918
+ ps.close();
919
+ } catch(Exception e) {}
920
+ }
921
+ return runtime.getNil();
922
+ }
923
+
924
+
925
+ private final static String LOB_UPDATE = "UPDATE ? WHERE ";
926
+
927
+ /*
928
+ * (is binary?, colname, tablename, primary key, id, value)
929
+ */
930
+ public static IRubyObject write_large_object(IRubyObject recv, IRubyObject[] args) throws SQLException, IOException {
931
+ Ruby runtime = recv.getRuntime();
932
+ Arity.checkArgumentCount(runtime, args, 6, 6);
933
+ Connection c = (Connection)recv.dataGetStruct();
934
+ String sql = "UPDATE " + args[2].toString() + " SET " + args[1].toString() + " = ? WHERE " + args[3] + "=" + args[4];
935
+ PreparedStatement ps = null;
936
+ try {
937
+ ByteList outp = RubyString.objAsString(args[5]).getByteList();
938
+ ps = c.prepareStatement(sql);
939
+ if(args[0].isTrue()) { // binary
940
+ ps.setBinaryStream(1,new ByteArrayInputStream(outp.bytes, outp.begin, outp.realSize), outp.realSize);
941
+ } else { // clob
942
+ String ss = outp.toString();
943
+ ps.setCharacterStream(1,new StringReader(ss), ss.length());
944
+ }
945
+ ps.executeUpdate();
946
+ } finally {
947
+ try {
948
+ ps.close();
949
+ } catch(Exception e) {}
950
+ }
951
+ return runtime.getNil();
952
+ }
953
+ }