Skip to content

Commit

Permalink
refs qorelanguage/qore#4684 initial connection reconnection support, …
Browse files Browse the repository at this point in the history
…other fixes and improvements
  • Loading branch information
davidnich committed Jan 26, 2023
1 parent 7527b16 commit a985d30
Show file tree
Hide file tree
Showing 13 changed files with 379 additions and 67 deletions.
36 changes: 30 additions & 6 deletions docs/mainpage.dox.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -609,8 +609,8 @@ Object obj = newClassData.cls.getDeclaredConstructor().newInstance();
|all \c jni objects|direct conversion

@note
- %Qore 64-bit integers are automatically converted to 32-bit Java integers; to get a Java \c long value; use:
@code{.py} new Long(val) @endcode
- %Qore 64-bit integers are automatically converted to 64-bit Java long values; to get a Java \c int value; use:
@code{.py} new Integer(val) @endcode
- see @ref jni_arrays for more information about conversions between %Qore lists and Java arrays

@subsection jni_java_to_qore Java to Qore Type Conversions
Expand All @@ -627,6 +627,7 @@ Object obj = newClassData.cls.getDeclaredConstructor().newInstance();
|\c float|@ref float_type "float"
|\c double|@ref float_type "double"
|\c java.lang.String|@ref string_type "string"
|\c java.sql.Date|@ref date_type "date" (@ref absolute_dates "absolute date")
|\c java.sql.Timestamp|@ref date_type "date" (@ref absolute_dates "absolute date")
|\c java.time.ZonedDateTime|@ref date_type "date" (@ref absolute_dates "absolute date")
|<tt>@ref org.qore.jni.QoreRelativeTime</tt>|@ref date_type "date" (@ref relative_dates "relative date")
Expand Down Expand Up @@ -736,23 +737,46 @@ set_save_object_callback(callback);

To use the \c jdbc DBI driver, use the driver name \c "jdbc".

%Qore datasource strings do not follow the same format as standard Java JDBC URL strings, so some care must be
taken when creating the %Qore datasource strings for the jdbc driver.

The format for %Qore datasource strings with the jdbc driver is as follows:
<tt><b>jdbc:</b>[</tt><em>user</em><tt>]/[</tt><em>pass</em><tt>]\@</tt><em>db</em><tt>[{</tt><em>option</em><tt>=</tt><em>val</em><tt>[,...]}]</tt> \n
where all elements except <tt>\@</tt><em>db</em> are optional.

The \a host, \a port, and \a charset values in %Qore datasource strings are ignored; the host and port must be
given in the \a db option, and, if the jdbc driver's URL format requires the use of \c @ or \c : characters, then
a dummy value must be provided for the \a db value, and the @ref jdbc_option_url "url option" must be used
instead.

