com.ziclix.python.sql.Procedure Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jython-slim Show documentation
Show all versions of jython-slim Show documentation
Jython is an implementation of the high-level, dynamic, object-oriented
language Python written in 100% Pure Java, and seamlessly integrated with
the Java platform. It thus allows you to run Python on any Java platform.
/*
* Jython Database Specification API 2.0
*
*
* Copyright (c) 2001 brian zimmer
*
*/
package com.ziclix.python.sql;
import org.python.core.Py;
import org.python.core.PyInteger;
import org.python.core.PyList;
import org.python.core.PyObject;
import org.python.core.PyString;
import java.sql.CallableStatement;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.BitSet;
/**
* This class provides the necessary functionality to call stored
* procedures. It handles managing the database metadata and binding
* the appropriate parameters.
*
* @author brian zimmer
*/
public class Procedure extends Object {
/**
* Field NAME
*/
protected static final int NAME = 3;
/**
* Field COLUMN_TYPE
*/
protected static final int COLUMN_TYPE = 4;
/**
* Field DATA_TYPE
*/
protected static final int DATA_TYPE = 5;
/**
* Field DATA_TYPE_NAME
*/
protected static final int DATA_TYPE_NAME = 6;
/**
* Field PRECISION
*/
protected static final int PRECISION = 7;
/**
* Field LENGTH
*/
protected static final int LENGTH = 8;
/**
* Field SCALE
*/
protected static final int SCALE = 9;
/**
* Field NULLABLE
*/
protected static final int NULLABLE = 11;
/**
* Field cursor
*/
protected PyCursor cursor;
/**
* Field columns
*/
protected PyObject columns;
/**
* Field procedureCatalog
*/
protected PyObject procedureCatalog;
/**
* Field procedureSchema
*/
protected PyObject procedureSchema;
/**
* Field procedureName
*/
protected PyObject procedureName;
/**
* Field inputSet
*/
protected BitSet inputSet;
/**
* Constructor Procedure
*
* @param cursor cursor an open cursor
* @param name name a string or tuple representing the name
* @throws SQLException
*/
public Procedure(PyCursor cursor, PyObject name) throws SQLException {
this.cursor = cursor;
this.inputSet = new BitSet();
if (name instanceof PyString) {
this.procedureCatalog = getDefault();
this.procedureSchema = getDefault();
this.procedureName = name;
} else if (PyCursor.isSeq(name)) {
if (name.__len__() == 3) {
this.procedureCatalog = name.__getitem__(0);
this.procedureSchema = name.__getitem__(1);
this.procedureName = name.__getitem__(2);
} else {
// throw an exception
}
} else {
// throw an exception
}
fetchColumns();
}
/**
* Prepares the statement and registers the OUT/INOUT parameters (if any).
*
* @return CallableStatement
* @throws SQLException
*/
public CallableStatement prepareCall() throws SQLException {
return prepareCall(Py.None, Py.None);
}
/**
* Prepares the statement and registers the OUT/INOUT parameters (if any).
*
* @param rsType the value of to be created ResultSet type
* @param rsConcur the value of the to be created ResultSet concurrency
* @return CallableStatement
* @throws SQLException
*/
public CallableStatement prepareCall(PyObject rsType, PyObject rsConcur) throws SQLException {
// prepare the statement
CallableStatement statement = null;
boolean normal = ((rsType == Py.None) && (rsConcur == Py.None));
try {
// build the full call syntax
String sqlString = toSql();
if (normal) {
statement = cursor.connection.connection.prepareCall(sqlString);
} else {
int t = rsType.asInt();
int c = rsConcur.asInt();
statement = cursor.connection.connection.prepareCall(sqlString, t, c);
}
// prepare the OUT parameters
registerOutParameters(statement);
} catch (SQLException e) {
if (statement != null) {
try {
statement.close();
} catch (Exception ex) {
}
}
throw e;
}
return statement;
}
/**
* Prepare the binding dictionary with the correct datatypes.
*
* @param params a non-None list of params
* @param bindings a dictionary of bindings
*/
public void normalizeInput(PyObject params, PyObject bindings) throws SQLException {
if (this.columns == Py.None) {
return;
}
// do nothing with params at the moment
for (int i = 0, len = this.columns.__len__(), binding = 0; i < len; i++) {
PyObject column = this.columns.__getitem__(i);
int colType = column.__getitem__(COLUMN_TYPE).asInt();
switch (colType) {
case DatabaseMetaData.procedureColumnIn:
case DatabaseMetaData.procedureColumnInOut:
// bindings are Python-indexed
PyInteger key = Py.newInteger(binding++);
if (bindings.__finditem__(key) == null) {
int dataType = column.__getitem__(DATA_TYPE).asInt();
bindings.__setitem__(key, Py.newInteger(dataType));
}
// inputs are JDBC-indexed
this.inputSet.set(i + 1);
break;
}
}
}
/**
* This method determines whether the param at the specified index is an
* IN or INOUT param for a stored procedure. This is only configured properly
* AFTER a call to normalizeInput().
*
* @param index JDBC indexed column index (1, 2, ...)
* @return true if the column is an input, false otherwise
* @throws SQLException
*/
public boolean isInput(int index) throws SQLException {
return this.inputSet.get(index);
}
/**
* Returns the call in the syntax: {@literal
* {? = call (?, ?, ...)}
* {call (?, ?, ...)}
* }
*
* As of now, all parameters variables are created and no support for named variable calling is
* supported.
*
* @return String
*/
public String toSql() throws SQLException {
int colParam = 0;
int colReturn = 0;
if (this.columns != Py.None) {
for (int i = 0, len = this.columns.__len__(); i < len; i++) {
PyObject column = this.columns.__getitem__(i);
int colType = column.__getitem__(COLUMN_TYPE).asInt();
switch (colType) {
case DatabaseMetaData.procedureColumnUnknown:
throw zxJDBC.makeException(zxJDBC.NotSupportedError, "procedureColumnUnknown");
case DatabaseMetaData.procedureColumnResult:
throw zxJDBC.makeException(zxJDBC.NotSupportedError, "procedureColumnResult");
// these go on the right hand side
case DatabaseMetaData.procedureColumnIn:
case DatabaseMetaData.procedureColumnInOut:
case DatabaseMetaData.procedureColumnOut:
colParam++;
break;
// these go on the left hand side
case DatabaseMetaData.procedureColumnReturn:
colReturn++;
break;
default :
throw zxJDBC.makeException(zxJDBC.DataError, "unknown column type [" + colType + "]");
}
}
}
StringBuffer sql = new StringBuffer("{");
if (colReturn > 0) {
PyList list = new PyList();
for (; colReturn > 0; colReturn--) {
list.append(Py.newString("?"));
}
sql.append(Py.newString(",").join(list)).append(" = ");
}
String name = this.getProcedureName();
sql.append("call ").append(name).append("(");
if (colParam > 0) {
PyList list = new PyList();
for (; colParam > 0; colParam--) {
list.append(Py.newString("?"));
}
sql.append(Py.newString(",").join(list));
}
return sql.append(")}").toString();
}
/**
* Registers the OUT/INOUT parameters of the statement.
*
* @param statement statement
* @throws SQLException
*/
protected void registerOutParameters(CallableStatement statement) throws SQLException {
if (this.columns == Py.None) {
return;
}
for (int i = 0, len = this.columns.__len__(); i < len; i++) {
PyObject column = this.columns.__getitem__(i);
int colType = column.__getitem__(COLUMN_TYPE).asInt();
int dataType = column.__getitem__(DATA_TYPE).asInt();
String dataTypeName = column.__getitem__(DATA_TYPE_NAME).toString();
switch (colType) {
case DatabaseMetaData.procedureColumnInOut:
case DatabaseMetaData.procedureColumnOut:
case DatabaseMetaData.procedureColumnReturn:
cursor.datahandler.registerOut(statement, i + 1, colType, dataType, dataTypeName);
break;
}
}
}
/**
* Get the columns for the stored procedure.
*
* @throws SQLException
*/
protected void fetchColumns() throws SQLException {
PyExtendedCursor pec = (PyExtendedCursor) cursor.connection.cursor();
try {
pec.datahandler = this.cursor.datahandler;
pec.procedurecolumns(procedureCatalog, procedureSchema, procedureName, Py.None);
this.columns = pec.fetchall();
} finally {
pec.close();
}
}
/**
* The value for a missing schema or catalog. This value is used to find
* the column names for the procedure. Not all DBMS use the same default
* value; for instance Oracle uses an empty string and SQLServer a null.
* This implementation returns the empty string.
*
* @return the default value (the empty string)
* @see java.sql.DatabaseMetaData#getProcedureColumns
*/
protected PyObject getDefault() {
return Py.EmptyString;
}
/**
* Construct a procedure name for the relevant schema and catalog information.
*/
protected String getProcedureName() {
StringBuffer proc = new StringBuffer();
if (this.procedureCatalog.__nonzero__()) {
proc.append(this.procedureCatalog.toString()).append(".");
}
return proc.append(this.procedureName.toString()).toString();
}
}