/*---------------------------------------------------------------------
$Header: /Perl/OlleDB/tableparam.cpp 17 19-07-08 22:28 Sommar $
Implements all support for table parameters.
Copyright (c) 2004-2019 Erland Sommarskog
$History: tableparam.cpp $
*
* ***************** Version 17 *****************
* User: Sommar Date: 19-07-08 Time: 22:28
* Updated in $/Perl/OlleDB
* The SQL version is now in mydata.
*
* ***************** Version 16 *****************
* User: Sommar Date: 16-07-11 Time: 22:24
* Updated in $/Perl/OlleDB
* Changed data types of ULONG for no_of_cols and no_of_defaults to avoid
* compilation warnings.
*
* ***************** Version 15 *****************
* User: Sommar Date: 12-09-23 Time: 22:52
* Updated in $/Perl/OlleDB
* Updated Copyright note.
*
* ***************** Version 14 *****************
* User: Sommar Date: 12-08-08 Time: 23:21
* Updated in $/Perl/OlleDB
* parsename now has a return value.
*
* ***************** Version 13 *****************
* User: Sommar Date: 11-08-07 Time: 23:30
* Updated in $/Perl/OlleDB
* Suppress warnings about data truncation on x64.
*
* ***************** Version 12 *****************
* User: Sommar Date: 09-07-27 Time: 12:31
* Updated in $/Perl/OlleDB
* There was a 64-bit bug when saving the table definition into the hash
* table. Changed for loop to while to remove compiler warning.
*
* ***************** Version 11 *****************
* User: Sommar Date: 09-07-26 Time: 12:45
* Updated in $/Perl/OlleDB
* Determining whether an SV is defined through my_sv_is_defined to as
* SvOK may return false, unless we first do SvGETMAGIC. This proved to be
* an issue when using table-valued parameters with threads::shared. Also
* had to moify how the hash is traversed. hv_iterinit does not returns
* the number of keys for tied hashes and the like.
*
* ***************** Version 10 *****************
* User: Sommar Date: 08-04-28 Time: 23:15
* Updated in $/Perl/OlleDB
* maxlen was incorrectly ULONG or UINT when it should have been DBLENGTH.
*
* ***************** Version 9 *****************
* User: Sommar Date: 08-03-23 Time: 23:45
* Updated in $/Perl/OlleDB
* 1) Don't bind columns with usedefault = 1.
* 2) Warn user when hash key has column with default.
* 3) Handle non-existing parameter types with a clear error message.
*
* ***************** Version 8 *****************
* User: Sommar Date: 08-03-21 Time: 17:44
* Updated in $/Perl/OlleDB
* Return was missing in the case the server or provider version was
* wrong.
*
* ***************** Version 7 *****************
* User: Sommar Date: 08-02-24 Time: 16:10
* Updated in $/Perl/OlleDB
* Need to quote name of table column.
*
* ***************** Version 6 *****************
* User: Sommar Date: 08-02-10 Time: 23:18
* Updated in $/Perl/OlleDB
* Handle column properties for UDT and XML columns.
*
* ***************** Version 5 *****************
* User: Sommar Date: 08-01-06 Time: 23:33
* Updated in $/Perl/OlleDB
* Replaced all unsafe CRT functions with their safe replacements in VC8.
* olledb_message now takes a va_list as argument, so we pass it
* parameterised strings and don't have to litter the rest of the code
* with that.
*
* ***************** Version 4 *****************
* User: Sommar Date: 08-01-06 Time: 18:56
* Updated in $/Perl/OlleDB
* All the switch(datatype) for parameters and column in TVPs are now in
* common code, and not duplicated in senddata and tableparam.
*
* ***************** Version 3 *****************
* User: Sommar Date: 08-01-05 Time: 21:26
* Updated in $/Perl/OlleDB
* Moving the creation of the session pointer broke AutoConnect. The code
* for AutoConnect is now in the connect module and be called from
* executebatch or definetablecolumn.
*
* ***************** Version 2 *****************
* User: Sommar Date: 08-01-05 Time: 20:50
* Updated in $/Perl/OlleDB
* The areas for saved pointers are now in the tableparam struct and
* created when the row buffer is created. Added support for defining that
* the default value should be used for a column.
*
* ***************** Version 1 *****************
* User: Sommar Date: 08-01-05 Time: 0:28
* Created in $/Perl/OlleDB
---------------------------------------------------------------------*/
#include "CommonInclude.h"
#include "handleattributes.h"
#include "convenience.h"
#include "datatypemap.h"
#include "init.h"
#include "utils.h"
#include "internaldata.h"
#include "connect.h"
#include "errcheck.h"
#include "datetime.h"
#include "senddata.h"
// Initialiases the internal OlleDB structure for a table variable, and
// allocates area for the column definitions. Called from enterparameter.
BOOL setup_tableparam(SV * olle_ptr,
SV * paramname,
paramdata * this_param,
ULONG no_of_cols,
SV * tabletypename)
{
tableparam * tabledef;
internaldata * mydata = get_internaldata(olle_ptr);
// Check that table parameter are supported at all.
if (mydata->provider < provider_sqlncli10 ||
mydata->majorsqlversion < 10) {
olledb_message(olle_ptr, -1, 1, 16,
L"To use table parameters, you need SQL 2008 and SQL Server Native Client 10 or later.");
return FALSE;
}
// Check that maxlen is legal.
if (no_of_cols < 1 || no_of_cols > 1024) {
olle_croak(olle_ptr, "Illegal number of columns (%d) specified for table-valued parameter",
no_of_cols);
}
// Check that we have name for the type.
if (! my_sv_is_defined(tabletypename)) {
olle_croak(olle_ptr, "Name of type missing for table-valued parameter");
}
// Allocate the table parameter itself and initiate the area.
New(902, tabledef, 1, tableparam);
memset(tabledef, 0, sizeof(tableparam));
tabledef->tabletypename = SV_to_BSTR(tabletypename);
tabledef->no_of_cols = tabledef->cols_undefined = no_of_cols;
tabledef->no_of_usedefault = 0;
tabledef->colnamemap = newHV();
New(902, tabledef->columns, no_of_cols, DBCOLUMNDESC);
memset(tabledef->columns, 0, no_of_cols * sizeof(DBCOLUMNDESC));
New(902, tabledef->colbindings, no_of_cols, DBBINDING);
memset(tabledef->colbindings, 0, no_of_cols * sizeof(DBBINDING));
New(902, tabledef->colbindstatus, no_of_cols, DBBINDSTATUS);
memset(tabledef->colbindstatus, 0, no_of_cols * sizeof(DBBINDSTATUS));
New(902, tabledef->usedefault, no_of_cols, BOOL);
memset(tabledef->usedefault, FALSE, no_of_cols * sizeof(BOOL));
New(902, tabledef->bindix, no_of_cols, UINT);
memset(tabledef->bindix, ~0, no_of_cols * sizeof(UINT));
// Set the table definition as the value for the current parameter.
this_param->value.table = tabledef;
// If the table parameter has a name, save it into a hash so that the
// caller can refer to the parameter by name later.
if (this_param->param_info.pwszName != NULL) {
if (mydata->tableparams == NULL) {
mydata->tableparams = newHV();
}
SV * sv_tabledef = newSViv((IV) tabledef);
hv_store_ent(mydata->tableparams, paramname, sv_tabledef, 0);
}
return TRUE;
}
// add_column_props is called to handle XML and UDT columns to define
// the type name or schema collection.
static void add_column_props (SV * olle_ptr,
DBCOLUMNDESC * coldesc,
SV * typeinfo)
{
DBPROPSET * propset;
int propscnt = 0;
// Drop out if there is no typeinfo.
if (! my_sv_is_defined(typeinfo)) {
return;
}
SV * server = newSV(sv_len(typeinfo));
SV * database = newSV(sv_len(typeinfo));
SV * schema = newSV(sv_len(typeinfo));
SV * object = newSV(sv_len(typeinfo));
int ix = 0;
DBPROPID dbpropid;
DBPROPID schemapropid;
DBPROPID objectpropid;
// First extract components from typeinfo.
if (! parsename(olle_ptr, typeinfo, 0, server, database, schema, object)) {
return;
}
// If there was a server, cry foul.
if (sv_len(server) > 0) {
BSTR typeinfo_str = SV_to_BSTR(typeinfo);
olledb_message(olle_ptr, -1, -1, 16,
L"Type name/XML schema '%s' includes a server compenent.\n",
typeinfo_str);
SysFreeString(typeinfo_str);
SvREFCNT_dec(server);
SvREFCNT_dec(database);
SvREFCNT_dec(schema);
SvREFCNT_dec(object);
return;
}
// Find out how many components we have.
if (sv_len(database) > 0) propscnt++;
if (sv_len(schema) > 0) propscnt++;
if (sv_len(object) > 0) propscnt++;
// If there was nothing, just drop out.
if (propscnt == 0)
return;
// Set up property ids
switch (coldesc->wType) {
case DBTYPE_UDT :
dbpropid = SSPROP_COL_UDT_CATALOGNAME;
schemapropid = SSPROP_COL_UDT_SCHEMANAME;
objectpropid = SSPROP_COL_UDT_NAME;
break;
case DBTYPE_XML :
dbpropid = SSPROP_COL_XML_SCHEMACOLLECTION_CATALOGNAME;
schemapropid = SSPROP_COL_XML_SCHEMACOLLECTION_SCHEMANAME;
objectpropid = SSPROP_COL_XML_SCHEMACOLLECTIONNAME;
break;
default :
olle_croak(olle_ptr,
"Internal error: Unexpected value %d for data type in add_column_props",
coldesc->wType);
}
// Set up the property set.
New(902, propset, 1, DBPROPSET);
propset->guidPropertySet = DBPROPSET_SQLSERVERCOLUMN;
propset->cProperties = propscnt;
New(902, propset->rgProperties, propscnt, DBPROP);
// Store database if any.
if (sv_len(database) > 0) {
propset->rgProperties[ix].dwPropertyID = dbpropid;
propset->rgProperties[ix].colid = DB_NULLID;
propset->rgProperties[ix].dwOptions = DBPROPOPTIONS_REQUIRED;
VariantInit(&(propset->rgProperties[ix].vValue));
propset->rgProperties[ix].vValue.vt = VT_BSTR;
propset->rgProperties[ix].vValue.bstrVal = SV_to_BSTR(database);
ix++;
}
// And schema if any.
if (sv_len(schema) > 0) {
propset->rgProperties[ix].dwPropertyID = schemapropid;
propset->rgProperties[ix].colid = DB_NULLID;
propset->rgProperties[ix].dwOptions = DBPROPOPTIONS_REQUIRED;
VariantInit(&(propset->rgProperties[ix].vValue));
propset->rgProperties[ix].vValue.vt = VT_BSTR;
propset->rgProperties[ix].vValue.bstrVal = SV_to_BSTR(schema);
ix++;
}
// And the type name.
if (sv_len(object) > 0) {
propset->rgProperties[ix].dwPropertyID = objectpropid;
propset->rgProperties[ix].colid = DB_NULLID;
propset->rgProperties[ix].dwOptions = DBPROPOPTIONS_REQUIRED;
VariantInit(&(propset->rgProperties[ix].vValue));
propset->rgProperties[ix].vValue.vt = VT_BSTR;
propset->rgProperties[ix].vValue.bstrVal = SV_to_BSTR(object);
}
// And save the property set.
coldesc->rgPropertySets = propset;
coldesc->cPropertySets = 1;
// We must clean up our SVs to not leak memory.
SvREFCNT_dec(server);
SvREFCNT_dec(database);
SvREFCNT_dec(schema);
SvREFCNT_dec(object);
}
//------------------------------------------------------------------------
// definetablecolumn, exposed in the mid-level interface.
int definetablecolumn(SV * olle_ptr,
SV * tblname,
SV * colname,
SV * sv_nameoftype,
SV * sv_maxlen,
SV * sv_precision,
SV * sv_scale,
SV * usedefault,
SV * typeinfo)
{
internaldata * mydata = get_internaldata(olle_ptr);
tableparam * tbldef;
char * nameoftype;
DBLENGTH maxlen;
int colno;
int colix;
int bindix;
DBTYPE typeind;
DBCOLUMNDESC * coldesc;
DBPARAMBINDINFO param_info;
DBBINDING * binding;
// Check that we're in the state where we're accepting parameters at all.
if (mydata->pending_cmd == NULL) {
olle_croak(olle_ptr, "Cannot call definetablecolumn now. There is no pending command. Call initbatch first");
}
if (mydata->cmdtext_ptr != NULL) {
olle_croak(olle_ptr, "Cannot call definetablecolumn now. There are unprocessed result sets. Call cancelbatch first");
}
// See if we have a table parameter to work with. The caller can specify
// a name, or undef to work the the most recently added parameter.
if (my_sv_is_defined(tblname)) {
HE * he = hv_fetch_ent(mydata->tableparams, tblname, 0, 0);
if (he == NULL) {
olle_croak(olle_ptr, "Attempt to define column for parameter %s, but this is not a table-valued parameter",
SvPV_nolen(tblname));
}
tbldef = (tableparam *) SvIV(HeVAL(he));
}
else if (mydata->paramlast && mydata->paramlast->value.table != NULL) {
tbldef = mydata->paramlast->value.table;
}
else {
olle_croak(olle_ptr, "Cannot define table column without a parameter name now. Most recently entered parameter is not a table");
}
// Check that there are still colunms left to define.
if (tbldef->cols_undefined == 0) {
olle_croak(olle_ptr, "All columns have alredy been defined for table-valued parameter");
}
// Check we did get a column name
if (! my_sv_is_defined(colname)) {
olle_croak(olle_ptr, "No column name specified for column definition");
}
// And a type name.
if (! my_sv_is_defined(sv_nameoftype)) {
olle_croak(olle_ptr, "You must pass a legal type name to definetablecolumn. Cannot pass undef");
}
nameoftype = SvPV_nolen(sv_nameoftype);
// Translate the type name to a type indicator. However, we will
// pass (var)char as WSTR to support UTF-8.
typeind = lookup_type_map(nameoftype);
// It must be a legal type.
if (typeind == DBTYPE_EMPTY || typeind == DBTYPE_TABLE) {
olledb_message(olle_ptr, -1, 1, 16,
L"Illegal data type '%S' for column %d in table type '%s'.",
nameoftype, tbldef->no_of_cols - tbldef->cols_undefined + 1,
tbldef->tabletypename);
return FALSE;
}
// Get maxlen.
if (my_sv_is_defined(sv_maxlen)) {
maxlen = SvUV(sv_maxlen);
}
else {
maxlen = 0;
}
// So which column in order is this? For the bindings, we need to reduce
// the index for the number of default columns, because we don't bind these.
colno = tbldef->no_of_cols - tbldef->cols_undefined + 1;
colix = colno - 1;
bindix = colix - tbldef->no_of_usedefault;
// Is this a default column?
if (SvTRUE(usedefault)) {
tbldef->usedefault[colix] = TRUE;
tbldef->no_of_usedefault++;
}
else {
// If not, save the index for the binding.
tbldef->bindix[colix] = bindix;
}
// Store column number in the column-name map.
hv_store_ent(tbldef->colnamemap, colname, newSViv(colno), 0);
// Get some pointers for short notiation. If we are to use a default for
// for this column, the binding information will be over-written by
// the next column (if there is one), but that's alright, because we
// will not use it, only the column description. There will be some
// holes in the row buffer, but who cares?
coldesc = &(tbldef->columns[colix]);
binding = &(tbldef->colbindings[bindix]);
// Fill up the column description for CreateTable.
coldesc->dbcid.eKind = DBKIND_NAME;
coldesc->dbcid.uName.pwszName = SV_to_BSTR(colname);
quotename(coldesc->dbcid.uName.pwszName);
coldesc->wType = typeind;
// Column bindings, all that is the same for all types.
binding->iOrdinal = colno;
binding->dwMemOwner = DBMEMOWNER_CLIENTOWNED;
binding->eParamIO = DBPARAMIO_NOTPARAM;
binding->wType = typeind; // BYREF may be added later.
binding->dwPart = DBPART_VALUE | DBPART_STATUS;
binding->obStatus = tbldef->size_row_buffer;
tbldef->size_row_buffer += sizeof(DBSTATUS);
binding->obValue = tbldef->size_row_buffer;
// For the type-specfic stuff we use complete_binding. This works with a
// DBPARAMBINDINFO struct, and not a coldesc.
memset(¶m_info, 0, sizeof(DBPARAMBINDINFO));
complete_binding(typeind, nameoftype, maxlen, sv_precision, sv_scale,
tbldef->size_row_buffer, binding, ¶m_info);
coldesc->ulColumnSize = param_info.ulParamSize;
coldesc->pwszTypeName = param_info.pwszDataSourceType;
coldesc->bPrecision = param_info.bPrecision;
coldesc->bScale = param_info.bScale;
if (coldesc->wType == DBTYPE_UDT || coldesc->wType == DBTYPE_XML) {
add_column_props(olle_ptr, coldesc, typeinfo);
}
// And if we did not fill in this one, fill it now.
if (coldesc->pwszTypeName == NULL) {
coldesc->pwszTypeName = SV_to_BSTR(sv_nameoftype);
}
// Decremenet number of undefined columns, and leave if there are more
// to defined.
if (--tbldef->cols_undefined > 0) {
return TRUE;
}
// All columns are now defined. Go on and create the table and the rowset.
// We need a session to be able to this.
if (!setup_session(olle_ptr)) {
return FALSE;
}
// First we need a table ID.
DBID TableID;
HRESULT ret;
TableID.uGuid.guid = CLSID_ROWSET_TVP;
TableID.eKind = DBKIND_GUID_NAME;
TableID.uName.pwszName = tbldef->tabletypename;
// Get interface for ITableDefinitionWithConstraints.
ret = mydata->session_ptr->QueryInterface(
IID_ITableDefinitionWithConstraints, (void **) &(tbldef->tabledef_ptr));
check_for_errors(olle_ptr, "session_ptr->QueryInterface to create ITableDefinitionWithConstraints object", ret);
// Now we can create the table.
ret = tbldef->tabledef_ptr->CreateTableWithConstraints(
NULL, &TableID, tbldef->no_of_cols, tbldef->columns, 0, NULL,
IID_IRowsetChange, 0, NULL, NULL, (IUnknown **) &(tbldef->rowset_ptr));
check_for_errors(olle_ptr, "tabledef_ptr->CreateTableWithConstraints", ret);
// Ramp up for an accessor. The accessor should only include the
// colunms with usedefault = 0.
ret = tbldef->rowset_ptr->QueryInterface(IID_IAccessor,
(void **) &(tbldef->accessor_ptr));
check_for_errors(olle_ptr, "tbldef->rowset_ptr->QueryInterface to create row accessor", ret);
ret = tbldef->accessor_ptr->CreateAccessor(
DBACCESSOR_ROWDATA,
tbldef->no_of_cols - tbldef->no_of_usedefault,
tbldef->colbindings, tbldef->size_row_buffer,
&(tbldef->rowaccessor), tbldef->colbindstatus);
check_for_errors(olle_ptr, "tbldef->accessor->CreateAccessor to create rowacessor", ret);
// If there are any columns for which we are to pass default, set up a
// parameter property for thisĀ for later use in executbatch.
if (tbldef->no_of_usedefault > 0) {
SAFEARRAYBOUND bound = {tbldef->no_of_usedefault, 0};
SAFEARRAY * safearr = SafeArrayCreate(VT_UI2, 1, &bound);
long arr_ix = 0;
for (ULONG ix = 0; ix < tbldef->no_of_cols; ix++) {
if (tbldef->usedefault[ix]) {
ULONG colno = ix + 1;
ret = SafeArrayPutElement(safearr, &arr_ix, &colno);
check_for_errors(olle_ptr, "SafeArrayPutElement", ret);
arr_ix++;
}
}
tbldef->defcolprop.dwPropertyID = SSPROP_PARAM_TABLE_DEFAULT_COLUMNS;
tbldef->defcolprop.colid = DB_NULLID;
tbldef->defcolprop.dwOptions = DBPROPOPTIONS_REQUIRED;
VariantInit(&(tbldef->defcolprop.vValue));
tbldef->defcolprop.vValue.vt = VT_UI2 | VT_ARRAY;
tbldef->defcolprop.vValue.parray = safearr;
}
// And allocate the row buffer.
New(902, tbldef->row_buffer, tbldef->size_row_buffer, BYTE);
// And the buffers for saved pointers.
New(902, tbldef->save_ptrs, tbldef->no_of_cols, void *);
New(902, tbldef->save_bstrs, tbldef->no_of_cols, BSTR);
return TRUE;
}
// Internal routine that is called from inserttableparam to write one
// column value to the buffer.
static BOOL value_to_rowbuffer(SV * olle_ptr,
SV * sv_value,
DBCOLUMNDESC * coldesc,
DBBINDING * binding,
BYTE * row_buffer,
void * &save_ptr,
BSTR &save_bstr)
{
internaldata * mydata = get_internaldata(olle_ptr);
BOOL value_OK = TRUE;
DBLENGTH value_len;
DBSTATUS * status_ptr = (DBSTATUS *) &(row_buffer[binding->obStatus]);
DBLENGTH * length_ptr = (DBLENGTH *) &(row_buffer[binding->obLength]);
valueunion colvalue;
// Check for NULL for an easy way out.
if (! my_sv_is_defined(sv_value)) {
* status_ptr = DBSTATUS_S_ISNULL;
return TRUE;
}
* status_ptr = DBSTATUS_S_OK;
// Convert the value from Perl to SQL Server. Method depends on data type.
value_OK = perl_to_sqlvalue(olle_ptr, sv_value, coldesc->wType,
coldesc->dbcid.uName.pwszName,
coldesc->pwszTypeName, binding,
coldesc->ulColumnSize,
colvalue, value_len, save_ptr, save_bstr);
// And write it to the row buffer, if all is OK.
if (value_OK) {
write_to_databuffer(olle_ptr, row_buffer, binding->obValue, coldesc->wType,
colvalue);
// Write the length if needed.
if (binding->dwPart & DBPART_LENGTH) {
* length_ptr = value_len;
}
}
return value_OK;
}
// Implements inserttableparam in the mid-level interface.
int inserttableparam(SV * olle_ptr,
SV * tblname,
SV * inputref)
{
internaldata * mydata = get_internaldata(olle_ptr);
tableparam * tbldef;
HV * input_hv = NULL;
AV * input_av = NULL;
SV ** svp;
SV * sv_value;
BOOL value_OK = TRUE;
UINT bindix;
// Check that we're in the state where we're accepting parameters at all.
if (mydata->pending_cmd == NULL) {
olle_croak(olle_ptr, "Cannot call inserttableparam now. There is no pending command");
}
if (mydata->cmdtext_ptr != NULL) {
olle_croak(olle_ptr, "Cannot call inserttableparam now. There are unprocessed result sets. Call cancelbatch first");
}
// See if we have a table parameter to work with. The caller can specify
// a name, or undef to work the the most recently added parameter.
if (my_sv_is_defined(tblname)) {
HE * he = hv_fetch_ent(mydata->tableparams, tblname, 0, 0);
if (he == NULL) {
olle_croak(olle_ptr, "Cannot call inserttableparam for parameter %s; this is not a table-valued parameter",
SvPV_nolen(tblname));
}
tbldef = (tableparam *) SvIV(HeVAL(he));
}
else if (mydata->paramlast && mydata->paramlast->value.table != NULL) {
tbldef = mydata->paramlast->value.table;
}
else {
olle_croak(olle_ptr, "Cannot call inserttableparam without a parameter name now. Most recently entered parameter is not a table");
}
// Check that all columns have been defined
if (tbldef->rowset_ptr == NULL) {
olle_croak(olle_ptr, "Cannot call inserttableparam now. All columns have not been defined for table-valued parameter");
}
// Determine if the input area is a hash or an array.
if (my_sv_is_defined(inputref) && SvROK(inputref)) {
if (strncmp(SvPV_nolen(inputref), "HASH(", 5) == 0) {
input_hv = (HV *) SvRV(inputref);
}
else if (strncmp(SvPV_nolen(inputref), "ARRAY(", 6) == 0) {
input_av = (AV *) SvRV(inputref);
}
}
// Initiate the row buffer. Set all columns to be NULL, in case caller
// did not supply all.
memset(tbldef->row_buffer, 0, tbldef->size_row_buffer);
for (ULONG i = 0; i < tbldef->no_of_cols; i++) {
DBBYTEOFFSET offset = tbldef->colbindings[i].obStatus;
DBSTATUS * status = (DBSTATUS *) (&tbldef->row_buffer[offset]);
* status = DBSTATUS_S_ISNULL;
}
// Clear the buffers with saved pointers.
memset(tbldef->save_ptrs, 0, tbldef->no_of_cols * sizeof(void *));
memset(tbldef->save_bstrs, 0, tbldef->no_of_cols * sizeof(BSTR));
// Now we handle the input area.
if (input_av != NULL) {
ULONG arraylen = av_len(input_av) + 1;
if ((arraylen > tbldef->no_of_cols) & PL_dowarn) {
olledb_message(olle_ptr, -1, 1, 10,
L"Warning: input array for inserttableparam has %d elements, but there are only %d columns in the table definition.",
arraylen, tbldef->no_of_cols);
}
for (ULONG colix = 0;
colix < (arraylen <= tbldef->no_of_cols ?
arraylen : tbldef->no_of_cols); colix++) {
if (tbldef->usedefault[colix]) {
continue;
}
svp = av_fetch(input_av, colix, 0);
if (svp == NULL) continue;
sv_value = *svp;
bindix = tbldef->bindix[colix];
value_OK &= value_to_rowbuffer(
olle_ptr, sv_value, &(tbldef->columns[colix]),
&(tbldef->colbindings[bindix]), tbldef->row_buffer,
tbldef->save_ptrs[colix], tbldef->save_bstrs[colix]);
}
}
else if (input_hv != NULL) {
// Iterate over all keys in the hash.
hv_iterinit(input_hv);
while (HE * he = hv_iternext(input_hv)) {
char * key;
I32 keylen;
SV ** svp;
SV * sv_colno = NULL;
int colix;
SV * sv_value = NULL;
// Get next key in iteration.
key = hv_iterkey(he, &keylen);
// Lookup this key in our colnamemap.
svp = hv_fetch(tbldef->colnamemap, key, keylen, 0);
if (svp != NULL) {
sv_colno = * svp;
}
// If we don't know this key value, skip and issue a warning if
// Perl warnings are enabled.
if (! my_sv_is_defined(sv_colno)) {
if (PL_dowarn) {
olledb_message(olle_ptr, -1, 1, 10,
"Warning: input hash to inserttableparam includes key '%s', but no such column has been defined for this table parameter.",
key);
}
continue;
}
// Get the column index.
colix = (int) SvIV(sv_colno) - 1;
// If this is a column with a default, we issue a warning and move on
// to the next column.
if (tbldef->usedefault[colix]) {
if (PL_dowarn) {
olledb_message(olle_ptr, -1, 1, 10,
"Warning: input hash to inserttableparam includes key '%s', but this column has been defined with usedefault=1 and the value is ignored.",
key);
}
continue;
}
// Get the index in the bindings array. This may be different from
// colix if there are columns with defaults.
bindix = tbldef->bindix[colix];
// And get the value in the input hash.
sv_value = hv_iterval(input_hv, he);
// And write the value to the buffer.
value_OK &= value_to_rowbuffer(
olle_ptr, sv_value, &(tbldef->columns[colix]),
&(tbldef->colbindings[bindix]), tbldef->row_buffer,
tbldef->save_ptrs[colix], tbldef->save_bstrs[colix]);
}
}
else {
olle_croak(olle_ptr, "Incorrect value for parameter $inputref to inserttableparam. This should be a hash or an array reference");
}
// Propagate value_OK out to mydata, so that we know that the batch should
// not be executed if there was an error.
mydata->all_params_OK &= value_OK;
// Write the row to the table if all values were OK. (And all previous
// values were to. No use writing if we are not to execute the batch.)
if (mydata->all_params_OK) {
HRESULT ret;
ret = tbldef->rowset_ptr->InsertRow(DB_NULL_HCHAPTER, tbldef->rowaccessor,
tbldef->row_buffer, NULL);
check_for_errors(olle_ptr, "tableparam->rowset_ptr->insert_row", ret);
}
// Release all saved pointers.
for (ULONG ix = 0; ix < tbldef->no_of_cols; ix++) {
Safefree(tbldef->save_ptrs[ix]);
SysFreeString(tbldef->save_bstrs[ix]);
}
return value_OK;
}