From: <mie...@us...> - 2013-05-10 04:36:21
|
Revision: 9192 http://sourceforge.net/p/oorexx/code-0/9192 Author: miesfeld Date: 2013-05-10 04:36:16 +0000 (Fri, 10 May 2013) Log Message: ----------- Feature Requests: #541 Allow registration of user defined collations in ooSQLite See ticket [FeaturRequests:#541] #543 Add access to sqlite3_load_extension in ooSQLite See ticket [FeaturRequests:#543] Modified Paths: -------------- incubator/ooSQLite/examples/loadLibrary.rex incubator/ooSQLite/examples/loadPackage.rex incubator/ooSQLite/examples/user.extensions/examplePackage.cpp incubator/ooSQLite/src/ooSQLite.cpp incubator/ooSQLite/src/ooSQLite.hpp incubator/ooSQLite/src/oosqlPackage.hpp incubator/ooSQLite/src/rexx/ooSQLite.cls Added Paths: ----------- incubator/ooSQLite/examples/udfExample.rex Modified: incubator/ooSQLite/examples/loadLibrary.rex =================================================================== --- incubator/ooSQLite/examples/loadLibrary.rex 2013-05-06 04:34:14 UTC (rev 9191) +++ incubator/ooSQLite/examples/loadLibrary.rex 2013-05-10 04:36:16 UTC (rev 9192) @@ -79,9 +79,11 @@ return .ooSQLExtensions~lastErrCode end - hFunc = lib~getHandle('reverse'); say 'hFunc:' hFunc + hReverse = lib~getHandle('reverse'); say 'hReverse:' hReverse + hEbcdic = lib~getHandle('ebcdic'); say 'hEbcdic: ' hEbcdic - return 0 + ret = dbConn~createCollation('REVERSE', hReverse) + ret = dbConn~createCollation('EBCDIC', hEbcdic) sql = "SELECT * FROM foods where name like 'J%' ORDER BY name COLLATE REVERSE;" resultSet = dbConn~exec(sql, .true) Modified: incubator/ooSQLite/examples/loadPackage.rex =================================================================== --- incubator/ooSQLite/examples/loadPackage.rex 2013-05-06 04:34:14 UTC (rev 9191) +++ incubator/ooSQLite/examples/loadPackage.rex 2013-05-10 04:36:16 UTC (rev 9192) @@ -70,6 +70,18 @@ return .ooSQLExtensions~lastErrCode end + sql = "SELECT season, strAggregate(name) from episodes group by season;" + resultSet = dbConn~exec(sql, .true) + + say 'SQL: ' sql + say 'Result Set: ' resultSet + say 'Result Set Class:' resultSet~class + say + say 'Hit enter to continue' + pull + z = printStrAgg(resultSet) + say + sql = "SELECT * FROM foods where name like 'J%' ORDER BY name COLLATE REVERSE;" resultSet = dbConn~exec(sql, .true) @@ -116,6 +128,45 @@ ret = dbConn~close + -- Open a new database connection to the same database. + dbConn2 = .ooSQLiteConnection~new(dbName, .ooSQLite~OPEN_READWRITE) + package = .ooSQLExtensions~getPackage('examplePackage') + + package~register(dbConn2) + + sql = "SELECT half(11);" + resultSet = dbConn2~exec(sql, .true) + + say 'SQL: ' sql + say 'Result Set: ' resultSet + say 'Result Set Class:' resultSet~class + say + say 'Hit enter to continue' + pull + z = printResultSet(resultSet) + + dbConn2~close + + + dbConn3 = .ooSQLiteConnection~new(dbName, .ooSQLite~OPEN_READWRITE) + package = .ooSQLExtensions~getPackage('examplePackage') + + collation = package~getCollation('reverse') + dbConn3~createCollation('reverse', collation) + + sql = "SELECT * FROM foods where name like 'J%' ORDER BY name COLLATE REVERSE;" + resultSet = dbConn3~exec(sql, .true) + + say 'SQL: ' sql + say 'Result Set: ' resultSet + say 'Result Set Class:' resultSet~class + say + say 'Hit enter to continue' + pull + z = printResultSet(resultSet) + + dbConn3~close + return ret ::requires 'ooSQLite.cls' @@ -148,6 +199,43 @@ return 0 +-- strAggregate specific utility routine used to print the strAggregate result +-- set. +::routine printStrAgg + use arg rs + + colCount = 2 + rowCount = rs~items + + headers = rs[1] + line = headers[1]~left(9) || headers[2] + + say line + say '='~copies(80) + + do i = 2 to rowCount + record = rs[i] + if record[1] == .nil then line = 'NULL'~left(9) || record[2] + else line = record[1]~left(9) || record[2] + + if line~length > 80 then do + say line~left(80) + + line = ' '~copies(9) || line~substr(81) + do while line~length > 80 + say line~left(80) + line = ' '~copies(9) || line~substr(81) + end + end + else do + say line + end + say + end + say + + return 0 + ::routine getOSName parse upper source os . Added: incubator/ooSQLite/examples/udfExample.rex =================================================================== --- incubator/ooSQLite/examples/udfExample.rex (rev 0) +++ incubator/ooSQLite/examples/udfExample.rex 2013-05-10 04:36:16 UTC (rev 9192) @@ -0,0 +1,277 @@ +#!/usr/bin/rexx +/*----------------------------------------------------------------------------*/ +/* */ +/* Copyright (c) 2013-2013 Rexx Language Association. All rights reserved. */ +/* */ +/* This program and the accompanying materials are made available under */ +/* the terms of the Common Public License v1.0 which accompanies this */ +/* distribution. A copy is also available at the following address: */ +/* http://www.oorexx.org/license.html */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* Redistributions of source code must retain the above copyright */ +/* notice, this list of conditions and the following disclaimer. */ +/* Redistributions in binary form must reproduce the above copyright */ +/* notice, this list of conditions and the following disclaimer in */ +/* the documentation and/or other materials provided with the distribution. */ +/* */ +/* Neither the name of Rexx Language Association nor the names */ +/* of its contributors may be used to endorse or promote products */ +/* derived from this software without specific prior written permission. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS */ +/* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT */ +/* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS */ +/* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT */ +/* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, */ +/* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED */ +/* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, */ +/* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY */ +/* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING */ +/* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS */ +/* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/*----------------------------------------------------------------------------*/ + +/** + * udfExample.rex + * + * This example demonstrates the createFunction() method of the + * ooSQLiteConnection class. The createFunction() method is used to add SQL + * functions or aggregates, or to redefine the behavior of existing SQL + * functions or aggregates. + * + * The functions or aggregates can be implemented externally in C / C++ code, + * or they can be implemented in Rexx. This example shows the implementaion of + * some user defined functions implmented in Rexx. + */ + + -- Set the result set format to an array of arrays: + .ooSQLite~recordFormat = .ooSQLite~OO_ARRAY_OF_ARRAYS + + dbName = 'ooFoods.rdbx' + + dbConn = .ooSQLiteConnection~new(dbName, .ooSQLite~OPEN_READWRITE) + + -- Instantiate our Rexx object that contains our callback methods. + callBackObj = .FunctionImpl~new + + -- Create an aggregate + dbConn~createFunction('strAggregate', callBackObj, 1, , 'strAggStep', 'strAggFinal') + + sql = "SELECT season, strAggregate(name) from episodes group by season;" + resultSet = dbConn~exec(sql, .true) + + z = printStrAgg(resultSet) + say + return 0 + + + dbConn~createFunction('helloWorld', callBackObj, 1, 'helloWorld') + dbConn~createFunction('half', callBackObj, 1, 'half') + dbConn~createFunction('echo', callBackObj, 1, 'echo') + + sql = "SELECT helloWorld('Rick');" + resultSet = dbConn~exec(sql, .true) + + z = printResultSet(resultSet) + say + + sql = "SELECT helloWorld('ooRexx');" + resultSet = dbConn~exec(sql, .true) + + z = printResultSet(resultSet) + say + + sql = "SELECT half(11);" + resultSet = dbConn~exec(sql, .true) + + z = printResultSet(resultSet) + say + + sql = "SELECT half(197);" + resultSet = dbConn~exec(sql, .true) + + z = printResultSet(resultSet) + say + + sql = "SELECT half(114599);" + resultSet = dbConn~exec(sql, .true) + + z = printResultSet(resultSet) + say + + sql = "SELECT echo(56.78) as reply, typeof(echo(56.78)) as type;" + resultSet = dbConn~exec(sql, .true) + + z = printResultSet(resultSet) + say + + sql = "SELECT echo(X'ab24') as reply, typeof(echo(X'ab24')) as type;" + resultSet = dbConn~exec(sql, .true) + + z = printResultSet(resultSet) + say + + sql = "SELECT echo(NULL) as reply, typeof(echo(NULL)) as type;" + resultSet = dbConn~exec(sql, .true) + + z = printResultSet(resultSet) + say + + -- When the database connection is closed, the user defined functions are + -- unregisterd. + ret = dbConn~close + + return ret + +::requires 'ooSQLite.cls' + +::class 'strCollecter' +::attribute str +::method init + use arg firstStr + self~str = firstStr + +::method addStr + use arg str + self~str ||= ',' str + + + +-- The FunctionImpl class is used to define the callback methods for our +-- functions. +::class 'FunctionImpl' inherit ooSQLiteConstants + +::method strAggStep + use arg dbConn, cntx, values, collecterObj, userData + + str = .OOSQLValue~text(values[1]) + + if collecterObj == .nil then do + collObj = .strCollecter~new(str) + return collObj + end + + collecterObj~addStr(str) + return self~OK + + +::method strAggFinal + use arg dbConn, cntx, collecterObj, userData + + .ooSQLResult~text(cntx, collecterObj~str) + + -- Must return a result code. You do not return an .ooSQLResult. + return self~OK + + +-- Our registered helloWorld() callback method. SQLite invokes this method when +-- it encounters a SQL function named helloWord +::method helloWorld + use arg dbConn, cntx, values, userData + + name = .ooSQLValue~text(values[1]) + txt = 'Hello' name'|' + + .ooSQLResult~text(cntx, txt) + + -- Must return a result code. You do not return an .ooSQLResult. + return self~OK + + +-- Our registered half() callback method. SQLite invokes this method when it +-- encounters a SQL function named half +::method half + use arg dbConn, cntx, values, userData + + d = .ooSQLValue~double(values[1]) + d = d / 2 + + .ooSQLResult~double(cntx, d) + + -- Must return a result code. You do not return an .ooSQLResult. + return self~OK + + +-- Our registered echo() callback method. SQLite invokes this method when it +-- encounters a SQL function named ehco +::method echo + use arg dbConn, cntx, values, userData + + .ooSQLResult~value(cntx, values[1]) + + -- Must return a result code. You do not return an .ooSQLResult. + return self~OK + + + +-- Common utility routine used to print a result set that is an array of arrays. +::routine printResultSet + use arg rs + + colCount = rs[1]~items + rowCount = rs~items + + line = '' + headers = rs[1] + do j = 1 to colCount + line ||= headers[j]~left(25) + end + + say line + say '='~copies(80) + + do i = 2 to rowCount + line = '' + record = rs[i] + do j = 1 to colCount + if record[j] == .nil then line ||= ''~left(25) + else line ||= record[j]~left(25) + end + + say line + end + say + + return 0 + +-- strAggregate specific utility routine used to print the strAggregate result +-- set. +::routine printStrAgg + use arg rs + + colCount = 2 + rowCount = rs~items + + headers = rs[1] + line = headers[1]~left(9) || headers[2] + + say line + say '='~copies(80) + + do i = 2 to rowCount + record = rs[i] + if record[1] == .nil then line = 'NULL'~left(9) || record[2] + else line = record[1]~left(9) || record[2] + + if line~length > 80 then do + say line~left(80) + + line = ' '~copies(9) || line~substr(81) + do while line~length > 80 + say line~left(80) + line = ' '~copies(9) || line~substr(81) + end + end + else do + say line + end + say + end + say + + return 0 Property changes on: incubator/ooSQLite/examples/udfExample.rex ___________________________________________________________________ Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Modified: incubator/ooSQLite/examples/user.extensions/examplePackage.cpp =================================================================== --- incubator/ooSQLite/examples/user.extensions/examplePackage.cpp 2013-05-06 04:34:14 UTC (rev 9191) +++ incubator/ooSQLite/examples/user.extensions/examplePackage.cpp 2013-05-10 04:36:16 UTC (rev 9192) @@ -204,6 +204,64 @@ } +typedef struct _StrAggCntx +{ + size_t count; + char *result; +} StrAggCntx; +typedef StrAggCntx *pStrAggCntx; + +void strAggStep(sqlite3_context* sqlCntx, int countArgs, sqlite3_value** values) +{ + pStrAggCntx psac = (pStrAggCntx) sqlite3_aggregate_context(sqlCntx, sizeof(StrAggCntx)); + if ( psac == NULL ) + { + sqlite3_result_error_nomem(sqlCntx); + return; + } + + static const char comma[] = ", "; + + const char *txt = (const char *)sqlite3_value_text(values[0]); + size_t len = strlen(txt); + + if ( psac->result == NULL ) + { + psac->result = (char *)sqlite3_malloc((int)len + 1); + if ( psac->result == NULL ) + { + sqlite3_result_error_nomem(sqlCntx); + return; + } + + memcpy(psac->result, txt, len + 1); + psac->count = len; + } + else + { + //const size_t commaLen = sizeof(comma); + const size_t commaLen = strlen(comma); + psac->result = (char *)sqlite3_realloc(psac->result, (int)(psac->count + len + commaLen + 1)); + memcpy(psac->result + psac->count, comma, commaLen); + psac->count += commaLen; + memcpy(psac->result + psac->count, txt, len + 1); + psac->count += len; + } +} + +void strAggFinalize(sqlite3_context* sqlCntx) +{ + pStrAggCntx psac = (pStrAggCntx) sqlite3_aggregate_context(sqlCntx, sizeof(StrAggCntx)); + if ( psac == NULL || psac->result == NULL ) + { + sqlite3_result_error_nomem(sqlCntx); + return; + } + + sqlite3_result_text(sqlCntx, psac->result, (int)psac->count, sqlite3_free); +} + + /** * examplePackage_collations is an array of SQLiteCollationEntry structs. See * the SQLiteCollationEntry struct in oosqlPackage.hpp for the fields in the @@ -219,12 +277,13 @@ * SQLite. */ SQLiteCollationEntry examplePackage_collations[] = { - // The REVERSE collation. No destroy callback, no user data pointer. - OOSQL_COLLATION(reverse, reverse, NULL, NULL, NULL), + // The REVERSE collation. No destroy callback, no user data pointer, no + // collation needed callback. + OOSQL_COLLATION(reverse, reverse, NULL, NULL, NULL, NULL), // The EBCDIC collation. We fill out the struct fields completely instead of // using the OOSQL_COLLATION macro. - {NULL, "ebcdic", ebcdic, NULL, NULL, NULL, 0}, + {NULL, "ebcdic", ebcdic, NULL, NULL, NULL, NULL, 0}, // The last entry in the struc, must always be present. Here the macro is // simplier to use. To fill it in manually, use all NULLs. @@ -243,6 +302,7 @@ // Only 1 function. Only the function callback is used, the other callbacks // are NULL. The function callback takes 1 arg. OOSQL_FUNCTION(half, half, NULL, NULL, NULL, NULL, NULL, 1), + {NULL, "strAggregate", NULL, strAggStep, strAggFinalize, NULL, NULL, NULL, 1, 0}, OOSQL_LAST_FUNCTION() }; Modified: incubator/ooSQLite/src/ooSQLite.cpp =================================================================== --- incubator/ooSQLite/src/ooSQLite.cpp 2013-05-06 04:34:14 UTC (rev 9191) +++ incubator/ooSQLite/src/ooSQLite.cpp 2013-05-10 04:36:16 UTC (rev 9192) @@ -62,6 +62,7 @@ RexxObjectPtr TheOneObj = NULLOBJECT; RexxObjectPtr TheTwoObj = NULLOBJECT; RexxObjectPtr TheNegativeOneObj = NULLOBJECT; +RexxObjectPtr TheZeroPointerObj = NULLOBJECT; // Initialized in the class init methods. RexxClassObject TheOOSQLiteClass = NULLOBJECT; @@ -108,6 +109,9 @@ TheTwoObj = c->WholeNumber(2); c->RequestGlobalReference(TheTwoObj); + TheZeroPointerObj = c->NewPointer(NULL); + c->RequestGlobalReference(TheZeroPointerObj); + #ifndef _WIN32 crit_sec = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); if ( crit_sec == NULL ) @@ -2004,8 +2008,264 @@ } } +/** + * The function callback method for user defined functions, implemented in Rexx + * code. In the sqlite3_create_function, we assign this function for the xFunc + * callback. When SQLite invokes this function, we then assemble the + * information and invoke a method in the Rexx code, where the user implements + * the actual function details. + * + * @param sqlContext + * @param argc + * @param argv + * + * @note This function and the stepCallback function are almost identical. + */ +static void functionCallback(sqlite3_context *sqlContext, int argc, sqlite3_value **argv) +{ + pCFunctionCallback pcf = (pCFunctionCallback)sqlite3_user_data(sqlContext); + RexxThreadContext *c = pcf->callbackContext; + bool isMethod = (pcf->callbackObj == NULLOBJECT) ? false : true; + + if ( pcf->initialThreadID != oosqlGetCurrentThreadId() ) + { + if ( ! pcf->interpreter->AttachThread(&c) ) + { + // If we can't attach, there is nothing we can do, we need to just + // quit. + return; + } + } + + RexxPointerObject sqlCntx = c->NewPointer(sqlContext); + RexxArrayObject args; + RexxObjectPtr reply = NULLOBJECT; + wholenumber_t rc = 0; + + RexxArrayObject values = c->NewArray(argc); + for ( int i = 0; i < argc; i++ ) + { + RexxPointerObject v = c->NewPointer(argv[i]); + c->ArrayPut(values, v, i + 1); + c->ReleaseLocalReference(v); + } + + if ( pcf->userData == NULL ) + { + args = c->ArrayOfFour(pcf->rexxDB, sqlCntx, values, TheNilObj); + } + else + { + args = c->ArrayOfFour(pcf->rexxDB, sqlCntx, values, pcf->userData); + } + + if ( isMethod ) + { + reply = c->SendMessage(pcf->callbackObj, pcf->funcMethod, args); + } + else + { + reply = c->CallRoutine(pcf->funcRtn, args); + } + + // We just check for errors, we do not actually use rc. + replyIsGood(c, reply, &rc, pcf->funcMethod, pcf->funcName, isMethod); + + c->ReleaseLocalReference(reply); + c->ReleaseLocalReference(values); + c->ReleaseLocalReference(args); + + if ( c != pcf->callbackContext ) + { + c->DetachThread(); + } +} + /** + * The function callback method for user defined aggregates, implemented in Rexx + * code. In the sqlite3_create_function, we assign this function for the xStep + * callback. When SQLite invokes this function, we then assemble the + * information and invoke a method in the Rexx code, where the user implements + * the actual aggregate. + * + * @param sqlContext + * @param argc + * @param argv + * + * @note This function and the functionCallback function are very similar. + * But, because of the subtle differences they do not share code. + */ +static void stepCallback(sqlite3_context *sqlContext, int argc, sqlite3_value **argv) +{ + pCAggregateCallback pcacb = NULL; + pCFunctionCallback pcf = NULL; + + pcacb = (pCAggregateCallback)sqlite3_aggregate_context(sqlContext, sizeof(CAggregateCallback)); + if ( pcacb == NULL ) + { + sqlite3_result_error_nomem(sqlContext); + return; + } + + if ( pcacb->pcfcb == NULL ) + { + pcacb->aggregateObj = TheNilObj; + pcacb->pcfcb = (pCFunctionCallback)sqlite3_user_data(sqlContext); + } + + pcf = pcacb->pcfcb; + + RexxThreadContext *c = pcf->callbackContext; + bool isMethod = (pcf->callbackObj == NULLOBJECT) ? false : true; + + if ( pcf->initialThreadID != oosqlGetCurrentThreadId() ) + { + if ( ! pcf->interpreter->AttachThread(&c) ) + { + // If we can't attach, there is nothing we can do, we need to just + // quit. + sqlite3_result_error(sqlContext, "failed to attach to ooRexx interpreter thread", -1); + return; + } + } + + RexxPointerObject sqlCntx = c->NewPointer(sqlContext); + RexxArrayObject args; + RexxObjectPtr reply = NULLOBJECT; + wholenumber_t rc = 0; + + RexxArrayObject values = c->NewArray(argc); + for ( int i = 0; i < argc; i++ ) + { + RexxPointerObject v = c->NewPointer(argv[i]); + c->ArrayPut(values, v, i + 1); + c->ReleaseLocalReference(v); + } + + args = c->ArrayOfFour(pcf->rexxDB, sqlCntx, values, pcacb->aggregateObj); + + if ( pcf->userData == NULL ) + { + c->ArrayAppend(args, TheNilObj); + } + else + { + c->ArrayAppend(args, pcf->userData); + } + + if ( isMethod ) + { + reply = c->SendMessage(pcf->callbackObj, pcf->stepMethod, args); + } + else + { + reply = c->CallRoutine(pcf->stepRtn, args); + } + + if ( checkForCondition(c, false) ) + { + sqlite3_result_error_code(sqlContext, SQLITE_MISUSE); + return; + } + else if ( reply == NULLOBJECT ) + { + if ( isMethod ) + { + noMsgReturnException(c, pcf->stepMethod); + } + else + { + noRoutineReturnException(c, pcf->stepName); + } + + // This is used to print out the condition we just raised. + checkForCondition(c, false); + + sqlite3_result_error_code(sqlContext, SQLITE_MISUSE); + return; + } + + if ( pcacb->aggregateObj == TheNilObj ) + { + // This was the first invocation of the step function. + pcacb->aggregateObj = c->RequestGlobalReference(reply); + } + + // We can release the local reference to reply because we have a global + // reference now. + c->ReleaseLocalReference(reply); + c->ReleaseLocalReference(values); + c->ReleaseLocalReference(args); + + if ( c != pcf->callbackContext ) + { + c->DetachThread(); + } +} + +static void finalCallback(sqlite3_context *sqlContext) +{ + pCAggregateCallback pcacb = (pCAggregateCallback)sqlite3_aggregate_context(sqlContext, sizeof(CAggregateCallback)); + if ( pcacb == NULL ) + { + sqlite3_result_error_nomem(sqlContext); + return; + } + + pCFunctionCallback pcf = pcacb->pcfcb; + RexxThreadContext *c = pcf->callbackContext; + bool isMethod = (pcf->callbackObj == NULLOBJECT) ? false : true; + + if ( pcf->initialThreadID != oosqlGetCurrentThreadId() ) + { + if ( ! pcf->interpreter->AttachThread(&c) ) + { + // If we can't attach, there is nothing we can do, we need to just + // quit. + sqlite3_result_error(sqlContext, "failed to attach to ooRexx interpreter thread", -1); + return; + } + } + + RexxPointerObject sqlCntx = c->NewPointer(sqlContext); + RexxArrayObject args; + RexxObjectPtr reply = NULLOBJECT; + wholenumber_t rc = 0; + + if ( pcf->userData == NULL ) + { + args = c->ArrayOfFour(pcf->rexxDB, sqlCntx, pcacb->aggregateObj, TheNilObj); + } + else + { + args = c->ArrayOfFour(pcf->rexxDB, sqlCntx, pcacb->aggregateObj, pcf->userData); + } + + if ( isMethod ) + { + reply = c->SendMessage(pcf->callbackObj, pcf->finalMethod, args); + } + else + { + reply = c->CallRoutine(pcf->finalRtn, args); + } + + // We just check for errors, we do not actually use rc. + replyIsGood(c, reply, &rc, pcf->stepMethod, pcf->finalName, isMethod); + + c->ReleaseLocalReference(reply); + c->ReleaseLocalReference(args); + + c->ReleaseGlobalReference(pcacb->aggregateObj); + + if ( c != pcf->callbackContext ) + { + c->DetachThread(); + } +} + +/** * A generic call back into Rexx used to send a record from sqlite3_exec() to * the user. This call turns out to be the same whether the format of single * record is a stem or a diretory. @@ -2451,6 +2711,99 @@ return (int)rc; } + +static int invalidCollation(RexxThreadContext *c, CSTRING name) +{ + char buf[256]; + + snprintf(buf, 256, "the compare function for the collation: %s can not be null", name); + userDefinedMsgException(c, buf); + return SQLITE_MISUSE; +} + +static int invalidFunction(RexxThreadContext *c, CSTRING name) +{ + char buf[256]; + + snprintf(buf, 256, "the func, step, and final functions for the function: %s can not all be null", name); + userDefinedMsgException(c, buf); + return SQLITE_MISUSE; +} + +static int invalidModule(RexxThreadContext *c, CSTRING name) +{ + char buf[256]; + + snprintf(buf, 256, "the module table for the user defined module: %s can not be null", name); + userDefinedMsgException(c, buf); + return SQLITE_MISUSE; +} + +/** + * Registers an external collation with a SQLite database connection. + * + * @param c + * @param e + * @param pConn + * + * @return int + */ +int registerCollation(RexxMethodContext *c, pSQLiteCollationEntry e, pCooSQLiteConn pConn) +{ + if ( e->entryCompare == NULL ) + { + return invalidCollation(c->threadContext, e->name); + } + + void *userData = NULL; + if ( e->entryGetUserData != NULL ) + { + userData = e->entryGetUserData(); + } + else if ( e->pUserData != NULL ) + { + userData = e->pUserData; + } + + return sqlite3_create_collation_v2(pConn->db, e->name, SQLITE_UTF8, userData, e->entryCompare, e->entryDestroy); +} + +int registerFunction(RexxMethodContext *c, pSQLiteFunctionEntry e, pCooSQLiteConn pConn) +{ + void *userData = NULL; + if ( e->entryGetUserData != NULL ) + { + userData = e->entryGetUserData(); + } + else if ( e->pUserData != NULL ) + { + userData = e->pUserData; + } + + return sqlite3_create_function_v2(pConn->db, e->name, e->countArgs, SQLITE_UTF8, userData, e->entryFunc, + e->entryStep, e->entryFinal, e->entryDestroy); +} + +int registerModule(RexxMethodContext *c, pSQLiteModuleEntry e, pCooSQLiteConn pConn) +{ + if ( e->moduleTab == NULL ) + { + return invalidModule(c->threadContext, e->name); + } + + void *userData = NULL; + if ( e->entryGetUserData != NULL ) + { + userData = e->entryGetUserData(); + } + else if ( e->pUserData != NULL ) + { + userData = e->pUserData; + } + + return sqlite3_create_module_v2(pConn->db, e->name, e->moduleTab, userData, e->entryDestroy); +} + #define ooSQLite_Methods_Section 1 @@ -2796,6 +3149,8 @@ return "__rxCollationNeeded"; case commitHook : return "__rxCommitHook"; + case function : + return "__rxFunction"; case profileHook : return "__rxProfileHook"; case progressHandler : @@ -2820,21 +3175,27 @@ * * This function retrieves the Rexx buffer object. * + * Note that for collation and function callbacks, the Rexx buffer object is + * removed and returned from the table that holds it. This works fine, because, + * if the buffer is in the table, then a new collation or function with the same + * name is being created. Which will replace the current SQLite collation or + * function of the same name. + * * @param c * @param cb * @param zName * * @return RexxBufferObject * - * @remarks This is somewhat complicated for a collation callback because any - * single database connection can have more than one collation - * function. + * @remarks This is somewhat complicated for collation and function callbacks + * because any single database connection can have more than one + * collation or function. */ static RexxBufferObject getCallbackVar(RexxMethodContext *c, CallbackType cb, CSTRING zName) { CSTRING varName = getCallbackVarName(cb); - if ( cb == collation ) + if ( cb == collation || cb == function ) { RexxObjectPtr table = c->GetObjectVariable(varName); if ( table != NULLOBJECT ) @@ -2859,15 +3220,17 @@ * @param cb * @param zName * - * @remarks This is somewhat complicated for a collation callback because any - * single database connection can have more than one collation - * function. + * @remarks This is somewhat complicated for collation and function callbacks + * because any single database connection can have more than one + * collation or function. For these, we use the object variable name + * to store a table and put each individual buffer in the table, + * indexed by the *upper* case name of the collation or function. */ static void setCallbackVar(RexxMethodContext *c, RexxBufferObject buf, CallbackType cb, CSTRING zName) { CSTRING varName = getCallbackVarName(cb); - if ( cb == collation ) + if ( cb == collation || cb == function ) { RexxObjectPtr table = c->GetObjectVariable(varName); if ( table == NULLOBJECT ) @@ -2882,7 +3245,7 @@ if ( table != NULLOBJECT ) { RexxStringObject index = c->String(zName); - c->SendMessage2(table, "PUT", buf, index); + c->SendMessage2(table, "PUT", buf, c->StringUpper(index)); } return; @@ -2941,6 +3304,41 @@ } } +static void cleanupAllFunctionBuffers(RexxMethodContext *c) +{ + + CSTRING varName = getCallbackVarName(function); + RexxObjectPtr table = c->GetObjectVariable(varName); + + if ( table != NULLOBJECT ) + { + RexxArrayObject items = (RexxArrayObject)c->SendMessage0(table, "ALLITEMS"); + + if ( items != NULLOBJECT ) + { + size_t count = c->ArrayItems(items); + for ( size_t i = 1; i <= count; i++ ) + { + RexxBufferObject cfcbBuffer = (RexxBufferObject)c->ArrayAt(items, i); + + if ( cfcbBuffer != NULLOBJECT ) + { + pCFunctionCallback cfcb = (pCFunctionCallback)c->BufferData(cfcbBuffer); + + c->ReleaseGlobalReference(cfcb->callbackObj); + + if ( cfcb->userData != NULL ) + { + c->ReleaseGlobalReference(cfcb->userData); + } + } + } + } + c->SendMessage0(table, "EMPTY"); + c->DropObjectVariable(varName); + } +} + /** * Cleans up the Rexx side of one of the callback functions in SQLite. * @@ -3007,12 +3405,14 @@ cleanupCallback(c, authorizer, NULL); cleanupCallback(c, busyHandler, NULL); cleanupCallback(c, collation, NULL); + cleanupCallback(c, collationNeeded, NULL); cleanupCallback(c, commitHook, NULL); cleanupCallback(c, profileHook, NULL); cleanupCallback(c, progressHandler, NULL); cleanupCallback(c, rollbackHook, NULL); cleanupCallback(c, traceHook, NULL); cleanupCallback(c, updateHook, NULL); + cleanupAllFunctionBuffers(c); } @@ -3041,6 +3441,31 @@ /** + * A callback clean up method for the function or aggregate callback. The setup + * for this type of callback uses a different struct than the generic callback. + * The buffer still may contain a user data object. If it does, that object has + * a global reference that should be released. The release is done here. + * + * @param c + * @param buf + */ +static void cleanupThisFunctionCallback(RexxThreadContext *c, RexxBufferObject buf) +{ + if ( buf != NULLOBJECT && buf != TheNilObj ) + { + pCFunctionCallback cfcb = (pCFunctionCallback)c->BufferData(buf); + + c->ReleaseGlobalReference(cfcb->callbackObj); + + if ( cfcb->userData != NULL ) + { + c->ReleaseGlobalReference(cfcb->userData); + } + } +} + + +/** * Methods for the .ooSQLiteConstants class. */ #define OOSQLCONSTANTS_CLASS "ooSQLiteConstants" @@ -3159,6 +3584,12 @@ return NULLOBJECT; } +/** ooSQLite::zeroPointer [attribute get] + */ +RexxMethod1(RexxObjectPtr, oosql_zeroPointer_atr_cls, CSELF, pCSelf) +{ + return TheZeroPointerObj; +} /** ooSQLite::compileOptionGet [class method] * @@ -3783,6 +4214,91 @@ } /** + * Sets up the infrastructure for a user defined SQL function or aggregate + * implemented in Rexx. + * + * @param c + * @param pConn + * @param zName + * @param callbackObj + * @param countArgs + * @param rxFunc + * @param rxStep + * @param rxFinal + * @param userData + * + * @return ooSQLite result code + * + * @note rxFunc -> argument 4 + * rxStep -> argument 5 + * rxFinal -> arguemnt 6 + * + * @assumes The caller has ensured that if rxFunc is null, rxStep and rxFinal + * are not null. + */ +static RexxObjectPtr rexxFunctionCallbacks(RexxMethodContext *c, pCooSQLiteConn pConn, CSTRING zName, RexxObjectPtr callbackObj, + int countArgs, RexxObjectPtr rxFunc, RexxObjectPtr rxStep, RexxObjectPtr rxFinal, + RexxObjectPtr userData) +{ + RexxBufferObject oldCallback = getCallbackVar(c, function, zName); + RexxObjectPtr result = c->WholeNumber(SQLITE_MISUSE); + sqlite3 *db = pConn->db; + int rc = SQLITE_MISUSE; + RexxBufferObject fncBuffer = c->NewBuffer(sizeof(CFunctionCallback)); + + if ( fncBuffer == NULLOBJECT ) + { + outOfMemoryException(c->threadContext); + return result; + } + + setCallbackVar(c, fncBuffer, function, zName); + + pCFunctionCallback cfcb = (pCFunctionCallback)c->BufferData(fncBuffer); + memset(cfcb, 0, sizeof(CFunctionCallback)); + + cfcb->callbackObj = c->RequestGlobalReference(callbackObj); + cfcb->callbackContext = c->threadContext; + cfcb->rexxDB = pConn->rexxSelf; + cfcb->interpreter = c->threadContext->instance; + cfcb->initialThreadID = oosqlGetCurrentThreadId(); + + if ( userData != NULLOBJECT ) + { + cfcb->userData = c->RequestGlobalReference(userData); + } + + if ( rxFunc != NULLOBJECT ) + { + cfcb->funcMethod = c->ObjectToStringValue(rxFunc); + + rc = sqlite3_create_function_v2(db, zName, countArgs, SQLITE_UTF8, (void *)cfcb, functionCallback, NULL, NULL, NULL); + result = c->WholeNumber(rc); + } + else + { + cfcb->stepMethod = c->ObjectToStringValue(rxStep); + cfcb->finalMethod = c->ObjectToStringValue(rxFinal); + + rc = sqlite3_create_function_v2(db, zName, countArgs, SQLITE_UTF8, (void *)cfcb, NULL, stepCallback, finalCallback, NULL); + result = c->WholeNumber(rc); + } + + if ( oldCallback != NULLOBJECT ) + { + pCFunctionCallback cfcb = (pCFunctionCallback)c->BufferData(oldCallback); + + c->ReleaseGlobalReference(cfcb->callbackObj); + if ( cfcb->userData != NULLOBJECT ) + { + c->ReleaseGlobalReference(cfcb->userData); + } + } + + return result; +} + +/** * Returns the proper PragmaType enum for the specified name. * * @param c @@ -4715,7 +5231,7 @@ * .nil. * * @param userData [optional] This can be any Rexx object the user - * desires. The object will be sent as the secon argument + * desires. The object will be sent as the second argument * to the callback method when it is invoked. This * argument is ignored when the callbackObj argument is * .nil. @@ -4793,164 +5309,434 @@ * connection. A collation defines how two strings are compared. SQLite uses * the collation whenever it needs to compare two strings in the database. * + * The implementation of the collation can either be done in Rexx code, or it + * can be done in C / C++ native code residing in an external library. + * * @param collationName [required] The collation name. This is the name that * identifies the collation to SQLite. * - * @param callbackObj [required] Identifies where 'callBackName' comes from. + * @param callbackObj [required] Identifies the collation implementation and + * dictates what the 'callbackOpt2' argument must be. * - * If this is an instantiated Rexx object, then - * 'callBackName' is a method within the class of the + * 1.) If this is an instantiated Rexx object, then + * 'callbackOpt2' is a method within the class of this * object. * - * If this is a .Pointer object then it identifies an - * already loaded library of external functions + * 2.) If this is a .Pointer object then it is the handle + * of an external function that implements the collation. + * The handle must be obtained though an ooSQLLibrary + * object. * - * If this is a .Directory object then the NAME index of - * the directory is the string name of an external - * library that should be loaded, if it is not already - * loaded. The HANDLE index is used to return the - * .Pointer object when the library is loaded - * successfully. If this method fails then the value for - * the HANDLE index is undefined. + * 3.) If this is a .Collation object then all the + * information needed to register the collation with this + * database connection is contained within the .Collation + * object. * - * @param callBackName [optional] Identifies the callback method or external - * function that is registered with SQLite for the - * collation. + * 4.) If this a zero pointer (the zero pointer attribute + * of the .ooSQLite class) than the external C collation + * function named by collationName is removed. All other + * arguments are ignored * - * If 'callBackObj' is a Rexx object, then 'callBackName' - * is name of the method to be invoked for the - * coallation. In this case only, if the argument is - * omitted the default name for the method is - * collationCallBack(). + * 5.) If this is the .nil object, then the Rexx + * collation named by collationNamed is removed. All + * other arguments are ignored. * - * If 'callBackObj' is a .Directory object or a .Pointer - * object then a similar pattern is used as is used for - * the 'callBackObj'. + * @param callbackOpt2 [optional] The value and meaning of this argument is + * dependent on the value of the required 'callbackObj' + * argument. * - * If it is a .Pointer object then it identifies and - * already loaded external *function*. Note that a - * loaded library and a loaded function are two different - * things. + * 1.) In this case 'callbackOpt2' is the name of the + * method to be invoked for the coallation. In this case + * if the argument is omitted the default name for the + * method is collationCallBack(). * - * If 'callBackName' is a directory object, then the NAME - * index identifies an external function to be loaded - * from the external library identified by 'callBackObj'. - * On success the HANDLE index is used to return the - * .Pointer object for the external function. If this - * method fails, the value for the HANDLE index is - * undefined. + * 2.) 'callbackOpt2', if not omitted, must be a + * .Pointer object that is the handle for a xDestroy + * function implemented in the external library. The + * handle is obtained through an ooSQLLibrary object. + * Unless you have a collation implemented in C / C++ + * there is no need to understand what the xDestroy + * fucntion is. * + * 3.) This argument is ignored. The xDestroy function + * infomation is contained within the .Collation object. * - * @param userData [optional] This can be any Rexx object the user - * desires. * - * If the 'callBackObj' is a Rexx object, then this - * object will be sent as the first argument to the + * @param userData [optional] The meaning and value of this argument is + * dependent on what 'callbackObj' is. + * + * 1.) This can be any Rexx object the user desires. The + * object will be sent as the first argument to the Rexx * callback method. * - * If the 'callBackObj is an external library, then this - * is a little more complicated and is described in the - * section on how to write an external collation - * function. + * 2.) If not omitted, then this argument must be a + * .Pointer object that is the handle of a data block in + * the external library. The handle is obtained through + * an ooSQLLibrary object. The data block is sent by + * SQLite as an argument to the collation function + * identified by the 'callbackObj' argument. Again, the + * Rexx programmer does not need to understand this + * unless she is using a collation implemented in native + * C / C++ code. * - * If, the string value of this object is exactly - * "REMOVE", case does not matter, then the collation - * callback identified by 'callBackObj' and - * 'callBackName' is removed. Please note that "REMOVE - * ", " REMOVE", and "REMOVE" are all not exactly the - * same. + * 3.) This argument is ignored. The user data block + * information, if any, is contained within the + * .Collation object. * - * @return ?? + * @return An ooSQLite result code. * * @notes */ RexxMethod5(RexxObjectPtr, oosqlconn_createCollation, CSTRING, collationName, RexxObjectPtr, callbackObj, - OPTIONAL_RexxObjectPtr, callbackName, OPTIONAL_RexxObjectPtr, userData, CSELF, pCSelf) + OPTIONAL_RexxObjectPtr, callbackOpt2, OPTIONAL_RexxObjectPtr, userData, CSELF, pCSelf) { - RexxObjectPtr result = context->WholeNumber(SQLITE_MISUSE); + RexxObjectPtr result = context->Int32(SQLITE_MISUSE); + int rc = SQLITE_MISUSE; pCooSQLiteConn pConn = requiredDB(context, pCSelf); if ( pConn == NULL ) { return result; } - RexxMethodContext *c = context; - bool isExternal = true; - bool removeCollation = false; + if ( context->IsPointer(callbackObj) ) + { + if ( callbackObj == TheZeroPointerObj ) + { + rc = sqlite3_create_collation_v2(pConn->db, collationName, SQLITE_UTF8, NULL, NULL, NULL); + return context->Int32(rc); + } - if ( c->IsOfType(callbackObj, "POINTER") ) + fnXCompare xCmp = (fnXCompare)context->PointerValue((RexxPointerObject)callbackObj); + fnXDestroy xDst = NULL; + void *usrD = NULL; + + if ( argumentExists(3) ) + { + if ( ! context->IsPointer(callbackOpt2) ) + { + wrongClassException(context->threadContext, 3, "Pointer", callbackOpt2); + return result; + } + xDst = (fnXDestroy)context->PointerValue((RexxPointerObject)callbackOpt2); + } + + if ( argumentExists(4) ) + { + if ( ! context->IsPointer(userData) ) + { + wrongClassException(context->threadContext, 4, "Pointer", userData); + return result; + } + usrD = context->PointerValue((RexxPointerObject)userData); + } + + rc = sqlite3_create_collation_v2(pConn->db, collationName, SQLITE_UTF8, usrD, xCmp, xDst); + return context->Int32(rc); + } + else if ( context->IsOfType(callbackObj, "OOSQLCOLLATION") ) { - c->RaiseException0(Rexx_Error_Unsupported_method); - return result; + pSQLiteCollationEntry e = (pSQLiteCollationEntry)context->ObjectToCSelf(callbackObj); + rc = registerCollation(context, e, pConn); + return context->Int32(rc); } - else if ( c->IsDirectory(callbackObj) ) + else if ( callbackObj == TheNilObj ) { - c->RaiseException0(Rexx_Error_Unsupported_method); + return doCallbackSetup(context, pConn, TheNilObj, NULL, NULL, collation, 0, collationName); + } + + CSTRING mthName = NULL; + if ( argumentExists(3) ) + { + mthName = context->ObjectToStringValue(callbackOpt2); + } + + return doCallbackSetup(context, pConn, callbackObj, mthName, userData, collation, 0, collationName); +} + + +/** ooSQLiteConnection::createFunction() + * + * Adds, removes, or modifies a user defined SQL function or aggregate + * associated with this database connection. Although functions and aggregates + * are technically different, this documentation will use function from here on + * out to refer to both. + * + * The implementation of the function can either be done in Rexx code, or it + * can be done in C / C++ native code residing in an external library. + * + * @param functionName [required] The function name. This is the name that + * identifies the function to SQLite. + * + * @param callbackObj [required] Identifies the implementation and dictates + * what the 'callbackOptX' arguments must be. + * + * 1.) If this is an instantiated Rexx object, then the + * 'callbackOptX' arguments are methods within the class + * of this object. + * + * 2.) If this is a .Pointer object then it is the handle + * of an external function that implements the function, + * or a zero pointer. A handle must be obtained though an + * ooSQLLibrary object, and a zero pointer must be + * obtained through the zeroPointer attribute of the + * .ooSQLite class. + * + * To implement a SQL function, this must be a handle to + * an external function, *and* callbackOpt2 and + * callbackOpt3 must each be omitted or must each be a + * zero pointer. + * + * To implement a SQL aggregate, this must be a zero + * pointer, *and* callbackOpt2 and callbackOpt3 are + * required and are required to *not* be zero pointers. + * + * To *remove* a previously registered external function + * or aggregate, callbackOjb, callbackOpt2, and + * callbackOpt3 must *all* be zero pointers. This + * applies to external functions registered either using + * handles or registered using a .Function object + * + * 3.) If this is a .Function object then all the + * information needed to register the function with this + * database connection is contained within the .Function + * object. + * + * 4.) If this is the .nil object, then the Rexx function + * named by functionName is removed. All other arguments + * are ignored. + * + * + * @param countArgs [optional] The number of arguments that the SQL + * function or aggregate takes. If this argument is -1, + * then the SQL function or aggregate may take any number + * of arguments between 0 and the limit set by + * sqlite3_limit(SQLITE_LIMIT_FUNCTION_ARG). If the third + * parameter is less than -1 or greater than 127 then the + * behavior is undefined. + * + * Note, this argument is *only* optional if a function + * or aggregate is being removed. In all other cases it + * is required. + * + * @param callbackOpt2 [optional] The value and meaning of this argument is + * dependent on the value of the required 'callbackObj' + * argument. + * + * 1.) In this case 'callbackOpt2' is the name of the + * method to be invoked for the function. If this + * argument is omitted, then the user must be defining an + * aggregate. + * + * 2.) 'callbackOpt2', if not omitted, must be a + * .Pointer object that is the handle for the xStep + * function implemented in the external library. The + * handle is obtained through an ooSQLLibrary object. + * Unless you have a function implemented in C / C++ + * there is no need to understand what the xDestroy + * fucntion is. + * + * 3.) This argument is ignored. The xStep function + * infomation is contained within the .Function object. + * + * + * @param callbackOpt3 [optional] The value and meaning of this argument is + * dependent on the value of the required 'callbackObj' + * argument. + * + * 1.) In this case 'callbackOpt3' is the name of the + * method to be invoked for the step() callback function. + * If this argument is omitted the user must be defining + * a function. + * + * 2.) 'callbackOpt3', if not omitted, must be a .Pointer + * object that is the handle for the xFinal function + * implemented in the external library. The handle is + * obtained through an ooSQLLibrary object. + * + * 3.) This argument is ignored. The xFinal function + * infomation is contained within the .Function object. + * + * + * @param callbackOpt4 [optional] The value and meaning of this argument is + * dependent on the value of the required 'callbackObj' + * argument. + * + * 1.) In this case 'callbackOpt4' is the name of the + * method to be invoked for the final() callback + * function. If the argument is omitted the user must be + * defining a function. + * + * 2.) 'callbackOpt4', if not omitted, must be a .Pointer + * object that is the handle for the xDestroy function + * implemented in the external library. The handle is + * obtained through an ooSQLLibrary object. + * + * 3.) This argument is ignored. The xDestroy function + * infomation is contained within the .Function object. + * + * + * @param userData [optional] The meaning and value of this argument is + * dependent on what 'callbackObj' is. + * + * 1.) This can be any Rexx object the user desires. The + * object will be sent as the first argument to each of + * the registered callback methods. + * + * 2.) If not omitted, then this argument must be a + * .Pointer object that is the handle of a data block in + * the external library. The handle is obtained through + * an ooSQLLibrary object. The implementation of the + * function can gain access to this pointer using + * sqlite3_user_data(). The Rexx programmer does not + * need to understand this unless she is using a function + * implemented in native C / C++ code. + * + * 3.) This argument is ignored. The user data block + * information, if any, is contained within the + * .Function object. + * + * + * @return An ooSQLite result code. + * + * @notes To define a SQL function in Rexx, the user *must* specify the + * callbackOpt2 argument and *must* omit the callbackOpt3 and + * callbackOpt4 arguments. To define a SQL aggregate, the user *must* + * omit the callbackOpt2 argument and *must* specify the callbackOp3 + * and callbackOpt4 arguments. There are no default method names for + * the callbacks. + */ +RexxMethod8(RexxObjectPtr, oosqlconn_createFunction, CSTRING, functionName, RexxObjectPtr, callbackObj, OPTIONAL_int32_t, countArgs, + OPTIONAL_RexxObjectPtr, callbackOpt2, OPTIONAL_RexxObjectPtr, callbackOpt3, OPTIONAL_RexxObjectPtr, callbackOpt4, + OPTIONAL_RexxObjectPtr, userData, CSELF, pCSelf) +{ + RexxObjectPtr result = context->Int32(SQLITE_MISUSE); + int rc = SQLITE_MISUSE; + + pCooSQLiteConn pConn = requiredDB(context, pCSelf); + if ( pConn == NULL ) + { return result; } - else + RexxMethodContext *c = context; + + // Determine if we are removing this function. + if ( callbackObj == TheZeroPointerObj && callbackOpt2 == TheZeroPointerObj && callbackOpt3 == TheZeroPointerObj ) { - isExternal = false; + rc = sqlite3_create_function_v2(pConn->db, functionName, 0, SQLITE_UTF8, NULL, NULL, NULL, NULL, NULL); + return context->Int32(rc); } + else if ( callbackObj == TheNilObj ) + { + RexxBufferObject oldCallback = getCallbackVar(context, function, functionName); + rc = sqlite3_create_function_v2(pConn->db, functionName, 0, SQLITE_UTF8, NULL, NULL, NULL, NULL, NULL); - // Determine if we are removing this collation. - if ( argumentExists(4) ) - { - CSTRING str = c->ObjectToStringValue(userData); - if ( sqlite3_stricmp(str, "REMOVE") == 0 ) + if ( oldCallback != NULLOBJECT ) { - removeCollation = true; + cleanupThisFunctionCallback(context->threadContext, oldCallback); } + return context->Int32(rc); } - if ( isExternal ) + // We are not removing, arg 3 is required. + if ( argumentOmitted(3) ) { - if ( argumentOmitted(3) ) + missingArgException(context->threadContext, 3); + return result; + } + + if ( c->IsPointer(callbackObj) ) + { + fnXStep xStep = NULL; + fnXFinal xFnl = NULL; + fnXDestroy xDst = NULL; + void *usrD = NULL; + + fnXFunc xFunc = (fnXFunc)c->PointerValue((RexxPointerObject)callbackObj); + if ( xFunc == NULL ) { - c->RaiseException1(Rexx_Error_Incorrect_method_noarg, c->WholeNumber(3)); - return result; + if ( argumentOmitted(4) ) + { + missingArgException(context->threadContext, 4); + return result; + } + + if ( argumentOmitted(5) ) + { + missingArgException(context->threadContext, 5); + return result; + } + + if ( ! c->IsPointer(callbackOpt2) ) + { + wrongClassException(context->threadContext, 4, "Pointer", callbackOpt2); + return result; + } + if ( ! c->IsPointer(callbackOpt3) ) + { + wrongClassException(context->threadContext, 5, "Pointer", callbackOpt3); + return result; + } + + xStep = (fnXStep)c->PointerValue((RexxPointerObject)callbackOpt2); + xFnl = (fnXFinal)c->PointerValue((RexxPointerObject)callbackOpt3); } - if ( c->IsOfType(callbackName, "POINTER") ) + if ( argumentExists(6) ) { - c->RaiseException0(Rexx_Error_Unsupported_method); - return result; + if ( ! c->IsPointer(callbackOpt4) ) + { + wrongClassException(context->threadContext, 6, "Pointer", callbackOpt4); + return result; + } + xDst = (fnXDestroy)c->PointerValue((RexxPointerObject)callbackOpt2); } - else if ( c->IsDirectory(callbackName) ) + + if ( argumentExists(7) ) { - c->RaiseException0(Rexx_Error_Unsupported_method); - return result; + if ( ! c->IsPointer(userData) ) + { + wrongClassException(context->threadContext, 7, "Pointer", userData); + return result; + } + usrD = c->PointerValue((RexxPointerObject)userData); } - else - { - wrongArgValueException(c->threadContext, 3, "Pointer or Directory", callbackName); - return result; - } - // Here do set up for external collation function - // sqlite3_create_collation. Until implemented we just return misuse - return result; + rc = sqlite3_create_function_v2(pConn->db, functionName, countArgs, SQLITE_UTF8, usrD, xFunc, xStep, xFnl, xDst); + return c-... [truncated message content] |