ActiveRecord-JDBC 0.4 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/History.txt +12 -0
  2. data/Manifest.txt +13 -0
  3. data/Rakefile +18 -10
  4. data/lib/active_record/connection_adapters/derby_adapter.rb +1 -0
  5. data/lib/active_record/connection_adapters/h2_adapter.rb +1 -0
  6. data/lib/active_record/connection_adapters/hsqldb_adapter.rb +1 -0
  7. data/lib/active_record/connection_adapters/jdbc_adapter.rb +124 -56
  8. data/lib/active_record/connection_adapters/jndi_adapter.rb +1 -0
  9. data/lib/active_record/connection_adapters/mysql_adapter.rb +1 -0
  10. data/lib/active_record/connection_adapters/oracle_adapter.rb +1 -0
  11. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1 -0
  12. data/lib/jdbc_adapter/jdbc_db2.rb +21 -0
  13. data/lib/jdbc_adapter/jdbc_derby.rb +22 -101
  14. data/lib/jdbc_adapter/jdbc_firebird.rb +5 -1
  15. data/lib/jdbc_adapter/jdbc_hsqldb.rb +23 -1
  16. data/lib/jdbc_adapter/jdbc_mimer.rb +4 -2
  17. data/lib/jdbc_adapter/jdbc_mssql.rb +149 -67
  18. data/lib/jdbc_adapter/jdbc_mysql.rb +22 -2
  19. data/lib/jdbc_adapter/jdbc_oracle.rb +80 -15
  20. data/lib/jdbc_adapter/jdbc_postgre.rb +132 -4
  21. data/lib/jdbc_adapter_internal.jar +0 -0
  22. data/src/java/JDBCDerbySpec.java +323 -0
  23. data/src/java/JDBCMySQLSpec.java +83 -0
  24. data/src/java/JdbcAdapterInternalService.java +802 -0
  25. data/test/activerecord/connection_adapters/type_conversion_test.rb +1 -0
  26. data/test/db/derby.rb +2 -5
  27. data/test/db/h2.rb +2 -5
  28. data/test/db/hsqldb.rb +2 -6
  29. data/test/db/jdbc.rb +9 -0
  30. data/test/db/mysql.rb +4 -15
  31. data/test/db/postgres.rb +2 -3
  32. data/test/generic_jdbc_connection_test.rb +9 -0
  33. data/test/jdbc_adapter/jdbc_db2_test.rb +21 -0
  34. data/test/simple.rb +6 -8
  35. metadata +15 -2