Some example %Qore datasource strings for the \c jdbc driver:
- <tt>jdbc:user/[ass@postgresql:dbname{classpath=/usr/share/java/postgresql.jar}</tt>
- <tt>jdbc:user/pass@firebird:?serverName=hq&databaseName=/var/lib/firebird/data/mydb.fdb&encoding=WIN1252{classpath=/usr/share/java/jna-5.12.1.jar:/usr/share/java/jaybird-5.0.0.java11.jar}</tt>
- <tt>jdbc:user/pass@ignored{url=oracle:thin:@hostname:1521:dbserver,classpath=/usr/lib/oracle/21/client64/lib/ojdbc8.jar}</tt>

@note the initial \c jdbc: in the \c db value is optional; if missing, it is added automatically by the %Qore
\c jdbc driver when the internal URL string is passed to the internal Java JDBC call.

@subsection jdbc_driver_options jdbc DBI Driver Options

@subsubsection jdbc_option_classpath jdbc classpath Option

The \c classpath option allows for the classpath to be set before the JVM loads the jdbc driver. The value of
this option must be a string and is subject to environment variable substitution before being used.
The \c classpath option allows for the classpath to be set before the JVM loads the Java \c jdbc driver. The
value of this option must be a string and is subject to environment variable substitution before being used.

@subsubsection jdbc_option_db jdbc db Option
@subsubsection jdbc_option_url jdbc url Option

The \c db option allows for jdbc URLs to be used that contain special characters like \c @ and \c : that would
The \c db option allows for \c jdbc URLs to be used that contain special characters like \c @ and \c : that would
cause the datasource to fail to parse when used as a database name.

@par db Option Example
@verbatim
Datasource ds("jdbc:user/pass@ignored{db=oracle:thin:@hostname:1521:dbserver,classpath=/usr/lib/oracle/21/client64/lib/ojdbc8.jar}");
@endverbatim

@note the initial \c jdbc: in the url option is optional; if missing, then it is added automatically by the %Qore
jdbc driver when the internal URL string is passed to the internal Java JDBC call.

@section jnireleasenotes jni Module Release Notes

@subsection jni_2_1_0 jni Module Version 2.1.0
Expand Down
18 changes: 18 additions & 0 deletions src/Globals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ jmethodID Globals::methodConnectionGetMetaData;
jmethodID Globals::methodConnectionPrepareStatement;
jmethodID Globals::methodConnectionPrepareStatementArray;
jmethodID Globals::methodConnectionSetAutoCommit;
jmethodID Globals::methodConnectionIsValid;

GlobalReference<jclass> Globals::classDatabaseMetaData;
jmethodID Globals::methodDatabaseMetaDataGetDatabaseMajorVersion;
Expand All @@ -330,6 +331,7 @@ jmethodID Globals::methodDatabaseMetaDataGetDriverName;
jmethodID Globals::methodDatabaseMetaDataGetDriverVersion;

GlobalReference<jclass> Globals::classPreparedStatement;
jmethodID Globals::methodPreparedStatementClose;
jmethodID Globals::methodPreparedStatementExecute;
jmethodID Globals::methodPreparedStatementGetResultSet;
jmethodID Globals::methodPreparedStatementGetUpdateCount;
Expand All @@ -348,7 +350,11 @@ jmethodID Globals::ctorTimestamp;
jmethodID Globals::methodTimestampSetNanos;
jmethodID Globals::methodTimestampToString;

GlobalReference<jclass> Globals::classDate;
jmethodID Globals::methodDateToString;

GlobalReference<jclass> Globals::classResultSet;
jmethodID Globals::methodResultSetClose;
jmethodID Globals::methodResultSetNext;
jmethodID Globals::methodResultSetGetMetaData;
jmethodID Globals::methodResultSetGetArray;
Expand All @@ -363,6 +369,8 @@ jmethodID Globals::methodResultSetMetaDataGetColumnType;
GlobalReference<jclass> Globals::classArray;
jmethodID Globals::methodArrayGetArray;

GlobalReference<jclass> Globals::classSQLException;

int Globals::typeNull;
int Globals::typeChar;

Expand Down Expand Up @@ -2752,6 +2760,7 @@ bool Globals::init() {
methodConnectionPrepareStatementArray = env.getMethod(classConnection, "prepareStatement",
"(Ljava/lang/String;[Ljava/lang/String;)Ljava/sql/PreparedStatement;");
methodConnectionSetAutoCommit = env.getMethod(classConnection, "setAutoCommit", "(Z)V");
methodConnectionIsValid = env.getMethod(classConnection, "isValid", "(I)Z");

classDatabaseMetaData = env.findClass("java/sql/DatabaseMetaData").makeGlobal();
methodDatabaseMetaDataGetDatabaseMajorVersion = env.getMethod(classDatabaseMetaData, "getDatabaseMajorVersion",
Expand All @@ -2772,6 +2781,7 @@ bool Globals::init() {
"()Ljava/lang/String;");

classPreparedStatement = env.findClass("java/sql/PreparedStatement").makeGlobal();
methodPreparedStatementClose = env.getMethod(classPreparedStatement, "close", "()V");
methodPreparedStatementExecute = env.getMethod(classPreparedStatement, "execute", "()Z");
methodPreparedStatementGetResultSet = env.getMethod(classPreparedStatement, "getResultSet", "()Ljava/sql/ResultSet;");
methodPreparedStatementGetUpdateCount = env.getMethod(classPreparedStatement, "getUpdateCount", "()I");
Expand All @@ -2792,7 +2802,11 @@ bool Globals::init() {
methodTimestampSetNanos = env.getMethod(classTimestamp, "setNanos", "(I)V");
methodTimestampToString = env.getMethod(classTimestamp, "toString", "()Ljava/lang/String;");

classDate = env.findClass("java/sql/Date").makeGlobal();
methodDateToString = env.getMethod(classDate, "toString", "()Ljava/lang/String;");

classResultSet = env.findClass("java/sql/ResultSet").makeGlobal();
methodResultSetClose = env.getMethod(classResultSet, "close", "()V");
methodResultSetNext = env.getMethod(classResultSet, "next", "()Z");
methodResultSetGetMetaData = env.getMethod(classResultSet, "getMetaData", "()Ljava/sql/ResultSetMetaData;");
methodResultSetGetArray = env.getMethod(classResultSet, "getArray", "(I)Ljava/sql/Array;");
Expand All @@ -2810,6 +2824,8 @@ bool Globals::init() {
classArray = env.findClass("java/sql/Array").makeGlobal();
methodArrayGetArray = env.getMethod(classArray, "getArray", "()Ljava/lang/Object;");

classSQLException = env.findClass("java/sql/SQLException").makeGlobal();

{
LocalReference<jclass> classTypes = env.findClass("java/sql/Types");
jfieldID field = env.getStaticField(classTypes, "NULL", "I");
Expand Down Expand Up @@ -3008,9 +3024,11 @@ void Globals::cleanup() {
classDatabaseMetaData = nullptr;
classPreparedStatement = nullptr;
classTimestamp = nullptr;
classDate = nullptr;
classResultSet = nullptr;
classResultSetMetaData = nullptr;
classArray = nullptr;
classSQLException = nullptr;
javaQoreClassField = nullptr;
}

Expand Down
8 changes: 8 additions & 0 deletions src/Globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ class Globals {
DLLLOCAL static jmethodID methodConnectionPrepareStatement; // PreparedStatement prepareStatement(String)
DLLLOCAL static jmethodID methodConnectionPrepareStatementArray; // PreparedStatement prepareStatement(String, String[])
DLLLOCAL static jmethodID methodConnectionSetAutoCommit; // void setAutoCommit(boolean)
DLLLOCAL static jmethodID methodConnectionIsValid; // boolean isValid(int)

DLLLOCAL static GlobalReference<jclass> classDatabaseMetaData; // java.sql.DatabaseMetaData
DLLLOCAL static jmethodID methodDatabaseMetaDataGetDatabaseMajorVersion; // int getDatabaseMajorVersion()
Expand All @@ -338,6 +339,7 @@ class Globals {
DLLLOCAL static jmethodID methodDatabaseMetaDataGetDriverVersion; // String getDriverVersion()

DLLLOCAL static GlobalReference<jclass> classPreparedStatement; // java.sql.PreparedStatement
DLLLOCAL static jmethodID methodPreparedStatementClose; // void close()
DLLLOCAL static jmethodID methodPreparedStatementExecute; // boolean execute()
DLLLOCAL static jmethodID methodPreparedStatementGetResultSet; // ResultSet getResultSet()
DLLLOCAL static jmethodID methodPreparedStatementGetUpdateCount; // int getUpdateCount()
Expand All @@ -356,7 +358,11 @@ class Globals {
DLLLOCAL static jmethodID methodTimestampSetNanos; // void setNanos(int)
DLLLOCAL static jmethodID methodTimestampToString; // String toString()

DLLLOCAL static GlobalReference<jclass> classDate; // java.sql.Date
DLLLOCAL static jmethodID methodDateToString; // String toString()

DLLLOCAL static GlobalReference<jclass> classResultSet; // java.sql.ResultSet
DLLLOCAL static jmethodID methodResultSetClose; // void close()
DLLLOCAL static jmethodID methodResultSetGetArray; // Array getArray(int)
DLLLOCAL static jmethodID methodResultSetGetObject; // Object getObject(int)
DLLLOCAL static jmethodID methodResultSetNext; // boolean next()
Expand All @@ -371,6 +377,8 @@ class Globals {
DLLLOCAL static GlobalReference<jclass> classArray; // java.sql.Array
DLLLOCAL static jmethodID methodArrayGetArray; // Object getArray()

DLLLOCAL static GlobalReference<jclass> classSQLException; // java.sql.SQLException

DLLLOCAL static int typeNull; // java.sql.Type.NULL value
DLLLOCAL static int typeChar; // java.sql.Type.CHAR value

Expand Down
7 changes: 7 additions & 0 deletions src/JavaToQore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ QoreValue JavaToQore::convertToQore(LocalReference<jobject> v, QoreProgram* pgm,
return QoreValue(new DateTimeNode(chars.c_str()));
}

if (env.isInstanceOf(v, Globals::classDate)) {
LocalReference<jstring> date_str = env.callObjectMethod(v,
Globals::methodDateToString, nullptr).as<jstring>();
Env::GetStringUtfChars chars(env, date_str);
return QoreValue(new DateTimeNode(chars.c_str()));
}

if (env.isInstanceOf(v, Globals::classBigDecimal)) {
LocalReference<jstring> num_str = env.callObjectMethod(v,
Globals::methodBigDecimalToString, nullptr).as<jstring>();
Expand Down
90 changes: 68 additions & 22 deletions src/QoreJdbcConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,29 @@ QoreJdbcConnection::QoreJdbcConnection(Datasource* ds, ExceptionSink* xsink) : d
return;
}

Env env;
connect(env, xsink);
}

QoreJdbcConnection::~QoreJdbcConnection() {
assert(!connection);
}

int QoreJdbcConnection::reconnect(Env& env, ExceptionSink* xsink) {
close(env);
return connect(env, xsink);
}

int QoreJdbcConnection::connect(Env& env, ExceptionSink* xsink) {
assert(!connection);
// get URL
const std::string& str = db.empty() ? ds->getDBNameStr() : db;
if (str.empty()) {
xsink->raiseException("JDBC-CONNECTION-ERROR", "cannot build JDBC URL without a db name");
return;
return -1;
}
QoreStringMaker url("jdbc:%s", str.c_str());
QoreStringMaker url("%s%s", !str.rfind("jdbc:", 0) ? "" : "jdbc:", str.c_str());

Env env;
try {
LocalReference<jobject> props = env.newObject(Globals::classProperties, Globals::ctorProperties, nullptr);
{
Expand Down Expand Up @@ -89,11 +103,10 @@ QoreJdbcConnection::QoreJdbcConnection(Datasource* ds, ExceptionSink* xsink) : d
env.callVoidMethod(connection, Globals::methodConnectionSetAutoCommit, &jargs[0]);
} catch (jni::Exception& e) {
e.convert(xsink);
return -1;
}
}

QoreJdbcConnection::~QoreJdbcConnection() {
assert(!connection);
assert(!*xsink);
return 0;
}

int QoreJdbcConnection::parseOptions(ExceptionSink* xsink) {
Expand All @@ -108,18 +121,13 @@ int QoreJdbcConnection::parseOptions(ExceptionSink* xsink) {
return 0;
}

int QoreJdbcConnection::close(ExceptionSink* xsink) {
int QoreJdbcConnection::close(Env& env) {
if (!connection) {
return 0;
}
Env env;
try {
env.callObjectMethod(connection, Globals::methodConnectionClose, nullptr);
connection = nullptr;
} catch (jni::Exception& e) {
e.convert(xsink);
}
return *xsink ? -1 : 0;
env.callObjectMethod(connection, Globals::methodConnectionClose, nullptr);
connection = nullptr;
return 0;
}

int QoreJdbcConnection::setOption(const char* opt, const QoreValue val, ExceptionSink* xsink) {
Expand All @@ -142,10 +150,10 @@ int QoreJdbcConnection::setOption(const char* opt, const QoreValue val, Exceptio
e.convert(xsink);
return -1;
}
} else if (!strcasecmp(opt, JDBC_OPT_DB)) {
} else if (!strcasecmp(opt, JDBC_OPT_URL)) {
if (val.getType() != NT_STRING) {
xsink->raiseException("JDBC-OPTION-ERROR", "'%s' expects a 'string' value; got type '%s' instead",
JDBC_OPT_DB, val.getFullTypeName());
JDBC_OPT_URL, val.getFullTypeName());
return -1;
}
db = val.get<const QoreStringNode>()->c_str();
Expand All @@ -159,7 +167,7 @@ int QoreJdbcConnection::setOption(const char* opt, const QoreValue val, Exceptio
QoreValue QoreJdbcConnection::getOption(const char* opt) {
if (!strcasecmp(opt, JDBC_OPT_CLASSPATH)) {
return classpath.empty() ? QoreValue() : new QoreStringNode(classpath);
} else if (!strcasecmp(opt, JDBC_OPT_DB)) {
} else if (!strcasecmp(opt, JDBC_OPT_URL)) {
return db.empty() ? QoreValue() : new QoreStringNode(db);
} else {
assert(false);
Expand Down Expand Up @@ -271,7 +279,7 @@ QoreValue QoreJdbcConnection::select(const QoreString* qstr, const QoreListNode*
try {
QoreJdbcStatement stmt(xsink, this);

bool result_set = stmt.exec(env, qstr, args, xsink);
bool result_set = stmt.exec(env, xsink, qstr, args);
if (*xsink) {
return QoreValue();
}
Expand All @@ -295,7 +303,7 @@ QoreListNode* QoreJdbcConnection::selectRows(const QoreString* qstr, const QoreL
try {
QoreJdbcStatement stmt(xsink, this);

bool result_set = stmt.exec(env, qstr, args, xsink);
bool result_set = stmt.exec(env, xsink, qstr, args);
if (*xsink) {
return nullptr;
}
Expand All @@ -320,7 +328,7 @@ QoreHashNode* QoreJdbcConnection::selectRow(const QoreString* qstr, const QoreLi
try {
QoreJdbcStatement stmt(xsink, this);

bool result_set = stmt.exec(env, qstr, args, xsink);
bool result_set = stmt.exec(env, xsink, qstr, args);
if (*xsink) {
return nullptr;
}
Expand All @@ -339,10 +347,48 @@ QoreHashNode* QoreJdbcConnection::selectRow(const QoreString* qstr, const QoreLi
}

QoreValue QoreJdbcConnection::exec(const QoreString* qstr, const QoreListNode* args, ExceptionSink* xsink) {
assert(connection);
Env env;
try {
QoreJdbcStatement stmt(xsink, this);

bool result_set = stmt.exec(env, xsink, qstr, args);
if (*xsink) {
return QoreValue();
}

if (result_set) {
return stmt.getOutputHash(env, xsink, false);
}

return stmt.rowsAffected(env);
} catch (jni::Exception& e) {
e.convert(xsink);
}
assert(*xsink);
return QoreValue();
}

QoreValue QoreJdbcConnection::execRaw(const QoreString* qstr, ExceptionSink* xsink) {
assert(connection);
Env env;
try {
QoreJdbcStatement stmt(xsink, this);

bool result_set = stmt.exec(env, xsink, qstr, nullptr);
if (*xsink) {
return QoreValue();
}

if (result_set) {
return stmt.getOutputHash(env, xsink, false);
}

return stmt.rowsAffected(env);
} catch (jni::Exception& e) {
e.convert(xsink);
}
assert(*xsink);
return QoreValue();
}
}
Loading

0 comments on commit a985d30

Please sign in to comment.