📄 dbxodbc.c
字号:
/* Some MS database engines uses nonstandard SQL for primary keys. Instead
of allowing a simple PRIMARY KEY qualifier, they require that the use of
a primary key to be given as constraint on a column, which also involves
creating an index on that column. In theory we could rewrite the primary
key qualifier to create a fixed-name index using the constraint notation,
however Access and SQL Server go even further and create an implied unique
index for the key, making it both useless for its intended purpose
(forcing clustering of identical entries) as well as rendering the table
unusable (since it'll only allow a single value to be added). Because of
this we have to remove the PRIMARY KEY qualifier entirely.
Detecting when this is necessary is tricky, it's required for Access, and
SQL Server but not for Foxpro or (probably) any non-MS products, so we
check for a DBMS name of "Access" or "SQL Server" and remove it if we find
either. In addition if we find Access we fix up some other problems it
has as well */
static void convertQuery( DBMS_STATE_INFO *dbmsInfo, char *query,
const char *command )
{
RETCODE retCode;
SWORD bufLen;
char *keywordPtr, buffer[ 128 ];
assert( command != NULL );
strcpy( query, command );
/* If it's a CREATE TABLE command, rewrite the blob and date types to
the appropriate values for the database backend */
if( !strncmp( command, "CREATE TABLE", 12 ) )
{
char *placeholderPtr;
if( ( placeholderPtr = strstr( query, " BLOB" ) ) != NULL )
{
const int nameLen = strlen( dbmsInfo->blobName );
/* Open up a gap and replace the blob name placeholder with the
actual blob name */
memmove( placeholderPtr + 1 + nameLen, placeholderPtr + 5,
strlen( placeholderPtr + 5 ) + 1 );
memcpy( placeholderPtr + 1, dbmsInfo->blobName, nameLen );
}
if( ( placeholderPtr = strstr( query, " DATETIME" ) ) != NULL )
{
const int nameLen = strlen( dbmsInfo->dateTimeName );
/* Open up a gap and replace the date name placeholder with the
actual date name */
memmove( placeholderPtr + 1 + nameLen, placeholderPtr + 9,
strlen( placeholderPtr + 9 ) + 1 );
memcpy( placeholderPtr + 1, dbmsInfo->dateTimeName, nameLen );
}
}
/* If it's not a CREATE TABLE command with a primary key or a
SELECT/DELETE with wildcards used, there's nothing to do */
if( ( strncmp( query, "CREATE TABLE", 12 ) || \
( keywordPtr = strstr( query, " PRIMARY KEY" ) ) == NULL ) && \
( ( strncmp( query, "SELECT", 6 ) && strncmp( query, "DELETE", 6 ) ) || \
strstr( query, " LIKE " ) == NULL ) )
return;
/* It's a potential problem command, check for the presence of Access or
SQL Server */
retCode = SQLGetInfo( dbmsInfo->hDbc, SQL_DBMS_NAME, buffer,
sizeof( buffer ), &bufLen );
if( ( retCode == SQL_SUCCESS || retCode == SQL_SUCCESS_WITH_INFO ) && \
strCompare( buffer, "Access", 6 ) && \
strCompare( buffer, "SQL Server", 10 ) )
return;
if( query[ 0 ] == 'C' )
{
#if 0
/* Rewrite the PRIMARY KEY qualifier as a constraint on the column.
We use the name 'PrimaryKey' since this is what Access uses by
default */
memmove( keywordPtr + 33, keywordPtr + 12,
( strlen( keywordPtr ) - 12 ) + 1 );
memcpy( keywordPtr, "CONSTRAINT PrimaryKey PRIMARY KEY", 33 );
#else
/* Remove the PRIMARY KEY qualifier (the constraint mechanism is too
awkward to handle cleanly, see the comment at the start of this
function) */
memmove( keywordPtr, keywordPtr + 12, strlen( keywordPtr + 12 ) + 1 );
#endif /* 0 */
}
else
{
/* Unlike everything else in the known universe, Access uses * and ?
instead of the standard SQL wildcards so if we find a LIKE ... %
we rewrite the % as a * */
if( buffer[ 0 ] == 'A' && \
( keywordPtr = strstr( query, " LIKE " ) ) != NULL )
{
int i;
/* Search up to 5 characters ahead for a wildcard and replace it
with the one needed by Access if we find it */
for( i = 7; i < 11 && keywordPtr[ i ]; i++ )
if( keywordPtr[ i ] == '%' )
keywordPtr[ i ] = '*';
}
}
}
/* Get the name of the blob and date data type for this data source */
static int getDatatypeInfo( DBMS_STATE_INFO *dbmsInfo )
{
RETCODE retCode;
SDWORD length;
SWORD bufLen;
char buffer[ 8 ];
long count;
SQLAllocStmt( dbmsInfo->hDbc, &dbmsInfo->hStmt );
/* First we see whether the database supports long binary strings. Most
of the newer ones that are likely to be used do */
retCode = SQLGetTypeInfo( dbmsInfo->hStmt, SQL_LONGVARBINARY );
if( retCode == SQL_SUCCESS || retCode == SQL_SUCCESS_WITH_INFO )
{
/* Get the results of the transaction. If the database doesn't
support this, we'll get SQL_NO_DATA_FOUND (status 100) returned */
retCode = SQLFetch( dbmsInfo->hStmt );
if( retCode == SQL_SUCCESS || retCode == SQL_SUCCESS_WITH_INFO )
{
/* Get the type name and maximum possible field length. We only
check the second return code since they both apply to the same
row */
SQLGetData( dbmsInfo->hStmt, 1, SQL_C_CHAR,
dbmsInfo->blobName, 64, &length );
retCode = SQLGetData( dbmsInfo->hStmt, 3, SQL_C_LONG,
&count, sizeof( long ), &length );
if( retCode == SQL_SUCCESS || retCode == SQL_SUCCESS_WITH_INFO )
{
dbmsInfo->hasBinaryBlobs = TRUE;
dbmsInfo->blobType = SQL_LONGVARBINARY;
}
}
else
{
/* The backend doesn't support binary blobs, get the name of the
long char type for this data source */
SQLFreeStmt( dbmsInfo->hStmt, SQL_CLOSE );
retCode = SQLGetTypeInfo( dbmsInfo->hStmt, SQL_LONGVARCHAR );
if( retCode == SQL_SUCCESS || retCode == SQL_SUCCESS_WITH_INFO )
{
/* Get the results of the transaction */
retCode = SQLFetch( dbmsInfo->hStmt );
if( retCode == SQL_SUCCESS || retCode == SQL_SUCCESS_WITH_INFO )
{
/* Get the type name and maximum possible field length.
We only check the second return code since they both
apply to the same row */
SQLGetData( dbmsInfo->hStmt, 1, SQL_C_CHAR,
dbmsInfo->blobName, 64, &length );
retCode = SQLGetData( dbmsInfo->hStmt, 3, SQL_C_LONG,
&count, sizeof( long ), &length );
dbmsInfo->blobType = SQL_LONGVARCHAR;
}
}
}
}
/* If we couldn't get a blob type or the type is too short to use,
report it back as a database open failure */
if( ( retCode != SQL_SUCCESS && retCode != SQL_SUCCESS_WITH_INFO ) || \
count < 4096 )
{
if( count >= 4096 )
/* There was a problem, get more details */
getErrorInfo( dbmsInfo, SQL_ERRLVL_0, CRYPT_ERROR_OPEN );
SQLFreeStmt( dbmsInfo->hStmt, SQL_DROP );
return( CRYPT_ERROR_OPEN );
}
/* Now do the same thing for the date+time data type. This changed from
SQL_TIMESTAMP in ODBC 2.x to SQL_TYPE_TIMESTAMP in ODBC 3.x, since 3.x
will be more common we try the 3.x version first and if that fails
fall back to 2.x */
SQLFreeStmt( dbmsInfo->hStmt, SQL_CLOSE );
retCode = SQLGetTypeInfo( dbmsInfo->hStmt, SQL_TYPE_TIMESTAMP );
if( retCode != SQL_SUCCESS && retCode != SQL_SUCCESS_WITH_INFO )
retCode = SQLGetTypeInfo( dbmsInfo->hStmt, SQL_TIMESTAMP );
if( retCode == SQL_SUCCESS || retCode == SQL_SUCCESS_WITH_INFO )
{
/* Fetch the results of the transaction and get the type name */
retCode = SQLFetch( dbmsInfo->hStmt );
if( retCode == SQL_SUCCESS || retCode == SQL_SUCCESS_WITH_INFO )
retCode = SQLGetData( dbmsInfo->hStmt, 1, SQL_C_CHAR,
dbmsInfo->dateTimeName, 64, &length );
}
if( retCode != SQL_SUCCESS && retCode != SQL_SUCCESS_WITH_INFO )
{
getErrorInfo( dbmsInfo, SQL_ERRLVL_0, CRYPT_ERROR_OPEN );
SQLFreeStmt( dbmsInfo->hStmt, SQL_DROP );
return( CRYPT_ERROR_OPEN );
}
#if 0 /* Not needed since we always supply the length */
retCode = SQLGetInfo( dbmsInfo->hDbc, SQL_NEED_LONG_DATA_LEN,
buffer, sizeof( buffer ), &bufLen );
if( retCode != SQL_SUCCESS )
dbmsInfo->needLongLength = TRUE; /* Make a paranoid guess */
else
dbmsInfo->needLongLength = ( *buffer == 'Y' ) ? TRUE : FALSE;
#endif /* 0 */
/* Finally, determine the escape char being used. This is usually '\',
but it may have been changed for some reason */
retCode = SQLGetInfo( dbmsInfo->hDbc, SQL_SEARCH_PATTERN_ESCAPE,
buffer, sizeof( buffer ), &bufLen );
dbmsInfo->escapeChar = ( retCode == SQL_SUCCESS ) ? buffer[ 0 ] : '\\';
SQLFreeStmt( dbmsInfo->hStmt, SQL_DROP );
dbmsInfo->hStmt = NULL;
return( CRYPT_OK );
}
/****************************************************************************
* *
* Database Open/Close Routines *
* *
****************************************************************************/
/* Close a previously-opened ODBC connection. We have to have this before
openDatabase() since it may be called by openDatabase() if the open
process fails. This is necessary because the complex ODBC open may
require a fairly extensive cleanup afterwards */
static void closeDatabase( DBMS_STATE_INFO *dbmsInfo )
{
assert( isWritePtr( dbmsInfo, DBMS_STATE_INFO ) );
/* Commit the transaction. The default transaction mode for drivers
that support SQLSetConnectOption() is auto-commit so the
SQLTransact() call isn't strictly necessary, but we play it safe
anyway */
if( dbmsInfo->needsUpdate )
{
SQLTransact( dbmsInfo->hEnv, dbmsInfo->hDbc, SQL_COMMIT );
dbmsInfo->needsUpdate = FALSE;
}
/* Clean up */
SQLDisconnect( dbmsInfo->hDbc );
SQLFreeConnect( dbmsInfo->hDbc );
SQLFreeEnv( dbmsInfo->hEnv );
dbmsInfo->hStmt = NULL;
dbmsInfo->hDbc = NULL;
dbmsInfo->hEnv = NULL;
}
/* Open a connection to a data source using ODBC. We don't check the return
codes for many of the functions since the worst that can happen if they
fail is that performance will be somewhat suboptimal. In addition we
don't allocate statement handles at this point since these are handled in
various strange and peculiar ways by different ODBC drivers. The main
problem is that some drivers don't support mode than one hstmt per hdbc,
some support only one active hstmt (an hstmt with results pending) per
hdbc, and some support multiple active hstmt's per hdbc. For this reason
we use a strategy of allocating an hstmt, performing a transaction, and
then immediately freeing it again afterwards */
static int openDatabase( DBMS_STATE_INFO *dbmsInfo, const char *name,
const int options, int *featureFlags )
{
DBMS_NAME_INFO nameInfo;
SWORD userLen = 0, passwordLen = 0;
RETCODE retCode;
int status;
assert( isWritePtr( dbmsInfo, DBMS_STATE_INFO ) );
assert( isReadPtr( name, 2 ) );
assert( isWritePtr( featureFlags, sizeof( int ) ) );
/* Make sure that the driver is bound in */
if( hODBC == NULL_HINSTANCE )
return( CRYPT_ERROR_OPEN );
/* Parse the data source into its individual components */
status = dbmsParseName( &nameInfo, name, SQL_NTS );
if( cryptStatusError( status ) )
return( status );
/* Allocate environment and connection handles */
SQLAllocEnv( &dbmsInfo->hEnv );
SQLAllocConnect( dbmsInfo->hEnv, &dbmsInfo->hDbc );
/* Set the access mode to read-only if we can. The default is R/W, but
setting it to read-only optimises transaction management */
if( options == CRYPT_KEYOPT_READONLY )
SQLSetConnectOption( dbmsInfo->hDbc, SQL_ACCESS_MODE,
SQL_MODE_READ_ONLY );
/* Set the cursor type to forward-only (which should be the default).
Note that we're passing an SQLSetStmtOption() arg.to
SQLSetConnectOption(), which causes all stmt's allocated for this
connection to have the specified behaviour */
SQLSetConnectOption( dbmsInfo->hDbc, SQL_CURSOR_TYPE,
SQL_CURSOR_FORWARD_ONLY );
/* Turn off scanning for escape clauses in the SQL strings, which lets
the driver pass the string directly to the data source. See the
comment for the previous call about the arg.being passed */
SQLSetConnectOption( dbmsInfo->hDbc, SQL_NOSCAN, SQL_NOSCAN_ON );
/* Once everything is set up the way we want it, try to connect to a data
source and allocate a statement handle */
retCode = SQLConnect( dbmsInfo->hDbc,
nameInfo.name, ( SQLSMALLINT ) nameInfo.nameLen,
nameInfo.user, ( SQLSMALLINT ) nameInfo.userLen,
nameInfo.password, ( SQLSMALLINT ) nameInfo.passwordLen );
if( retCode != SQL_SUCCESS && retCode != SQL_SUCCESS_WITH_INFO )
{
getErrorInfo( dbmsInfo, SQL_ERRLVL_0, CRYPT_ERROR_OPEN );
SQLFreeConnect( dbmsInfo->hDbc );
SQLFreeEnv( dbmsInfo->hEnv );
return( CRYPT_ERROR_OPEN );
}
/* Get various driver and source-specific information that we may need
later on */
status = getDatatypeInfo( dbmsInfo );
if( cryptStatusError( status ) )
{
closeDatabase( dbmsInfo );
return( status );
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -