ActiveRecord-JDBC 0.4 → 0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
+ }