/***** BEGIN LICENSE BLOCK *****
* Copyright (c) 2006-2008 Nick Sieger <nick@nicksieger.com>
* Copyright (c) 2006-2007 Ola Bini <ola.bini@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
***** END LICENSE BLOCK *****/
package jdbc_adapter;
import java.io.IOException;
import java.io.Reader;
import java.io.InputStream;
import java.io.ByteArrayInputStream;
import java.io.StringReader;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSetMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.PreparedStatement;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyClass;
import org.jruby.RubyHash;
import org.jruby.RubyModule;
import org.jruby.RubyNumeric;
import org.jruby.RubyObjectAdapter;
import org.jruby.RubyString;
import org.jruby.RubySymbol;
import org.jruby.RubyTime;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.javasupport.Java;
import org.jruby.javasupport.JavaEmbedUtils;
import org.jruby.javasupport.JavaObject;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.load.BasicLibraryService;
import org.jruby.util.ByteList;
public class JdbcAdapterInternalService implements BasicLibraryService {
private static RubyObjectAdapter rubyApi;
public boolean basicLoad(final Ruby runtime) throws IOException {
RubyModule jdbcConnection = ((RubyModule)(runtime.getModule("ActiveRecord").getConstant("ConnectionAdapters"))).
defineClassUnder("JdbcConnection",runtime.getObject(),runtime.getObject().getAllocator());
jdbcConnection.defineAnnotatedMethods(JdbcAdapterInternalService.class);
RubyModule jdbcSpec = runtime.getOrCreateModule("JdbcSpec");
rubyApi = JavaEmbedUtils.newObjectAdapter();
JdbcMySQLSpec.load(jdbcSpec);
JdbcDerbySpec.load(jdbcSpec, rubyApi);
return true;
}
private static int whitespace(int p, final int pend, ByteList bl) {
while(p < pend) {
switch(bl.bytes[p]) {
case ' ':
case '\n':
case '\r':
case '\t':
p++;
break;
default:
return p;
}
}
return p;
}
@JRubyMethod(name = "insert?", required = 1, meta = true)
public static IRubyObject insert_p(IRubyObject recv, IRubyObject _sql) {
ByteList bl = rubyApi.convertToRubyString(_sql).getByteList();
int p = bl.begin;
int pend = p + bl.realSize;
p = whitespace(p, pend, bl);
if(pend - p >= 6) {
switch(bl.bytes[p++]) {
case 'i':
case 'I':
switch(bl.bytes[p++]) {
case 'n':
case 'N':
switch(bl.bytes[p++]) {
case 's':
case 'S':
switch(bl.bytes[p++]) {
case 'e':
case 'E':
switch(bl.bytes[p++]) {
case 'r':
case 'R':
switch(bl.bytes[p++]) {
case 't':
case 'T':
return recv.getRuntime().getTrue();
}
}
}
}
}
}
}
return recv.getRuntime().getFalse();
}
@JRubyMethod(name = "select?", required = 1, meta = true)
public static IRubyObject select_p(IRubyObject recv, IRubyObject _sql) {
ByteList bl = rubyApi.convertToRubyString(_sql).getByteList();
int p = bl.begin;
int pend = p + bl.realSize;
p = whitespace(p, pend, bl);
if(pend - p >= 6) {
if(bl.bytes[p] == '(') {
p++;
p = whitespace(p, pend, bl);
}
if(pend - p >= 6) {
switch(bl.bytes[p++]) {
case 's':
case 'S':
switch(bl.bytes[p++]) {
case 'e':
case 'E':
switch(bl.bytes[p++]) {
case 'l':
case 'L':
switch(bl.bytes[p++]) {
case 'e':
case 'E':
switch(bl.bytes[p++]) {
case 'c':
case 'C':
switch(bl.bytes[p++]) {
case 't':
case 'T':
return recv.getRuntime().getTrue();
}
}
}
}
case 'h':
case 'H':
switch(bl.bytes[p++]) {
case 'o':
case 'O':
switch(bl.bytes[p++]) {
case 'w':
case 'W':
return recv.getRuntime().getTrue();
}
}
}
}
}
}
return recv.getRuntime().getFalse();
}
@JRubyMethod(name = "connection")
public static IRubyObject connection(IRubyObject recv) {
Connection c = getConnection(recv);
if (c == null) {
reconnect(recv);
}
return rubyApi.getInstanceVariable(recv, "@connection");
}
@JRubyMethod(name = "disconnect!")
public static IRubyObject disconnect(IRubyObject recv) {
setConnection(recv, null);
return recv;
}
@JRubyMethod(name = "reconnect!")
public static IRubyObject reconnect(IRubyObject recv) {
setConnection(recv, getConnectionFactory(recv).newConnection());
return recv;
}
@JRubyMethod(name = "with_connection_retry_guard", frame = true)
public static IRubyObject with_connection_retry_guard(final IRubyObject recv, final Block block) {
return withConnectionAndRetry(recv, new SQLBlock() {
public IRubyObject call(Connection c) throws SQLException {
return block.call(recv.getRuntime().getCurrentContext(), new IRubyObject[] {
wrappedConnection(recv, c)
});
}
});
}
private static IRubyObject withConnectionAndRetry(IRubyObject recv, SQLBlock block) {
int tries = 1;
int i = 0;
Throwable toWrap = null;
boolean autoCommit = false;
while (i < tries) {
Connection c = getConnection(recv, true);
try {
autoCommit = c.getAutoCommit();
return block.call(c);
} catch (Exception e) {
toWrap = e;
while (toWrap.getCause() != null && toWrap.getCause() != toWrap) {
toWrap = toWrap.getCause();
}
i++;
if (autoCommit) {
if (i == 1) {
tries = (int) rubyApi.convertToRubyInteger(config_value(recv, "retry_count")).getLongValue();
if (tries <= 0) {
tries = 1;
}
}
if (isConnectionBroken(recv, c)) {
reconnect(recv);
} else {
throw wrap(recv, toWrap);
}
}
}
}
throw wrap(recv, toWrap);
}
private static SQLBlock tableLookupBlock(final Ruby runtime,
final String catalog, final String schemapat,
final String tablepat, final String[] types) {
return new SQLBlock() {
public IRubyObject call(Connection c) throws SQLException {
ResultSet rs = null;
try {
DatabaseMetaData metadata = c.getMetaData();
String clzName = metadata.getClass().getName().toLowerCase();
boolean isOracle = clzName.indexOf("oracle") != -1 || clzName.indexOf("oci") != -1;
String realschema = schemapat;
String realtablepat = tablepat;
if(metadata.storesUpperCaseIdentifiers()) {
if (realschema != null) realschema = realschema.toUpperCase();
if (realtablepat != null) realtablepat = realtablepat.toUpperCase();
} else if(metadata.storesLowerCaseIdentifiers()) {
if (null != realschema) realschema = realschema.toLowerCase();
if (realtablepat != null) realtablepat = realtablepat.toLowerCase();
}
if (realschema == null && isOracle) {
ResultSet schemas = metadata.getSchemas();
String username = metadata.getUserName();
while (schemas.next()) {
if (schemas.getString(1).equalsIgnoreCase(username)) {
realschema = schemas.getString(1);
break;
}
}
schemas.close();
}
rs = metadata.getTables(catalog, realschema, realtablepat, types);
List arr = new ArrayList();
while (rs.next()) {
String name = rs.getString(3).toLowerCase();
// Handle stupid Oracle 10g RecycleBin feature
if (!isOracle || !name.startsWith("bin$")) {
arr.add(RubyString.newUnicodeString(runtime, name));
}
}
return runtime.newArray(arr);
} finally {
try { rs.close(); } catch (Exception e) { }
}
}
};
}
@JRubyMethod(name = "tables", rest = true)
public static IRubyObject tables(final IRubyObject recv, IRubyObject[] args) {
final Ruby runtime = recv.getRuntime();
final String catalog = getCatalog(args);
final String schemapat = getSchemaPattern(args);
final String tablepat = getTablePattern(args);
final String[] types = getTypes(args);
return withConnectionAndRetry(recv, tableLookupBlock(runtime, catalog,
schemapat, tablepat, types));
}
private static String getCatalog(IRubyObject[] args) {
if (args != null && args.length > 0) {
return convertToStringOrNull(args[0]);
}
return null;
}
private static String getSchemaPattern(IRubyObject[] args) {
if (args != null && args.length > 1) {
return convertToStringOrNull(args[1]);
}
return null;
}
private static String getTablePattern(IRubyObject[] args) {
if (args != null && args.length > 2) {
return convertToStringOrNull(args[2]);
}
return null;
}
private static String[] getTypes(IRubyObject[] args) {
String[] types = new String[]{"TABLE"};
if (args != null && args.length > 3) {
IRubyObject typearr = args[3];
if (typearr instanceof RubyArray) {
IRubyObject[] arr = rubyApi.convertToJavaArray(typearr);
types = new String[arr.length];
for (int i = 0; i < types.length; i++) {
types[i] = arr[i].toString();
}
} else {
types = new String[]{types.toString()};
}
}
return types;
}
@JRubyMethod(name = "native_database_types")
public static IRubyObject native_database_types(IRubyObject recv) {
return rubyApi.getInstanceVariable(recv, "@tps");
}
@JRubyMethod(name = "set_native_database_types")
public static IRubyObject set_native_database_types(IRubyObject recv) throws SQLException, IOException {
Ruby runtime = recv.getRuntime();
IRubyObject types = unmarshal_result_downcase(recv, getConnection(recv, true).getMetaData().getTypeInfo());
IRubyObject typeConverter = ((RubyModule) (runtime.getModule("ActiveRecord").getConstant("ConnectionAdapters"))).getConstant("JdbcTypeConverter");
IRubyObject value = rubyApi.callMethod(rubyApi.callMethod(typeConverter, "new", types), "choose_best_types");
rubyApi.setInstanceVariable(recv, "@native_types", value);
return runtime.getNil();
}
@JRubyMethod(name = "database_name")
public static IRubyObject database_name(IRubyObject recv) throws SQLException {
String name = getConnection(recv, true).getCatalog();
if(null == name) {
name = getConnection(recv, true).getMetaData().getUserName();
if(null == name) {
name = "db1";
}
}
return recv.getRuntime().newString(name);
}
@JRubyMethod(name = "begin")
public static IRubyObject begin(IRubyObject recv) throws SQLException {
getConnection(recv, true).setAutoCommit(false);
return recv.getRuntime().getNil();
}
@JRubyMethod(name = "commit")
public static IRubyObject commit(IRubyObject recv) throws SQLException {
Connection c = getConnection(recv, true);
if (!c.getAutoCommit()) {
try {
c.commit();
} finally {
c.setAutoCommit(true);
}
}
return recv.getRuntime().getNil();
}
@JRubyMethod(name = "rollback")
public static IRubyObject rollback(IRubyObject recv) throws SQLException {
Connection c = getConnection(recv, true);
if (!c.getAutoCommit()) {
try {
c.rollback();
} finally {
c.setAutoCommit(true);
}
}
return recv.getRuntime().getNil();
}
@JRubyMethod(name = {"columns", "columns_internal"}, required = 1, optional = 2)
public static IRubyObject columns_internal(final IRubyObject recv, final IRubyObject[] args) throws SQLException, IOException {
return withConnectionAndRetry(recv, new SQLBlock() {
public IRubyObject call(Connection c) throws SQLException {
ResultSet results = null;
try {
String table_name = rubyApi.convertToRubyString(args[0]).getUnicodeValue();
String schemaName = null;
int index = table_name.indexOf(".");
if(index != -1) {
schemaName = table_name.substring(0, index);
table_name = table_name.substring(index + 1);
}
DatabaseMetaData metadata = c.getMetaData();
String clzName = metadata.getClass().getName().toLowerCase();
boolean isDerby = clzName.indexOf("derby") != -1;
boolean isOracle = clzName.indexOf("oracle") != -1 || clzName.indexOf("oci") != -1;
if(args.length>2) {
schemaName = args[2].toString();
}
if(metadata.storesUpperCaseIdentifiers()) {
if (null != schemaName) schemaName = schemaName.toUpperCase();
table_name = table_name.toUpperCase();
} else if(metadata.storesLowerCaseIdentifiers()) {
if (null != schemaName) schemaName = schemaName.toLowerCase();
table_name = table_name.toLowerCase();
}
if(schemaName == null && (isDerby || isOracle)) {
ResultSet schemas = metadata.getSchemas();
String username = metadata.getUserName();
while(schemas.next()) {
if(schemas.getString(1).equalsIgnoreCase(username)) {
schemaName = schemas.getString(1);
break;
}
}
schemas.close();
}
RubyArray matchingTables = (RubyArray) tableLookupBlock(recv.getRuntime(),
c.getCatalog(), schemaName, table_name, new String[]{"TABLE","VIEW"}).call(c);
if (matchingTables.isEmpty()) {
throw new SQLException("Table " + table_name + " does not exist");
}
results = metadata.getColumns(c.getCatalog(),schemaName,table_name,null);
return unmarshal_columns(recv, metadata, results);
} finally {
try { if (results != null) results.close(); } catch (SQLException sqx) {}
}
}
});
}
private static final java.util.regex.Pattern HAS_SMALL = java.util.regex.Pattern.compile("[a-z]");
private static IRubyObject unmarshal_columns(IRubyObject recv, DatabaseMetaData metadata, ResultSet rs) throws SQLException {
try {
List columns = new ArrayList();
String clzName = metadata.getClass().getName().toLowerCase();
boolean isDerby = clzName.indexOf("derby") != -1;
boolean isOracle = clzName.indexOf("oracle") != -1 || clzName.indexOf("oci") != -1;
Ruby runtime = recv.getRuntime();
IRubyObject adapter = rubyApi.callMethod(recv, "adapter");
RubyHash tps = (RubyHash) rubyApi.callMethod(adapter, "native_database_types");
IRubyObject jdbcCol = ((RubyModule)(runtime.getModule("ActiveRecord").getConstant("ConnectionAdapters"))).getConstant("JdbcColumn");
while(rs.next()) {
String column_name = rs.getString(4);
if(metadata.storesUpperCaseIdentifiers() && !HAS_SMALL.matcher(column_name).find()) {
column_name = column_name.toLowerCase();
}
String prec = rs.getString(7);
String scal = rs.getString(9);
int precision = -1;
int scale = -1;
if(prec != null) {
precision = Integer.parseInt(prec);
if(scal != null) {
scale = Integer.parseInt(scal);
}
else if(isOracle && rs.getInt(5) == java.sql.Types.DECIMAL) { // NUMBER type in Oracle
prec = null;
}
}
String type = rs.getString(6);
if(prec != null && precision > 0) {
type += "(" + precision;
if(scal != null && scale > 0) {
type += "," + scale;
}
type += ")";
}
String def = rs.getString(13);
IRubyObject _def;
if(def == null || (isOracle && def.toLowerCase().trim().equals("null"))) {
_def = runtime.getNil();
} else {
if(isOracle) {
def = def.trim();
}
if((isDerby || isOracle) && def.length() > 0 && def.charAt(0) == '\'') {
def = def.substring(1, def.length()-1);
}
_def = RubyString.newUnicodeString(runtime, def);
}
IRubyObject config = rubyApi.getInstanceVariable(recv, "@config");
IRubyObject c = rubyApi.callMethod(jdbcCol, "new",
new IRubyObject[]{
config, RubyString.newUnicodeString(runtime, column_name),
_def, RubyString.newUnicodeString(runtime, type),
runtime.newBoolean(!rs.getString(18).trim().equals("NO"))
});
columns.add(c);
IRubyObject tp = (IRubyObject)tps.fastARef(rubyApi.callMethod(c,"type"));
if(tp != null && !tp.isNil() && rubyApi.callMethod(tp, "[]", runtime.newSymbol("limit")).isNil()) {
rubyApi.callMethod(c, "limit=", runtime.getNil());
if(!rubyApi.callMethod(c, "type").equals(runtime.newSymbol("decimal"))) {
rubyApi.callMethod(c, "precision=", runtime.getNil());
}
}
}
return runtime.newArray(columns);
} finally {
try {
rs.close();
} catch(Exception e) {}
}
}
@JRubyMethod(name = "primary_keys", required = 1)
public static IRubyObject primary_keys(final IRubyObject recv, final IRubyObject _table_name) throws SQLException {
return withConnectionAndRetry(recv, new SQLBlock() {
public IRubyObject call(Connection c) throws SQLException {
DatabaseMetaData metadata = c.getMetaData();
String table_name = _table_name.toString();
if (metadata.storesUpperCaseIdentifiers()) {
table_name = table_name.toUpperCase();
} else if (metadata.storesLowerCaseIdentifiers()) {
table_name = table_name.toLowerCase();
}
ResultSet result_set = metadata.getPrimaryKeys(null, null, table_name);
List keyNames = new ArrayList();
Ruby runtime = recv.getRuntime();
while (result_set.next()) {
String s1 = result_set.getString(4);
if (metadata.storesUpperCaseIdentifiers() && !HAS_SMALL.matcher(s1).find()) {
s1 = s1.toLowerCase();
}
keyNames.add(RubyString.newUnicodeString(runtime,s1));
}
try {
result_set.close();
} catch (Exception e) {
}
return runtime.newArray(keyNames);
}
});
}
@JRubyMethod(name = "execute_id_insert", required = 2)
public static IRubyObject execute_id_insert(IRubyObject recv, final IRubyObject sql, final IRubyObject id) throws SQLException {
return withConnectionAndRetry(recv, new SQLBlock() {
public IRubyObject call(Connection c) throws SQLException {
PreparedStatement ps = c.prepareStatement(rubyApi.convertToRubyString(sql).getUnicodeValue());
try {
ps.setLong(1, RubyNumeric.fix2long(id));
ps.executeUpdate();
} finally {
ps.close();
}
return id;
}
});
}
@JRubyMethod(name = "execute_update", required = 1)
public static IRubyObject execute_update(final IRubyObject recv, final IRubyObject sql) throws SQLException {
return withConnectionAndRetry(recv, new SQLBlock() {
public IRubyObject call(Connection c) throws SQLException {
Statement stmt = null;
try {
stmt = c.createStatement();
return recv.getRuntime().newFixnum((long)stmt.executeUpdate(rubyApi.convertToRubyString(sql).getUnicodeValue()));
} finally {
if (null != stmt) {
try {
stmt.close();
} catch (Exception e) {
}
}
}
}
});
}
@JRubyMethod(name = "execute_query", rest = true)
public static IRubyObject execute_query(final IRubyObject recv, IRubyObject[] args) throws SQLException, IOException {
final IRubyObject sql = args[0];
final int maxrows;
if (args.length > 1) {
maxrows = RubyNumeric.fix2int(args[1]);
} else {
maxrows = 0;
}
return withConnectionAndRetry(recv, new SQLBlock() {
public IRubyObject call(Connection c) throws SQLException {
Statement stmt = null;
try {
stmt = c.createStatement();
stmt.setMaxRows(maxrows);
return unmarshal_result(recv, stmt.executeQuery(rubyApi.convertToRubyString(sql).getUnicodeValue()));
} finally {
if (null != stmt) {
try {
stmt.close();
} catch (Exception e) {
}
}
}
}
});
}
@JRubyMethod(name = "execute_insert", required = 1)
public static IRubyObject execute_insert(final IRubyObject recv, final IRubyObject sql) throws SQLException {
return withConnectionAndRetry(recv, new SQLBlock() {
public IRubyObject call(Connection c) throws SQLException {
Statement stmt = null;
try {
stmt = c.createStatement();
stmt.executeUpdate(rubyApi.convertToRubyString(sql).getUnicodeValue(), Statement.RETURN_GENERATED_KEYS);
return unmarshal_id_result(recv.getRuntime(), stmt.getGeneratedKeys());
} finally {
if (null != stmt) {
try {
stmt.close();
} catch (Exception e) {
}
}
}
}
});
}
public static IRubyObject unmarshal_result_downcase(IRubyObject recv, ResultSet rs) throws SQLException, IOException {
List results = new ArrayList();
Ruby runtime = recv.getRuntime();
try {
ResultSetMetaData metadata = rs.getMetaData();
int col_count = metadata.getColumnCount();
IRubyObject[] col_names = new IRubyObject[col_count];
int[] col_types = new int[col_count];
int[] col_scale = new int[col_count];
for(int i=0;i<col_count;i++) {
col_names[i] = RubyString.newUnicodeString(runtime, metadata.getColumnLabel(i+1).toLowerCase());
col_types[i] = metadata.getColumnType(i+1);
col_scale[i] = metadata.getScale(i+1);
}
while(rs.next()) {
RubyHash row = RubyHash.newHash(runtime);
for(int i=0;i<col_count;i++) {
rubyApi.callMethod(row, "[]=", new IRubyObject[] {
col_names[i], jdbc_to_ruby(runtime, i+1, col_types[i], col_scale[i], rs)
});
}
results.add(row);
}
} finally {
try {
rs.close();
} catch(Exception e) {}
}
return runtime.newArray(results);
}
public static IRubyObject unmarshal_result(IRubyObject recv, ResultSet rs) throws SQLException {
Ruby runtime = recv.getRuntime();
List results = new ArrayList();
try {
ResultSetMetaData metadata = rs.getMetaData();
boolean storesUpper = rs.getStatement().getConnection().getMetaData().storesUpperCaseIdentifiers();
int col_count = metadata.getColumnCount();
IRubyObject[] col_names = new IRubyObject[col_count];
int[] col_types = new int[col_count];
int[] col_scale = new int[col_count];
for(int i=0;i<col_count;i++) {
String s1 = metadata.getColumnLabel(i+1);
if(storesUpper && !HAS_SMALL.matcher(s1).find()) {
s1 = s1.toLowerCase();
}
col_names[i] = RubyString.newUnicodeString(runtime, s1);
col_types[i] = metadata.getColumnType(i+1);
col_scale[i] = metadata.getScale(i+1);
}
while(rs.next()) {
RubyHash row = RubyHash.newHash(runtime);
for(int i=0;i<col_count;i++) {
rubyApi.callMethod(row, "[]=", new IRubyObject[] {
col_names[i], jdbc_to_ruby(runtime, i+1, col_types[i], col_scale[i], rs)
});
}
results.add(row);
}
} finally {
try {
rs.close();
} catch(Exception e) {}
}
return runtime.newArray(results);
}
@JRubyMethod(name = "unmarshal_result", required = 1)
public static IRubyObject unmarshal_result(IRubyObject recv, IRubyObject resultset, Block row_filter) throws SQLException, IOException {
Ruby runtime = recv.getRuntime();
ResultSet rs = intoResultSet(resultset);
List results = new ArrayList();
try {
ResultSetMetaData metadata = rs.getMetaData();
int col_count = metadata.getColumnCount();
IRubyObject[] col_names = new IRubyObject[col_count];
int[] col_types = new int[col_count];
int[] col_scale = new int[col_count];
for (int i=0;i<col_count;i++) {
col_names[i] = RubyString.newUnicodeString(runtime, metadata.getColumnLabel(i+1));
col_types[i] = metadata.getColumnType(i+1);
col_scale[i] = metadata.getScale(i+1);
}
if (row_filter.isGiven()) {
while (rs.next()) {
if (row_filter.yield(runtime.getCurrentContext(),resultset).isTrue()) {
RubyHash row = RubyHash.newHash(runtime);
for (int i=0;i<col_count;i++) {
rubyApi.callMethod(row, "[]=", new IRubyObject[] {
col_names[i], jdbc_to_ruby(runtime, i+1, col_types[i], col_scale[i], rs)
});
}
results.add(row);
}
}
} else {
while (rs.next()) {
RubyHash row = RubyHash.newHash(runtime);
for (int i=0;i<col_count;i++) {
rubyApi.callMethod(row, "[]=", new IRubyObject[] {
col_names[i], jdbc_to_ruby(runtime, i+1, col_types[i], col_scale[i], rs)
});
}
results.add(row);
}
}
} finally {
try {
rs.close();
} catch(Exception e) {}
}
return runtime.newArray(results);
}
private static IRubyObject jdbc_to_ruby(Ruby runtime, int row, int type, int scale, ResultSet rs) throws SQLException {
try {
int n;
switch (type) {
case Types.BINARY:
case Types.BLOB:
case Types.LONGVARBINARY:
case Types.VARBINARY:
InputStream is = rs.getBinaryStream(row);
if (is == null || rs.wasNull()) {
return runtime.getNil();
}
ByteList str = new ByteList(2048);
byte[] buf = new byte[2048];
while ((n = is.read(buf)) != -1) {
str.append(buf, 0, n);
}
is.close();
return runtime.newString(str);
case Types.LONGVARCHAR:
case Types.CLOB:
Reader rss = rs.getCharacterStream(row);
if (rss == null || rs.wasNull()) {
return runtime.getNil();
}
StringBuffer str2 = new StringBuffer(2048);
char[] cuf = new char[2048];
while ((n = rss.read(cuf)) != -1) {
str2.append(cuf, 0, n);
}
rss.close();
return RubyString.newUnicodeString(runtime, str2.toString());
case Types.TIMESTAMP:
Timestamp time = rs.getTimestamp(row);
if (time == null || rs.wasNull()) {
return runtime.getNil();
}
String sttr = time.toString();
if (sttr.endsWith(" 00:00:00.0")) {
sttr = sttr.substring(0, sttr.length() - (" 00:00:00.0".length()));
}
return RubyString.newUnicodeString(runtime, sttr);
default:
String vs = rs.getString(row);
if (vs == null || rs.wasNull()) {
return runtime.getNil();
}
return RubyString.newUnicodeString(runtime, vs);
}
} catch (IOException ioe) {
throw (SQLException) new SQLException(ioe.getMessage()).initCause(ioe);
}
}
public static IRubyObject unmarshal_id_result(Ruby runtime, ResultSet rs) throws SQLException {
try {
if(rs.next()) {
if(rs.getMetaData().getColumnCount() > 0) {
return runtime.newFixnum(rs.getLong(1));
}
}
return runtime.getNil();
} finally {
try {
rs.close();
} catch(Exception e) {}
}
}
private static String convertToStringOrNull(IRubyObject obj) {
if (obj.isNil()) {
return null;
}
return obj.toString();
}
private static int getTypeValueFor(Ruby runtime, IRubyObject type) throws SQLException {
if(!(type instanceof RubySymbol)) {
type = rubyApi.callMethod(type, "class");
}
if(type == runtime.newSymbol("string")) {
return Types.VARCHAR;
} else if(type == runtime.newSymbol("text")) {
return Types.CLOB;
} else if(type == runtime.newSymbol("integer")) {
return Types.INTEGER;
} else if(type == runtime.newSymbol("decimal")) {
return Types.DECIMAL;
} else if(type == runtime.newSymbol("float")) {
return Types.FLOAT;
} else if(type == runtime.newSymbol("datetime")) {
return Types.TIMESTAMP;
} else if(type == runtime.newSymbol("timestamp")) {
return Types.TIMESTAMP;
} else if(type == runtime.newSymbol("time")) {
return Types.TIME;
} else if(type == runtime.newSymbol("date")) {
return Types.DATE;
} else if(type == runtime.newSymbol("binary")) {
return Types.BLOB;
} else if(type == runtime.newSymbol("boolean")) {
return Types.BOOLEAN;
} else {
return -1;
}
}
private final static DateFormat FORMAT = new SimpleDateFormat("%y-%M-%d %H:%m:%s");
private static void setValue(PreparedStatement ps, int index, Ruby runtime, ThreadContext context,
IRubyObject value, IRubyObject type) throws SQLException {
final int tp = getTypeValueFor(runtime, type);
if(value.isNil()) {
ps.setNull(index, tp);
return;
}
switch(tp) {
case Types.VARCHAR:
case Types.CLOB:
ps.setString(index, RubyString.objAsString(context, value).toString());
break;
case Types.INTEGER:
ps.setLong(index, RubyNumeric.fix2long(value));
break;
case Types.FLOAT:
ps.setDouble(index, ((RubyNumeric)value).getDoubleValue());
break;
case Types.TIMESTAMP:
case Types.TIME:
case Types.DATE:
if(!(value instanceof RubyTime)) {
try {
Date dd = FORMAT.parse(RubyString.objAsString(context, value).toString());
ps.setTimestamp(index, new java.sql.Timestamp(dd.getTime()), Calendar.getInstance());
} catch(Exception e) {
ps.setString(index, RubyString.objAsString(context, value).toString());
}
} else {
RubyTime rubyTime = (RubyTime) value;
java.util.Date date = rubyTime.getJavaDate();
long millis = date.getTime();
long micros = rubyTime.microseconds() - millis / 1000;
java.sql.Timestamp ts = new java.sql.Timestamp(millis);
java.util.Calendar cal = Calendar.getInstance();
cal.setTime(date);
ts.setNanos((int)(micros * 1000));
ps.setTimestamp(index, ts, cal);
}
break;
case Types.BOOLEAN:
ps.setBoolean(index, value.isTrue());
break;
default: throw new RuntimeException("type " + type + " not supported in _bind yet");
}
}
private static void setValuesOnPS(PreparedStatement ps, Ruby runtime, ThreadContext context,
IRubyObject values, IRubyObject types) throws SQLException {
RubyArray vals = (RubyArray)values;
RubyArray tps = (RubyArray)types;
for(int i=0, j=vals.getLength(); i<j; i++) {
setValue(ps, i+1, runtime, context, vals.eltInternal(i), tps.eltInternal(i));
}
}
/*
* sql, values, types, name = nil, pk = nil, id_value = nil, sequence_name = nil
*/
@JRubyMethod(name = "insert_bind", required = 3, rest = true)
public static IRubyObject insert_bind(final ThreadContext context, IRubyObject recv, final IRubyObject[] args) throws SQLException {
final Ruby runtime = recv.getRuntime();
return withConnectionAndRetry(recv, new SQLBlock() {
public IRubyObject call(Connection c) throws SQLException {
PreparedStatement ps = null;
try {
ps = c.prepareStatement(rubyApi.convertToRubyString(args[0]).toString(), Statement.RETURN_GENERATED_KEYS);
setValuesOnPS(ps, runtime, context, args[1], args[2]);
ps.executeUpdate();
return unmarshal_id_result(runtime, ps.getGeneratedKeys());
} finally {
try {
ps.close();
} catch (Exception e) {
}
}
}
});
}
/*
* sql, values, types, name = nil
*/
@JRubyMethod(name = "update_bind", required = 3, rest = true)
public static IRubyObject update_bind(final ThreadContext context, IRubyObject recv, final IRubyObject[] args) throws SQLException {
final Ruby runtime = recv.getRuntime();
Arity.checkArgumentCount(runtime, args, 3, 4);
return withConnectionAndRetry(recv, new SQLBlock() {
public IRubyObject call(Connection c) throws SQLException {
PreparedStatement ps = null;
try {
ps = c.prepareStatement(rubyApi.convertToRubyString(args[0]).toString());
setValuesOnPS(ps, runtime, context, args[1], args[2]);
ps.executeUpdate();
} finally {
try {
ps.close();
} catch (Exception e) {
}
}
return runtime.getNil();
}
});
}
/*
* (is binary?, colname, tablename, primary key, id, value)
*/
@JRubyMethod(name = "write_large_object", required = 6)
public static IRubyObject write_large_object(IRubyObject recv, final IRubyObject[] args)
throws SQLException, IOException {
final Ruby runtime = recv.getRuntime();
return withConnectionAndRetry(recv, new SQLBlock() {
public IRubyObject call(Connection c) throws SQLException {
String sql = "UPDATE " + rubyApi.convertToRubyString(args[2])
+ " SET " + rubyApi.convertToRubyString(args[1])
+ " = ? WHERE " + rubyApi.convertToRubyString(args[3])
+ "=" + rubyApi.convertToRubyString(args[4]);
PreparedStatement ps = null;
try {
ps = c.prepareStatement(sql);
if (args[0].isTrue()) { // binary
ByteList outp = rubyApi.convertToRubyString(args[5]).getByteList();
ps.setBinaryStream(1, new ByteArrayInputStream(outp.bytes,
outp.begin, outp.realSize), outp.realSize);
} else { // clob
String ss = rubyApi.convertToRubyString(args[5]).getUnicodeValue();
ps.setCharacterStream(1, new StringReader(ss), ss.length());
}
ps.executeUpdate();
} finally {
try {
ps.close();
} catch (Exception e) {
}
}
return runtime.getNil();
}
});
}
private static Connection getConnection(IRubyObject recv) {
return getConnection(recv, false);
}
private static Connection getConnection(IRubyObject recv, boolean error) {
Connection conn = (Connection) recv.dataGetStruct();
if(error && conn == null) {
RubyClass err = recv.getRuntime().getModule("ActiveRecord").getClass("ConnectionNotEstablished");
throw new RaiseException(recv.getRuntime(), err, "no connection available", false);
}
return conn;
}
private static RuntimeException wrap(IRubyObject recv, Throwable exception) {
RubyClass err = recv.getRuntime().getModule("ActiveRecord").getClass("ActiveRecordError");
return (RuntimeException) new RaiseException(recv.getRuntime(), err, exception.getMessage(), false).initCause(exception);
}
private static ResultSet intoResultSet(IRubyObject inp) {
JavaObject jo;
if (inp instanceof JavaObject) {
jo = (JavaObject) inp;
} else {
jo = (JavaObject) rubyApi.getInstanceVariable(inp, "@java_object");
}
return (ResultSet) jo.getValue();
}
private static boolean isConnectionBroken(IRubyObject recv, Connection c) {
try {
IRubyObject alive = config_value(recv, "connection_alive_sql");
if (select_p(recv, alive).isTrue()) {
String connectionSQL = rubyApi.convertToRubyString(alive).toString();
Statement s = c.createStatement();
try {
s.execute(connectionSQL);
} finally {
try { s.close(); } catch (SQLException ignored) {}
}
return false;
} else {
return !c.isClosed();
}
} catch (SQLException sx) {
return true;
}
}
private static IRubyObject setConnection(IRubyObject recv, Connection c) {
Connection prev = getConnection(recv);
if (prev != null) {
try {
prev.close();
} catch(Exception e) {}
}
IRubyObject rubyconn = recv.getRuntime().getNil();
if (c != null) {
rubyconn = wrappedConnection(recv,c);
}
rubyApi.setInstanceVariable(recv, "@connection", rubyconn);
recv.dataWrapStruct(c);
return recv;
}
private static IRubyObject wrappedConnection(IRubyObject recv, Connection c) {
return Java.java_to_ruby(recv, JavaObject.wrap(recv.getRuntime(), c), Block.NULL_BLOCK);
}
private static JdbcConnectionFactory getConnectionFactory(IRubyObject recv) throws RaiseException {
IRubyObject connection_factory = rubyApi.getInstanceVariable(recv, "@connection_factory");
JdbcConnectionFactory factory = null;
try {
factory = (JdbcConnectionFactory) ((JavaObject) rubyApi.getInstanceVariable(connection_factory, "@java_object")).getValue();
} catch (Exception e) {
factory = null;
}
if (factory == null) {
throw recv.getRuntime().newRuntimeError("@connection_factory not set properly");
}
return factory;
}
private static IRubyObject config_value(IRubyObject recv, String key) {
Ruby runtime = recv.getRuntime();
IRubyObject config_hash = rubyApi.getInstanceVariable(recv, "@config");
return rubyApi.callMethod(config_hash, "[]", runtime.newSymbol(key));
}
}