@@ -0,0 +1,83 @@
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
+ return recv.getRuntime().newStringShared(bl);
82
+ }
83
+ }
@@ -0,0 +1,802 @@
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
+ RubyModule jdbcSpec = runtime.getOrCreateModule("JdbcSpec");
105
+ JDBCMySQLSpec.load(runtime, jdbcSpec);
106
+ JDBCDerbySpec.load(runtime, jdbcSpec);
107
+
108
+ return true;
109
+ }
110
+
111
+ private static ResultSet intoResultSet(IRubyObject inp) {
112
+ return (ResultSet)((inp instanceof JavaObject ? ((JavaObject)inp) : (((JavaObject)(inp.getInstanceVariable("@java_object"))))).getValue());
113
+ }
114
+
115
+ public static IRubyObject set_connection(IRubyObject recv, IRubyObject conn) {
116
+ Connection c = (Connection)recv.dataGetStruct();
117
+ if(c != null) {
118
+ try {
119
+ c.close();
120
+ } catch(Exception e) {}
121
+ }
122
+ recv.setInstanceVariable("@connection",conn);
123
+ c = (Connection)(((JavaObject)conn.getInstanceVariable("@java_object")).getValue());
124
+ recv.dataWrapStruct(c);
125
+ return recv;
126
+ }
127
+
128
+ public static IRubyObject tables(IRubyObject recv, IRubyObject[] args) throws SQLException, IOException {
129
+ Ruby runtime = recv.getRuntime();
130
+ String catalog = null, schemapat = null, tablepat = null;
131
+ String[] types = new String[]{"TABLE"};
132
+ if (args != null) {
133
+ if (args.length > 0) {
134
+ catalog = convertToStringOrNull(args[0]);
135
+ }
136
+ if (args.length > 1) {
137
+ schemapat = convertToStringOrNull(args[1]);
138
+ }
139
+ if (args.length > 2) {
140
+ tablepat = convertToStringOrNull(args[2]);
141
+ }
142
+ if (args.length > 3) {
143
+ IRubyObject typearr = args[3];
144
+ if (typearr instanceof RubyArray) {
145
+ IRubyObject[] arr = ((RubyArray) typearr).toJavaArray();
146
+ types = new String[arr.length];
147
+ for (int i = 0; i < types.length; i++) {
148
+ types[i] = arr[i].toString();
149
+ }
150
+ } else {
151
+ types = new String[] {types.toString()};
152
+ }
153
+ }
154
+ }
155
+ while (true) {
156
+ Connection c = (Connection) recv.dataGetStruct();
157
+ ResultSet rs = null;
158
+ try {
159
+ rs = c.getMetaData().getTables(catalog, schemapat, tablepat, types);
160
+ List arr = new ArrayList();
161
+ while (rs.next()) {
162
+ arr.add(runtime.newString(rs.getString(3).toLowerCase()));
163
+ }
164
+ return runtime.newArray(arr);
165
+ } catch (SQLException e) {
166
+ if(c.isClosed()) {
167
+ recv = recv.callMethod(recv.getRuntime().getCurrentContext(),"reconnect!");
168
+ if(!((Connection)recv.dataGetStruct()).isClosed()) {
169
+ continue;
170
+ }
171
+ }
172
+ throw e;
173
+ } finally {
174
+ try {
175
+ rs.close();
176
+ } catch(Exception e) {}
177
+ }
178
+
179
+ }
180
+ }
181
+
182
+ public static IRubyObject native_database_types(IRubyObject recv) {
183
+ return recv.getInstanceVariable("@tps");
184
+ }
185
+
186
+ public static IRubyObject set_native_database_types(IRubyObject recv) throws SQLException, IOException {
187
+ Ruby runtime = recv.getRuntime();
188
+ ThreadContext ctx = runtime.getCurrentContext();
189
+ IRubyObject types = unmarshal_result_downcase(recv, ((Connection)recv.dataGetStruct()).getMetaData().getTypeInfo());
190
+ recv.setInstanceVariable("@native_types",
191
+ ((RubyModule)(runtime.getModule("ActiveRecord").getConstant("ConnectionAdapters"))).
192
+ getConstant("JdbcTypeConverter").callMethod(ctx,"new", types).
193
+ callMethod(ctx, "choose_best_types"));
194
+ return runtime.getNil();
195
+ }
196
+
197
+ public static IRubyObject database_name(IRubyObject recv) throws SQLException {
198
+ String name = ((Connection)recv.dataGetStruct()).getCatalog();
199
+ if(null == name) {
200
+ name = ((Connection)recv.dataGetStruct()).getMetaData().getUserName();
201
+ if(null == name) {
202
+ name = "db1";
203
+ }
204
+ }
205
+ return recv.getRuntime().newString(name);
206
+ }
207
+
208
+ public static IRubyObject begin(IRubyObject recv) throws SQLException {
209
+ ((Connection)recv.dataGetStruct()).setAutoCommit(false);
210
+ return recv.getRuntime().getNil();
211
+ }
212
+
213
+ public static IRubyObject commit(IRubyObject recv) throws SQLException {
214
+ try {
215
+ ((Connection)recv.dataGetStruct()).commit();
216
+ return recv.getRuntime().getNil();
217
+ } finally {
218
+ ((Connection)recv.dataGetStruct()).setAutoCommit(true);
219
+ }
220
+ }
221
+
222
+ public static IRubyObject rollback(IRubyObject recv) throws SQLException {
223
+ try {
224
+ ((Connection)recv.dataGetStruct()).rollback();
225
+ return recv.getRuntime().getNil();
226
+ } finally {
227
+ ((Connection)recv.dataGetStruct()).setAutoCommit(true);
228
+ }
229
+ }
230
+
231
+ public static IRubyObject columns_internal(IRubyObject recv, IRubyObject[] args) throws SQLException, IOException {
232
+ String table_name = args[0].convertToString().getUnicodeValue();
233
+ while(true) {
234
+ Connection c = (Connection)recv.dataGetStruct();
235
+ try {
236
+ DatabaseMetaData metadata = c.getMetaData();
237
+ String schemaName = null;
238
+ if(args.length>2) {
239
+ schemaName = args[2].toString();
240
+ }
241
+ if(metadata.storesUpperCaseIdentifiers()) {
242
+ table_name = table_name.toUpperCase();
243
+ } else if(metadata.storesLowerCaseIdentifiers()) {
244
+ table_name = table_name.toLowerCase();
245
+ }
246
+ if(schemaName == null) {
247
+ ResultSet schemas = metadata.getSchemas();
248
+ String username = metadata.getUserName();
249
+ while(schemas.next()) {
250
+ if(schemas.getString(1).equalsIgnoreCase(username)) {
251
+ schemaName = schemas.getString(1);
252
+ break;
253
+ }
254
+ }
255
+ schemas.close();
256
+ }
257
+
258
+ ResultSet results = metadata.getColumns(c.getCatalog(),schemaName,table_name,null);
259
+ return unmarshal_columns(recv, metadata, results);
260
+ } catch(SQLException e) {
261
+ if(c.isClosed()) {
262
+ recv = recv.callMethod(recv.getRuntime().getCurrentContext(),"reconnect!");
263
+ if(!((Connection)recv.dataGetStruct()).isClosed()) {
264
+ continue;
265
+ }
266
+ }
267
+ throw e;
268
+ }
269
+ }
270
+ }
271
+
272
+ private static final java.util.regex.Pattern HAS_SMALL = java.util.regex.Pattern.compile("[a-z]");
273
+ private static final java.util.regex.Pattern HAS_LARGE = java.util.regex.Pattern.compile("[A-Z]");
274
+ private static IRubyObject unmarshal_columns(IRubyObject recv, DatabaseMetaData metadata, ResultSet rs) throws SQLException, IOException {
275
+ try {
276
+ List columns = new ArrayList();
277
+ boolean isDerby = metadata.getClass().getName().indexOf("derby") != -1;
278
+ Ruby runtime = recv.getRuntime();
279
+ ThreadContext ctx = runtime.getCurrentContext();
280
+
281
+ RubyHash tps = ((RubyHash)(recv.callMethod(ctx, "adapter").callMethod(ctx,"native_database_types")));
282
+
283
+ IRubyObject jdbcCol = ((RubyModule)(runtime.getModule("ActiveRecord").getConstant("ConnectionAdapters"))).getConstant("JdbcColumn");
284
+
285
+ while(rs.next()) {
286
+ String column_name = rs.getString(4);
287
+ if(metadata.storesUpperCaseIdentifiers() && !HAS_SMALL.matcher(column_name).find()) {
288
+ column_name = column_name.toLowerCase();
289
+ }
290
+
291
+ String prec = rs.getString(7);
292
+ String scal = rs.getString(9);
293
+ int precision = -1;
294
+ int scale = -1;
295
+ if(prec != null) {
296
+ precision = Integer.parseInt(prec);
297
+ if(scal != null) {
298
+ scale = Integer.parseInt(scal);
299
+ }
300
+ }
301
+ String type = rs.getString(6);
302
+ if(prec != null && precision > 0) {
303
+ type += "(" + precision;
304
+ if(scal != null && scale > 0) {
305
+ type += "," + scale;
306
+ }
307
+ type += ")";
308
+ }
309
+ String def = rs.getString(13);
310
+ IRubyObject _def;
311
+ if(def == null) {
312
+ _def = runtime.getNil();
313
+ } else {
314
+ if(isDerby && def.length() > 0 && def.charAt(0) == '\'') {
315
+ def = def.substring(1, def.length()-1);
316
+ }
317
+ _def = runtime.newString(def);
318
+ }
319
+
320
+ IRubyObject c = jdbcCol.callMethod(ctx,"new", new IRubyObject[]{recv.getInstanceVariable("@config"), runtime.newString(column_name),
321
+ _def, runtime.newString(type),
322
+ runtime.newBoolean(!rs.getString(18).equals("NO"))});
323
+ columns.add(c);
324
+
325
+ IRubyObject tp = (IRubyObject)tps.fastARef(c.callMethod(ctx,"type"));
326
+ if(tp != null && !tp.isNil() && tp.callMethod(ctx,"[]",runtime.newSymbol("limit")).isNil()) {
327
+ c.callMethod(ctx,"limit=", runtime.getNil());
328
+ if(!c.callMethod(ctx,"type").equals(runtime.newSymbol("decimal"))) {
329
+ c.callMethod(ctx,"precision=", runtime.getNil());
330
+ }
331
+ }
332
+ }
333
+ return runtime.newArray(columns);
334
+ } finally {
335
+ try {
336
+ rs.close();
337
+ } catch(Exception e) {}
338
+ }
339
+ }
340
+
341
+ public static IRubyObject primary_keys(IRubyObject recv, IRubyObject _table_name) throws SQLException {
342
+ Connection c = (Connection)recv.dataGetStruct();
343
+ DatabaseMetaData metadata = c.getMetaData();
344
+ String table_name = _table_name.toString();
345
+ if(metadata.storesUpperCaseIdentifiers()) {
346
+ table_name = table_name.toUpperCase();
347
+ } else if(metadata.storesLowerCaseIdentifiers()) {
348
+ table_name = table_name.toLowerCase();
349
+ }
350
+ ResultSet result_set = metadata.getPrimaryKeys(null,null,table_name);
351
+ List keyNames = new ArrayList();
352
+ Ruby runtime = recv.getRuntime();
353
+ while(result_set.next()) {
354
+ String s1 = result_set.getString(4);
355
+ if(metadata.storesUpperCaseIdentifiers() && !HAS_SMALL.matcher(s1).find()) {
356
+ s1 = s1.toLowerCase();
357
+ }
358
+ keyNames.add(runtime.newString(s1));
359
+ }
360
+
361
+ try {
362
+ result_set.close();
363
+ } catch(Exception e) {}
364
+
365
+ return runtime.newArray(keyNames);
366
+ }
367
+
368
+ public static IRubyObject execute_id_insert(IRubyObject recv, IRubyObject sql, IRubyObject id) throws SQLException {
369
+ Connection c = (Connection)recv.dataGetStruct();
370
+ PreparedStatement ps = c.prepareStatement(sql.convertToString().getUnicodeValue());
371
+ try {
372
+ ps.setLong(1,RubyNumeric.fix2long(id));
373
+ ps.executeUpdate();
374
+ } finally {
375
+ ps.close();
376
+ }
377
+ return id;
378
+ }
379
+
380
+ public static IRubyObject execute_update(IRubyObject recv, IRubyObject sql) throws SQLException {
381
+ while(true) {
382
+ Connection c = (Connection)recv.dataGetStruct();
383
+ Statement stmt = null;
384
+ try {
385
+ stmt = c.createStatement();
386
+ return recv.getRuntime().newFixnum(stmt.executeUpdate(sql.convertToString().getUnicodeValue()));
387
+ } catch(SQLException e) {
388
+ if(c.isClosed()) {
389
+ recv = recv.callMethod(recv.getRuntime().getCurrentContext(),"reconnect!");
390
+ if(!((Connection)recv.dataGetStruct()).isClosed()) {
391
+ continue;
392
+ }
393
+ }
394
+ throw e;
395
+ } finally {
396
+ if(null != stmt) {
397
+ try {
398
+ stmt.close();
399
+ } catch(Exception e) {}
400
+ }
401
+ }
402
+ }
403
+ }
404
+
405
+ public static IRubyObject execute_query(IRubyObject recv, IRubyObject[] args) throws SQLException, IOException {
406
+ IRubyObject sql = args[0];
407
+ int maxrows = 0;
408
+ if(args.length > 1) {
409
+ maxrows = RubyNumeric.fix2int(args[1]);
410
+ }
411
+ while(true) {
412
+ Connection c = (Connection)recv.dataGetStruct();
413
+ Statement stmt = null;
414
+ try {
415
+ stmt = c.createStatement();
416
+ stmt.setMaxRows(maxrows);
417
+ return unmarshal_result(recv, stmt.executeQuery(sql.convertToString().getUnicodeValue()));
418
+ } catch(SQLException e) {
419
+ if(c.isClosed()) {
420
+ recv = recv.callMethod(recv.getRuntime().getCurrentContext(),"reconnect!");
421
+ if(!((Connection)recv.dataGetStruct()).isClosed()) {
422
+ continue;
423
+ }
424
+ }
425
+ throw e;
426
+ } finally {
427
+ if(null != stmt) {
428
+ try {
429
+ stmt.close();
430
+ } catch(Exception e) {}
431
+ }
432
+ }
433
+ }
434
+ }
435
+
436
+ public static IRubyObject execute_insert(IRubyObject recv, IRubyObject sql) throws SQLException {
437
+ while(true) {
438
+ Connection c = (Connection)recv.dataGetStruct();
439
+ Statement stmt = null;
440
+ try {
441
+ stmt = c.createStatement();
442
+ stmt.executeUpdate(sql.convertToString().getUnicodeValue(), Statement.RETURN_GENERATED_KEYS);
443
+ return unmarshal_id_result(recv.getRuntime(), stmt.getGeneratedKeys());
444
+ } catch(SQLException e) {
445
+ if(c.isClosed()) {
446
+ recv = recv.callMethod(recv.getRuntime().getCurrentContext(),"reconnect!");
447
+ if(!((Connection)recv.dataGetStruct()).isClosed()) {
448
+ continue;
449
+ }
450
+ }
451
+ throw e;
452
+ } finally {
453
+ if(null != stmt) {
454
+ try {
455
+ stmt.close();
456
+ } catch(Exception e) {}
457
+ }
458
+ }
459
+ }
460
+ }
461
+
462
+ public static IRubyObject unmarshal_result_downcase(IRubyObject recv, ResultSet rs) throws SQLException, IOException {
463
+ List results = new ArrayList();
464
+ Ruby runtime = recv.getRuntime();
465
+ try {
466
+ ResultSetMetaData metadata = rs.getMetaData();
467
+ int col_count = metadata.getColumnCount();
468
+ IRubyObject[] col_names = new IRubyObject[col_count];
469
+ int[] col_types = new int[col_count];
470
+ int[] col_scale = new int[col_count];
471
+
472
+ for(int i=0;i<col_count;i++) {
473
+ col_names[i] = runtime.newString(metadata.getColumnName(i+1).toLowerCase());
474
+ col_types[i] = metadata.getColumnType(i+1);
475
+ col_scale[i] = metadata.getScale(i+1);
476
+ }
477
+
478
+ while(rs.next()) {
479
+ RubyHash row = RubyHash.newHash(runtime);
480
+ for(int i=0;i<col_count;i++) {
481
+ row.aset(col_names[i], jdbc_to_ruby(runtime, i+1, col_types[i], col_scale[i], rs));
482
+ }
483
+ results.add(row);
484
+ }
485
+ } finally {
486
+ try {
487
+ rs.close();
488
+ } catch(Exception e) {}
489
+ }
490
+
491
+ return runtime.newArray(results);
492
+ }
493
+
494
+ public static IRubyObject unmarshal_result(IRubyObject recv, ResultSet rs) throws SQLException, IOException {
495
+ Ruby runtime = recv.getRuntime();
496
+ List results = new ArrayList();
497
+ try {
498
+ ResultSetMetaData metadata = rs.getMetaData();
499
+ boolean storesUpper = rs.getStatement().getConnection().getMetaData().storesUpperCaseIdentifiers();
500
+ int col_count = metadata.getColumnCount();
501
+ IRubyObject[] col_names = new IRubyObject[col_count];
502
+ int[] col_types = new int[col_count];
503
+ int[] col_scale = new int[col_count];
504
+
505
+ for(int i=0;i<col_count;i++) {
506
+ String s1 = metadata.getColumnName(i+1);
507
+ if(storesUpper && !HAS_SMALL.matcher(s1).find()) {
508
+ s1 = s1.toLowerCase();
509
+ }
510
+ col_names[i] = runtime.newString(s1);
511
+ col_types[i] = metadata.getColumnType(i+1);
512
+ col_scale[i] = metadata.getScale(i+1);
513
+ }
514
+
515
+ while(rs.next()) {
516
+ RubyHash row = RubyHash.newHash(runtime);
517
+ for(int i=0;i<col_count;i++) {
518
+ row.aset(col_names[i], jdbc_to_ruby(runtime, i+1, col_types[i], col_scale[i], rs));
519
+ }
520
+ results.add(row);
521
+ }
522
+ } finally {
523
+ try {
524
+ rs.close();
525
+ } catch(Exception e) {}
526
+ }
527
+ return runtime.newArray(results);
528
+ }
529
+
530
+ public static IRubyObject unmarshal_result(IRubyObject recv, IRubyObject resultset, Block row_filter) throws SQLException, IOException {
531
+ Ruby runtime = recv.getRuntime();
532
+ ResultSet rs = intoResultSet(resultset);
533
+ List results = new ArrayList();
534
+ try {
535
+ ResultSetMetaData metadata = rs.getMetaData();
536
+ int col_count = metadata.getColumnCount();
537
+ IRubyObject[] col_names = new IRubyObject[col_count];
538
+ int[] col_types = new int[col_count];
539
+ int[] col_scale = new int[col_count];
540
+
541
+ for(int i=0;i<col_count;i++) {
542
+ col_names[i] = runtime.newString(metadata.getColumnName(i+1));
543
+ col_types[i] = metadata.getColumnType(i+1);
544
+ col_scale[i] = metadata.getScale(i+1);
545
+ }
546
+
547
+ if(row_filter.isGiven()) {
548
+ while(rs.next()) {
549
+ if(row_filter.yield(runtime.getCurrentContext(),resultset).isTrue()) {
550
+ RubyHash row = RubyHash.newHash(runtime);
551
+ for(int i=0;i<col_count;i++) {
552
+ row.aset(col_names[i], jdbc_to_ruby(runtime, i+1, col_types[i], col_scale[i], rs));
553
+ }
554
+ results.add(row);
555
+ }
556
+ }
557
+ } else {
558
+ while(rs.next()) {
559
+ RubyHash row = RubyHash.newHash(runtime);
560
+ for(int i=0;i<col_count;i++) {
561
+ row.aset(col_names[i], jdbc_to_ruby(runtime, i+1, col_types[i], col_scale[i], rs));
562
+ }
563
+ results.add(row);
564
+ }
565
+ }
566
+
567
+ } finally {
568
+ try {
569
+ rs.close();
570
+ } catch(Exception e) {}
571
+ }
572
+
573
+ return runtime.newArray(results);
574
+ }
575
+
576
+ private static IRubyObject jdbc_to_ruby(Ruby runtime, int row, int type, int scale, ResultSet rs) throws SQLException, IOException {
577
+ int n;
578
+ switch(type) {
579
+ case Types.BINARY:
580
+ case Types.BLOB:
581
+ case Types.LONGVARBINARY:
582
+ case Types.VARBINARY:
583
+ InputStream is = rs.getBinaryStream(row);
584
+ if(is == null || rs.wasNull()) {
585
+ return runtime.getNil();
586
+ }
587
+ ByteList str = new ByteList(2048);
588
+ byte[] buf = new byte[2048];
589
+ while((n = is.read(buf)) != -1) {
590
+ str.append(buf, 0, n);
591
+ }
592
+ is.close();
593
+ return runtime.newString(str);
594
+ case Types.LONGVARCHAR:
595
+ case Types.CLOB:
596
+ Reader rss = rs.getCharacterStream(row);
597
+ if(rss == null || rs.wasNull()) {
598
+ return runtime.getNil();
599
+ }
600
+ StringBuffer str2 = new StringBuffer(2048);
601
+ char[] cuf = new char[2048];
602
+ while((n = rss.read(cuf)) != -1) {
603
+ str2.append(cuf, 0, n);
604
+ }
605
+ rss.close();
606
+ return RubyString.newUnicodeString(runtime, str2.toString());
607
+ case Types.TIMESTAMP:
608
+ Timestamp time = rs.getTimestamp(row);
609
+ if (time == null || rs.wasNull()) {
610
+ return runtime.getNil();
611
+ }
612
+ return RubyString.newUnicodeString(runtime, time.toString());
613
+ default:
614
+ String vs = rs.getString(row);
615
+ if(vs == null || rs.wasNull()) {
616
+ return runtime.getNil();
617
+ }
618
+ return RubyString.newUnicodeString(runtime, vs);
619
+ }
620
+ }
621
+
622
+ public static IRubyObject unmarshal_id_result(Ruby runtime, ResultSet rs) throws SQLException {
623
+ try {
624
+ if(rs.next()) {
625
+ if(rs.getMetaData().getColumnCount() > 0) {
626
+ return runtime.newFixnum(rs.getLong(1));
627
+ }
628
+ }
629
+ return runtime.getNil();
630
+ } finally {
631
+ try {
632
+ rs.close();
633
+ } catch(Exception e) {}
634
+ }
635
+ }
636
+
637
+ private static String convertToStringOrNull(IRubyObject obj) {
638
+ if (obj.isNil()) {
639
+ return null;
640
+ }
641
+ return obj.toString();
642
+ }
643
+
644
+ private static int getTypeValueFor(Ruby runtime, IRubyObject type) throws SQLException {
645
+ if(!(type instanceof RubySymbol)) {
646
+ type = type.callMethod(runtime.getCurrentContext(),"type");
647
+ }
648
+ if(type == runtime.newSymbol("string")) {
649
+ return Types.VARCHAR;
650
+ } else if(type == runtime.newSymbol("text")) {
651
+ return Types.CLOB;
652
+ } else if(type == runtime.newSymbol("integer")) {
653
+ return Types.INTEGER;
654
+ } else if(type == runtime.newSymbol("decimal")) {
655
+ return Types.DECIMAL;
656
+ } else if(type == runtime.newSymbol("float")) {
657
+ return Types.FLOAT;
658
+ } else if(type == runtime.newSymbol("datetime")) {
659
+ return Types.TIMESTAMP;
660
+ } else if(type == runtime.newSymbol("timestamp")) {
661
+ return Types.TIMESTAMP;
662
+ } else if(type == runtime.newSymbol("time")) {
663
+ return Types.TIME;
664
+ } else if(type == runtime.newSymbol("date")) {
665
+ return Types.DATE;
666
+ } else if(type == runtime.newSymbol("binary")) {
667
+ return Types.BLOB;
668
+ } else if(type == runtime.newSymbol("boolean")) {
669
+ return Types.BOOLEAN;
670
+ } else {
671
+ return -1;
672
+ }
673
+ }
674
+
675
+ private final static DateFormat FORMAT = new SimpleDateFormat("%y-%M-%d %H:%m:%s");
676
+
677
+ private static void setValue(PreparedStatement ps, int index, Ruby runtime, IRubyObject value, IRubyObject type) throws SQLException {
678
+ final int tp = getTypeValueFor(runtime, type);
679
+ if(value.isNil()) {
680
+ ps.setNull(index, tp);
681
+ return;
682
+ }
683
+
684
+ switch(tp) {
685
+ case Types.VARCHAR:
686
+ case Types.CLOB:
687
+ ps.setString(index, RubyString.objAsString(value).toString());
688
+ break;
689
+ case Types.INTEGER:
690
+ ps.setLong(index, RubyNumeric.fix2long(value));
691
+ break;
692
+ case Types.FLOAT:
693
+ ps.setDouble(index, ((RubyNumeric)value).getDoubleValue());
694
+ break;
695
+ case Types.TIMESTAMP:
696
+ case Types.TIME:
697
+ case Types.DATE:
698
+ if(!(value instanceof RubyTime)) {
699
+ try {
700
+ Date dd = FORMAT.parse(RubyString.objAsString(value).toString());
701
+ ps.setTimestamp(index, new java.sql.Timestamp(dd.getTime()), Calendar.getInstance());
702
+ } catch(Exception e) {
703
+ ps.setString(index, RubyString.objAsString(value).toString());
704
+ }
705
+ } else {
706
+ RubyTime rubyTime = (RubyTime) value;
707
+ java.util.Date date = rubyTime.getJavaDate();
708
+ long millis = date.getTime();
709
+ long micros = rubyTime.microseconds() - millis / 1000;
710
+ java.sql.Timestamp ts = new java.sql.Timestamp(millis);
711
+ java.util.Calendar cal = Calendar.getInstance();
712
+ cal.setTime(date);
713
+ ts.setNanos((int)(micros * 1000));
714
+ ps.setTimestamp(index, ts, cal);
715
+ }
716
+ break;
717
+ case Types.BOOLEAN:
718
+ ps.setBoolean(index, value.isTrue());
719
+ break;
720
+ default: throw new RuntimeException("type " + type + " not supported in _bind yet");
721
+ }
722
+ }
723
+
724
+ private static void setValuesOnPS(PreparedStatement ps, Ruby runtime, IRubyObject values, IRubyObject types) throws SQLException {
725
+ RubyArray vals = (RubyArray)values;
726
+ RubyArray tps = (RubyArray)types;
727
+
728
+ for(int i=0, j=vals.getLength(); i<j; i++) {
729
+ setValue(ps, i+1, runtime, vals.eltInternal(i), tps.eltInternal(i));
730
+ }
731
+ }
732
+
733
+ /*
734
+ * sql, values, types, name = nil, pk = nil, id_value = nil, sequence_name = nil
735
+ */
736
+ public static IRubyObject insert_bind(IRubyObject recv, IRubyObject[] args) throws SQLException {
737
+ Ruby runtime = recv.getRuntime();
738
+ Arity.checkArgumentCount(runtime, args, 3, 7);
739
+ Connection c = (Connection)recv.dataGetStruct();
740
+ PreparedStatement ps = null;
741
+ try {
742
+ ps = c.prepareStatement(RubyString.objAsString(args[0]).toString(), Statement.RETURN_GENERATED_KEYS);
743
+ setValuesOnPS(ps, runtime, args[1], args[2]);
744
+ ps.executeUpdate();
745
+ return unmarshal_id_result(runtime, ps.getGeneratedKeys());
746
+ } finally {
747
+ try {
748
+ ps.close();
749
+ } catch(Exception e) {}
750
+ }
751
+ }
752
+
753
+ /*
754
+ * sql, values, types, name = nil
755
+ */
756
+ public static IRubyObject update_bind(IRubyObject recv, IRubyObject[] args) throws SQLException {
757
+ Ruby runtime = recv.getRuntime();
758
+ Arity.checkArgumentCount(runtime, args, 3, 4);
759
+ Connection c = (Connection)recv.dataGetStruct();
760
+ PreparedStatement ps = null;
761
+ try {
762
+ ps = c.prepareStatement(RubyString.objAsString(args[0]).toString());
763
+ setValuesOnPS(ps, runtime, args[1], args[2]);
764
+ ps.executeUpdate();
765
+ } finally {
766
+ try {
767
+ ps.close();
768
+ } catch(Exception e) {}
769
+ }
770
+ return runtime.getNil();
771
+ }
772
+
773
+
774
+ private final static String LOB_UPDATE = "UPDATE ? WHERE ";
775
+
776
+ /*
777
+ * (is binary?, colname, tablename, primary key, id, value)
778
+ */
779
+ public static IRubyObject write_large_object(IRubyObject recv, IRubyObject[] args) throws SQLException, IOException {
780
+ Ruby runtime = recv.getRuntime();
781
+ Arity.checkArgumentCount(runtime, args, 6, 6);
782
+ Connection c = (Connection)recv.dataGetStruct();
783
+ String sql = "UPDATE " + args[2].toString() + " SET " + args[1].toString() + " = ? WHERE " + args[3] + "=" + args[4];
784
+ PreparedStatement ps = null;
785
+ try {
786
+ ByteList outp = RubyString.objAsString(args[5]).getByteList();
787
+ ps = c.prepareStatement(sql);
788
+ if(args[0].isTrue()) { // binary
789
+ ps.setBinaryStream(1,new ByteArrayInputStream(outp.bytes, outp.begin, outp.realSize), outp.realSize);
790
+ } else { // clob
791
+ String ss = outp.toString();
792
+ ps.setCharacterStream(1,new StringReader(ss), ss.length());
793
+ }
794
+ ps.executeUpdate();
795
+ } finally {
796
+ try {
797
+ ps.close();
798
+ } catch(Exception e) {}
799
+ }
800
+ return runtime.getNil();
801
+ }
802
+ }