/*---------------------------------------------------------------------
$Header: /Perl/OlleDB/connect.cpp 8 12-09-23 22:52 Sommar $
Implements the connection routines on Win32::SqlServer.
Copyright (c) 2004-2012 Erland Sommarskog
$History: connect.cpp $
*
* ***************** Version 8 *****************
* User: Sommar Date: 12-09-23 Time: 22:52
* Updated in $/Perl/OlleDB
* Updated Copyright note.
*
* ***************** Version 7 *****************
* User: Sommar Date: 12-08-15 Time: 21:28
* Updated in $/Perl/OlleDB
* New model for checking the number of properties depending on the
* provider. New login property ApplicationIntent that requires
* validation.
*
* ***************** Version 6 *****************
* User: Sommar Date: 12-07-20 Time: 23:49
* Updated in $/Perl/OlleDB
* Add support for SQLNCLI11.
*
* ***************** Version 5 *****************
* User: Sommar Date: 11-08-07 Time: 23:17
* Updated in $/Perl/OlleDB
* Suppress warning about data truncation on x64.
*
* ***************** Version 4 *****************
* User: Sommar Date: 09-07-26 Time: 12:44
* 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.
*
* ***************** Version 3 *****************
* 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 2 *****************
* 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 1 *****************
* User: Sommar Date: 07-12-24 Time: 21:40
* Created in $/Perl/OlleDB
---------------------------------------------------------------------*/
#include "CommonInclude.h"
#include "handleattributes.h"
#include "convenience.h"
#include "init.h"
#include "internaldata.h"
#include "errcheck.h"
#include "connect.h"
// Connect, called from $X->Connect() and $X->executebatch for autoconnect.
BOOL do_connect (SV * olle_ptr,
BOOL isautoconnect)
{
internaldata * mydata = get_internaldata(olle_ptr);
HRESULT ret = S_OK;
IDBProperties * property_ptr;
CLSID * clsid;
char * provider_name;
switch (mydata->provider) {
// At this point provider_default should never appear.
case provider_sqlncli11 :
clsid = &clsid_sqlncli11;
provider_name = "SQLNCLI11";
break;
case provider_sqlncli10 :
clsid = &clsid_sqlncli10;
provider_name = "SQLNCLI10";
break;
case provider_sqlncli :
clsid = &clsid_sqlncli;
provider_name = "SQLNCLI";
break;
case provider_sqloledb :
clsid = &clsid_sqloledb;
provider_name = "SQLOLEDB";
break;
default :
croak ("Internal error: Illegal value %d for the provider enum",
mydata->provider);
}
if (FAILED(ret)) {
croak("Chosen provider '%s' does not appear to be installed on this machine",
provider_name);
}
ret = data_init_ptr->CreateDBInstance(*clsid,
NULL, CLSCTX_INPROC_SERVER,
NULL, IID_IDBInitialize,
reinterpret_cast<IUnknown **> (&mydata->init_ptr));
if (FAILED(ret)) {
croak("Internal error: IDataInitliaze->CreateDBInstance failed: %08X", ret);
}
// We need a property object.
ret = mydata->init_ptr->QueryInterface(IID_IDBProperties,
(void **) &property_ptr);
if (FAILED(ret)) {
croak("Internal error: init_ptr->QueryInterface to create Property object failed with hresult %x", ret);
}
// Set the number of SSPROPS depending on the provider.
mydata->init_propsets[ssinit_props].cProperties =
no_of_ssprops(mydata->provider);
// Set all dwStatus to -1 for the first two propsets, this helps to
// detect that some properties were not set, because we're in for an
// old version of SQLOLEDB.
for (int p = oleinit_props; p <= ssinit_props; p++) {
for (UINT i = init_propset_info[p].start;
i < mydata->init_propsets[p].cProperties +
init_propset_info[p].start; i++) {
mydata->init_properties[i].dwStatus = -1;
}
}
ret = property_ptr->SetProperties(2, mydata->init_propsets);
if (FAILED(ret)) {
dump_properties(mydata->init_properties, OptPropsDebug(olle_ptr));
croak("Internal error: property_ptr->SetProperties for initialization props failed with hresult %x", ret);
}
// This is the place where we actually log in to SQL Server. We might
// be reusing a connection from a pool.
ret = mydata->init_ptr->Initialize();
// If success, continue with creating data-source object.
if (SUCCEEDED(ret)) {
// Set properties for the data source.
ret = property_ptr->SetProperties(1, &mydata->init_propsets[datasrc_props]);
check_for_errors(NULL, "property_ptr->SetProperties for data-source props",
ret);
// Get a data source object.
ret = mydata->init_ptr->QueryInterface(IID_IDBCreateSession,
(void **) &(mydata->datasrc_ptr));
check_for_errors(olle_ptr, "init_ptr->QueryInterface for data source",
ret);
mydata->isautoconnected = isautoconnect;
}
else {
dump_properties(mydata->init_properties, OptPropsDebug(olle_ptr));
check_for_errors(olle_ptr, "init_ptr->Initialize", ret);
}
// And release the property pointers.
property_ptr->Release();
return SUCCEEDED(ret);
}
// This is $X->setloginproperty.
void setloginproperty(SV * olle_ptr,
char * prop_name,
SV * prop_value)
{
internaldata * mydata = get_internaldata(olle_ptr);
int ix = 0;
// If we are connected, and warnings are enabled, emit a warning.
if (mydata->datasrc_ptr != NULL) {
olle_croak(olle_ptr, "You cannot set login properties while connected");
}
// Check we got a proper prop_name.
if (prop_name == NULL) {
croak("Property name must not be NULL.");
}
// Look up property name in the global array.
while (gbl_init_props[ix].propset_enum != not_in_use &&
_stricmp(prop_name, gbl_init_props[ix].name) != 0) {
ix++;
}
if (gbl_init_props[ix].propset_enum == not_in_use) {
croak("Unknown property '%s' passed to setloginproperty", prop_name);
}
// Some properties affects others.
if (gbl_init_props[ix].propset_enum == oleinit_props &&
gbl_init_props[ix].property_id == DBPROP_AUTH_USERID) {
// If userid is set, we clear Integrated security.
setloginproperty(olle_ptr, "IntegratedSecurity", &PL_sv_undef);
}
else if (gbl_init_props[ix].propset_enum == oleinit_props &&
gbl_init_props[ix].property_id == DBPROP_INIT_PROVIDERSTRING) {
// In this case, all other properties should be flushed.
for (int j = 0; gbl_init_props[j].propset_enum != datasrc_props; j++) {
VariantClear(&mydata->init_properties[j].vValue);
}
}
// If the server changes, the SQL_version attribute is no longer valid.
if (gbl_init_props[ix].propset_enum == oleinit_props &&
(gbl_init_props[ix].property_id == DBPROP_INIT_PROVIDERSTRING ||
gbl_init_props[ix].property_id == DBPROP_INIT_DATASOURCE ||
gbl_init_props[ix].property_id == SSPROP_INIT_NETWORKADDRESS)) {
drop_SQLversion(olle_ptr);
}
// The property ApplicationIntent requires validation.
if (gbl_init_props[ix].propset_enum == ssinit_props &&
gbl_init_props[ix].property_id == SSPROP_INIT_APPLICATIONINTENT) {
char * appintent = SvPV_nolen(prop_value);
if (_stricmp(appintent, "readwrite") != 0 &&
_stricmp(appintent, "readonly") != 0) {
croak("Illegal value '%s' passed for the '%s' property",
appintent, prop_name);
}
}
// First clear the current value and set property to VT_EMPTY.
VariantClear(&mydata->init_properties[ix].vValue);
// Then set the value appropriately
if (my_sv_is_defined(prop_value)) {
mydata->init_properties[ix].vValue.vt = gbl_init_props[ix].datatype;
// First handle any specials. Currently there are two.
if (gbl_init_props[ix].propset_enum == oleinit_props &&
gbl_init_props[ix].property_id == DBPROP_INIT_OLEDBSERVICES) {
// For OLE DB Services, we are only using connection pooling.
mydata->init_properties[ix].vValue.lVal = (SvTRUE(prop_value)
? DBPROPVAL_OS_RESOURCEPOOLING : DBPROPVAL_OS_DISABLEALL);
}
else if (gbl_init_props[ix].propset_enum == oleinit_props &&
gbl_init_props[ix].property_id == DBPROP_AUTH_INTEGRATED) {
// For integrated security, handle numeric values gently.
if (SvIOK(prop_value)) {
if (SvIV(prop_value) != 0) {
mydata->init_properties[ix].vValue.bstrVal =
SysAllocString(L"SSPI");
}
else {
mydata->init_properties[ix].vValue.vt = VT_EMPTY;
}
}
else {
mydata->init_properties[ix].vValue.bstrVal = SV_to_BSTR(prop_value);
}
}
else {
switch(gbl_init_props[ix].datatype) {
case VT_BOOL :
mydata->init_properties[ix].vValue.boolVal =
(SvTRUE(prop_value) ? VARIANT_TRUE : VARIANT_FALSE);
break;
case VT_I2 :
mydata->init_properties[ix].vValue.iVal = (SHORT) SvIV(prop_value);
break;
case VT_UI2 :
mydata->init_properties[ix].vValue.uiVal = (USHORT) SvIV(prop_value);
break;
case VT_I4 :
mydata->init_properties[ix].vValue.lVal = (LONG) SvIV(prop_value);
break;
case VT_BSTR :
mydata->init_properties[ix].vValue.bstrVal = SV_to_BSTR(prop_value);
break;
default :
croak ("Internal error: Unexpected datatype %d when setting property '%s'",
gbl_init_props[ix].datatype, prop_name);
}
}
}
}
// Creates the datastc and session objects if needed.
BOOL setup_session(SV * olle_ptr)
{
internaldata * mydata = get_internaldata(olle_ptr);
HRESULT ret;
if (mydata->datasrc_ptr == NULL) {
if (OptAutoConnect(olle_ptr)) {
if (! do_connect(olle_ptr, TRUE)) {
return FALSE;
}
}
else {
olle_croak(olle_ptr, "Not connected to SQL Server, nor is AutoConnect set. Cannot execute batch");
}
}
if (mydata->session_ptr == NULL) {
ret = mydata->datasrc_ptr->CreateSession(NULL, IID_IDBCreateCommand,
(IUnknown **) &(mydata->session_ptr));
check_for_errors(olle_ptr, "datasrc_ptr->CreateSession for session object", ret);
}
return TRUE;
}
void disconnect(SV * olle_ptr)
{
internaldata * mydata = get_internaldata(olle_ptr);
free_connection_data(mydata);
}