Home | History | Annotate | Line # | Download | only in back-sql
      1 /*	$NetBSD: sql-wrap.c,v 1.4 2025/09/05 21:16:31 christos Exp $	*/
      2 
      3 /* $OpenLDAP$ */
      4 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
      5  *
      6  * Copyright 1999-2024 The OpenLDAP Foundation.
      7  * Portions Copyright 1999 Dmitry Kovalev.
      8  * Portions Copyright 2002 Pierangelo Masarati.
      9  * Portions Copyright 2004 Mark Adamson.
     10  * All rights reserved.
     11  *
     12  * Redistribution and use in source and binary forms, with or without
     13  * modification, are permitted only as authorized by the OpenLDAP
     14  * Public License.
     15  *
     16  * A copy of this license is available in the file LICENSE in the
     17  * top-level directory of the distribution or, alternatively, at
     18  * <http://www.OpenLDAP.org/license.html>.
     19  */
     20 /* ACKNOWLEDGEMENTS:
     21  * This work was initially developed by Dmitry Kovalev for inclusion
     22  * by OpenLDAP Software.  Additional significant contributors include
     23  * Pierangelo Masarati and Mark Adamson.
     24  */
     25 
     26 #include <sys/cdefs.h>
     27 __RCSID("$NetBSD: sql-wrap.c,v 1.4 2025/09/05 21:16:31 christos Exp $");
     28 
     29 #include "portable.h"
     30 
     31 #include <stdio.h>
     32 #include "ac/string.h"
     33 #include <sys/types.h>
     34 
     35 #include "slap.h"
     36 #include "proto-sql.h"
     37 
     38 #define MAX_ATTR_LEN 16384
     39 
     40 void
     41 backsql_PrintErrors( SQLHENV henv, SQLHDBC hdbc, SQLHSTMT sth, int rc )
     42 {
     43 	SQLCHAR	msg[SQL_MAX_MESSAGE_LENGTH];		/* msg. buffer    */
     44 	SQLCHAR	state[SQL_SQLSTATE_SIZE];		/* statement buf. */
     45 	SDWORD	iSqlCode;				/* return code    */
     46 	SWORD	len = SQL_MAX_MESSAGE_LENGTH - 1;	/* return length  */
     47 
     48 	Debug( LDAP_DEBUG_TRACE, "Return code: %d\n", rc );
     49 
     50 	for ( ; rc = SQLError( henv, hdbc, sth, state, &iSqlCode, msg,
     51 		SQL_MAX_MESSAGE_LENGTH - 1, &len ), BACKSQL_SUCCESS( rc ); )
     52 	{
     53 		Debug( LDAP_DEBUG_TRACE,
     54 			"   nativeErrCode=%d SQLengineState=%s msg=\"%s\"\n",
     55 			(int)iSqlCode, state, msg );
     56 	}
     57 }
     58 
     59 RETCODE
     60 backsql_Prepare( SQLHDBC dbh, SQLHSTMT *sth, const char *query, int timeout )
     61 {
     62 	RETCODE		rc;
     63 
     64 	rc = SQLAllocStmt( dbh, sth );
     65 	if ( rc != SQL_SUCCESS ) {
     66 		return rc;
     67 	}
     68 
     69 #ifdef BACKSQL_TRACE
     70 	Debug( LDAP_DEBUG_TRACE, "==>backsql_Prepare()\n" );
     71 #endif /* BACKSQL_TRACE */
     72 
     73 #ifdef BACKSQL_MSSQL_WORKAROUND
     74 	{
     75 		char		drv_name[ 30 ];
     76 		SWORD		len;
     77 
     78 		SQLGetInfo( dbh, SQL_DRIVER_NAME, drv_name, sizeof( drv_name ), &len );
     79 
     80 #ifdef BACKSQL_TRACE
     81 		Debug( LDAP_DEBUG_TRACE, "backsql_Prepare(): driver name=\"%s\"\n",
     82 				drv_name );
     83 #endif /* BACKSQL_TRACE */
     84 
     85 		ldap_pvt_str2upper( drv_name );
     86 		if ( !strncmp( drv_name, "SQLSRV32.DLL", STRLENOF( "SQLSRV32.DLL" ) ) ) {
     87 			/*
     88 			 * stupid default result set in MS SQL Server
     89 			 * does not support multiple active statements
     90 			 * on the same connection -- so we are trying
     91 			 * to make it not to use default result set...
     92 			 */
     93 			Debug( LDAP_DEBUG_TRACE, "_SQLprepare(): "
     94 				"enabling MS SQL Server default result "
     95 				"set workaround\n" );
     96 			rc = SQLSetStmtOption( *sth, SQL_CONCURRENCY,
     97 					SQL_CONCUR_ROWVER );
     98 			if ( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO ) {
     99 				Debug( LDAP_DEBUG_TRACE, "backsql_Prepare(): "
    100 					"SQLSetStmtOption(SQL_CONCURRENCY,"
    101 					"SQL_CONCUR_ROWVER) failed:\n" );
    102 				backsql_PrintErrors( SQL_NULL_HENV, dbh, *sth, rc );
    103 				SQLFreeStmt( *sth, SQL_DROP );
    104 				return rc;
    105 			}
    106 		}
    107 	}
    108 #endif /* BACKSQL_MSSQL_WORKAROUND */
    109 
    110 	if ( timeout > 0 ) {
    111 		Debug( LDAP_DEBUG_TRACE, "_SQLprepare(): "
    112 			"setting query timeout to %d sec.\n",
    113 			timeout );
    114 		rc = SQLSetStmtOption( *sth, SQL_QUERY_TIMEOUT, timeout );
    115 		if ( rc != SQL_SUCCESS ) {
    116 			backsql_PrintErrors( SQL_NULL_HENV, dbh, *sth, rc );
    117 			SQLFreeStmt( *sth, SQL_DROP );
    118 			return rc;
    119 		}
    120 	}
    121 
    122 #ifdef BACKSQL_TRACE
    123 	Debug( LDAP_DEBUG_TRACE, "<==backsql_Prepare() calling SQLPrepare()\n" );
    124 #endif /* BACKSQL_TRACE */
    125 
    126 	return SQLPrepare( *sth, (SQLCHAR *)query, SQL_NTS );
    127 }
    128 
    129 RETCODE
    130 backsql_BindRowAsStrings_x( SQLHSTMT sth, BACKSQL_ROW_NTS *row, void *ctx )
    131 {
    132 	RETCODE		rc;
    133 
    134 	if ( row == NULL ) {
    135 		return SQL_ERROR;
    136 	}
    137 
    138 #ifdef BACKSQL_TRACE
    139 	Debug( LDAP_DEBUG_TRACE, "==> backsql_BindRowAsStrings()\n" );
    140 #endif /* BACKSQL_TRACE */
    141 
    142 	rc = SQLNumResultCols( sth, &row->ncols );
    143 	if ( rc != SQL_SUCCESS ) {
    144 #ifdef BACKSQL_TRACE
    145 		Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings(): "
    146 			"SQLNumResultCols() failed:\n" );
    147 #endif /* BACKSQL_TRACE */
    148 
    149 		backsql_PrintErrors( SQL_NULL_HENV, SQL_NULL_HDBC, sth, rc );
    150 
    151 	} else {
    152 		SQLCHAR		colname[ 64 ];
    153 		SQLSMALLINT	name_len, col_type, col_scale, col_null;
    154 		SQLLEN		col_prec;
    155 		int		i;
    156 
    157 #ifdef BACKSQL_TRACE
    158 		Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: "
    159 			"ncols=%d\n", (int)row->ncols );
    160 #endif /* BACKSQL_TRACE */
    161 
    162 		row->col_names = (BerVarray)ber_memcalloc_x( row->ncols + 1,
    163 				sizeof( struct berval ), ctx );
    164 		if ( row->col_names == NULL ) {
    165 			goto nomem;
    166 		}
    167 
    168 		row->col_prec = (UDWORD *)ber_memcalloc_x( row->ncols,
    169 				sizeof( UDWORD ), ctx );
    170 		if ( row->col_prec == NULL ) {
    171 			goto nomem;
    172 		}
    173 
    174 		row->col_type = (SQLSMALLINT *)ber_memcalloc_x( row->ncols,
    175 				sizeof( SQLSMALLINT ), ctx );
    176 		if ( row->col_type == NULL ) {
    177 			goto nomem;
    178 		}
    179 
    180 		row->cols = (char **)ber_memcalloc_x( row->ncols + 1,
    181 				sizeof( char * ), ctx );
    182 		if ( row->cols == NULL ) {
    183 			goto nomem;
    184 		}
    185 
    186 		row->value_len = (SQLLEN *)ber_memcalloc_x( row->ncols,
    187 				sizeof( SQLLEN ), ctx );
    188 		if ( row->value_len == NULL ) {
    189 			goto nomem;
    190 		}
    191 
    192 		if ( 0 ) {
    193 nomem:
    194 			ber_memfree_x( row->col_names, ctx );
    195 			row->col_names = NULL;
    196 			ber_memfree_x( row->col_prec, ctx );
    197 			row->col_prec = NULL;
    198 			ber_memfree_x( row->col_type, ctx );
    199 			row->col_type = NULL;
    200 			ber_memfree_x( row->cols, ctx );
    201 			row->cols = NULL;
    202 			ber_memfree_x( row->value_len, ctx );
    203 			row->value_len = NULL;
    204 
    205 			Debug( LDAP_DEBUG_ANY, "backsql_BindRowAsStrings: "
    206 				"out of memory\n" );
    207 
    208 			return LDAP_NO_MEMORY;
    209 		}
    210 
    211 		for ( i = 0; i < row->ncols; i++ ) {
    212 			SQLSMALLINT	TargetType;
    213 
    214 			rc = SQLDescribeCol( sth, (SQLSMALLINT)(i + 1), &colname[ 0 ],
    215 					(SQLUINTEGER)( sizeof( colname ) - 1 ),
    216 					&name_len, &col_type,
    217 					&col_prec, &col_scale, &col_null );
    218 			/* FIXME: test rc? */
    219 
    220 			ber_str2bv_x( (char *)colname, 0, 1,
    221 					&row->col_names[ i ], ctx );
    222 #ifdef BACKSQL_TRACE
    223 			Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: "
    224 				"col_name=%s, col_prec[%d]=%d\n",
    225 				colname, (int)(i + 1), (int)col_prec );
    226 #endif /* BACKSQL_TRACE */
    227 			if ( col_type != SQL_CHAR && col_type != SQL_VARCHAR )
    228 			{
    229 				col_prec = MAX_ATTR_LEN;
    230 			}
    231 
    232 			row->cols[ i ] = (char *)ber_memcalloc_x( col_prec + 1,
    233 					sizeof( char ), ctx );
    234 			row->col_prec[ i ] = col_prec;
    235 			row->col_type[ i ] = col_type;
    236 
    237 			/*
    238 			 * ITS#3386, ITS#3113 - 20070308
    239 			 * Note: there are many differences between various DPMS and ODBC
    240 			 * Systems; some support SQL_C_BLOB, SQL_C_BLOB_LOCATOR.  YMMV:
    241 			 * This has only been tested on Linux/MySQL/UnixODBC
    242 			 * For BINARY-type Fields (BLOB, etc), read the data as BINARY
    243 			 */
    244 			if ( BACKSQL_IS_BINARY( col_type ) ) {
    245 #ifdef BACKSQL_TRACE
    246 				Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: "
    247 					"col_name=%s, col_type[%d]=%d: reading binary data\n",
    248 					colname, (int)(i + 1), (int)col_type);
    249 #endif /* BACKSQL_TRACE */
    250 				TargetType = SQL_C_BINARY;
    251 
    252 			} else {
    253 				/* Otherwise read it as Character data */
    254 #ifdef BACKSQL_TRACE
    255 				Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: "
    256 					"col_name=%s, col_type[%d]=%d: reading character data\n",
    257 					colname, (int)(i + 1), (int)col_type);
    258 #endif /* BACKSQL_TRACE */
    259 				TargetType = SQL_C_CHAR;
    260 			}
    261 
    262 			rc = SQLBindCol( sth, (SQLUSMALLINT)(i + 1),
    263 				 TargetType,
    264 				 (SQLPOINTER)row->cols[ i ],
    265 				 col_prec + 1,
    266 				 &row->value_len[ i ] );
    267 
    268 			/* FIXME: test rc? */
    269 		}
    270 
    271 		BER_BVZERO( &row->col_names[ i ] );
    272 		row->cols[ i ] = NULL;
    273 	}
    274 
    275 #ifdef BACKSQL_TRACE
    276 	Debug( LDAP_DEBUG_TRACE, "<== backsql_BindRowAsStrings()\n" );
    277 #endif /* BACKSQL_TRACE */
    278 
    279 	return rc;
    280 }
    281 
    282 RETCODE
    283 backsql_BindRowAsStrings( SQLHSTMT sth, BACKSQL_ROW_NTS *row )
    284 {
    285 	return backsql_BindRowAsStrings_x( sth, row, NULL );
    286 }
    287 
    288 RETCODE
    289 backsql_FreeRow_x( BACKSQL_ROW_NTS *row, void *ctx )
    290 {
    291 	if ( row->cols == NULL ) {
    292 		return SQL_ERROR;
    293 	}
    294 
    295 	ber_bvarray_free_x( row->col_names, ctx );
    296 	ber_memfree_x( row->col_prec, ctx );
    297 	ber_memfree_x( row->col_type, ctx );
    298 	ber_memvfree_x( (void **)row->cols, ctx );
    299 	ber_memfree_x( row->value_len, ctx );
    300 
    301 	return SQL_SUCCESS;
    302 }
    303 
    304 
    305 RETCODE
    306 backsql_FreeRow( BACKSQL_ROW_NTS *row )
    307 {
    308 	return backsql_FreeRow_x( row, NULL );
    309 }
    310 
    311 static void
    312 backsql_close_db_handle( SQLHDBC dbh )
    313 {
    314 	if ( dbh == SQL_NULL_HDBC ) {
    315 		return;
    316 	}
    317 
    318 	Debug( LDAP_DEBUG_TRACE, "==>backsql_close_db_handle(%p)\n",
    319 		(void *)dbh );
    320 
    321 	/*
    322 	 * Default transact is SQL_ROLLBACK; commit is required only
    323 	 * by write operations, and it is explicitly performed after
    324 	 * each atomic operation succeeds.
    325 	 */
    326 
    327 	/* TimesTen */
    328 	SQLTransact( SQL_NULL_HENV, dbh, SQL_ROLLBACK );
    329 	SQLDisconnect( dbh );
    330 	SQLFreeConnect( dbh );
    331 
    332 	Debug( LDAP_DEBUG_TRACE, "<==backsql_close_db_handle(%p)\n",
    333 		(void *)dbh );
    334 }
    335 
    336 int
    337 backsql_conn_destroy(
    338 	backsql_info	*bi )
    339 {
    340 	return 0;
    341 }
    342 
    343 int
    344 backsql_init_db_env( backsql_info *bi )
    345 {
    346 	RETCODE		rc;
    347 	int		ret = SQL_SUCCESS;
    348 
    349 	Debug( LDAP_DEBUG_TRACE, "==>backsql_init_db_env()\n" );
    350 
    351 	rc = SQLAllocEnv( &bi->sql_db_env );
    352 	if ( rc != SQL_SUCCESS ) {
    353 		Debug( LDAP_DEBUG_TRACE, "init_db_env: SQLAllocEnv failed:\n" );
    354 		backsql_PrintErrors( SQL_NULL_HENV, SQL_NULL_HDBC,
    355 				SQL_NULL_HENV, rc );
    356 		ret = SQL_ERROR;
    357 	}
    358 
    359 	Debug( LDAP_DEBUG_TRACE, "<==backsql_init_db_env()=%d\n", ret );
    360 
    361 	return ret;
    362 }
    363 
    364 int
    365 backsql_free_db_env( backsql_info *bi )
    366 {
    367 	Debug( LDAP_DEBUG_TRACE, "==>backsql_free_db_env()\n" );
    368 
    369 	(void)SQLFreeEnv( bi->sql_db_env );
    370 	bi->sql_db_env = SQL_NULL_HENV;
    371 
    372 	/*
    373 	 * stop, if frontend waits for all threads to shutdown
    374 	 * before calling this -- then what are we going to delete??
    375 	 * everything is already deleted...
    376 	 */
    377 	Debug( LDAP_DEBUG_TRACE, "<==backsql_free_db_env()\n" );
    378 
    379 	return SQL_SUCCESS;
    380 }
    381 
    382 static int
    383 backsql_open_db_handle(
    384 	backsql_info	*bi,
    385 	SQLHDBC		*dbhp )
    386 {
    387 	/* TimesTen */
    388 	char			DBMSName[ 32 ];
    389 	int			rc;
    390 
    391 	assert( dbhp != NULL );
    392 	*dbhp = SQL_NULL_HDBC;
    393 
    394 	Debug( LDAP_DEBUG_TRACE, "==>backsql_open_db_handle()\n" );
    395 
    396 	rc = SQLAllocConnect( bi->sql_db_env, dbhp );
    397 	if ( !BACKSQL_SUCCESS( rc ) ) {
    398 		Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): "
    399 			"SQLAllocConnect() failed:\n" );
    400 		backsql_PrintErrors( bi->sql_db_env, SQL_NULL_HDBC,
    401 			SQL_NULL_HENV, rc );
    402 		return LDAP_UNAVAILABLE;
    403 	}
    404 
    405 	rc = SQLConnect( *dbhp,
    406 		(SQLCHAR*)bi->sql_dbname, SQL_NTS,
    407 		(SQLCHAR*)bi->sql_dbuser, SQL_NTS,
    408 		(SQLCHAR*)bi->sql_dbpasswd, SQL_NTS );
    409 	if ( rc != SQL_SUCCESS ) {
    410 		Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): "
    411 			"SQLConnect() to database \"%s\" %s.\n",
    412 			bi->sql_dbname,
    413 			rc == SQL_SUCCESS_WITH_INFO ?
    414 				"succeeded with info" : "failed" );
    415 		backsql_PrintErrors( bi->sql_db_env, *dbhp, SQL_NULL_HENV, rc );
    416 		if ( rc != SQL_SUCCESS_WITH_INFO ) {
    417 			SQLFreeConnect( *dbhp );
    418 			return LDAP_UNAVAILABLE;
    419 		}
    420 	}
    421 
    422 	/*
    423 	 * TimesTen : Turn off autocommit.  We must explicitly
    424 	 * commit any transactions.
    425 	 */
    426 	SQLSetConnectOption( *dbhp, SQL_AUTOCOMMIT,
    427 		BACKSQL_AUTOCOMMIT_ON( bi ) ?  SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF );
    428 
    429 	/*
    430 	 * See if this connection is to TimesTen.  If it is,
    431 	 * remember that fact for later use.
    432 	 */
    433 	/* Assume until proven otherwise */
    434 	bi->sql_flags &= ~BSQLF_USE_REVERSE_DN;
    435 	DBMSName[ 0 ] = '\0';
    436 	rc = SQLGetInfo( *dbhp, SQL_DBMS_NAME, (PTR)&DBMSName,
    437 			sizeof( DBMSName ), NULL );
    438 	if ( rc == SQL_SUCCESS ) {
    439 		if ( strcmp( DBMSName, "TimesTen" ) == 0 ||
    440 			strcmp( DBMSName, "Front-Tier" ) == 0 )
    441 		{
    442 			Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): "
    443 				"TimesTen database!\n" );
    444 			bi->sql_flags |= BSQLF_USE_REVERSE_DN;
    445 		}
    446 
    447 	} else {
    448 		Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): "
    449 			"SQLGetInfo() failed.\n" );
    450 		backsql_PrintErrors( bi->sql_db_env, *dbhp, SQL_NULL_HENV, rc );
    451 		SQLDisconnect( *dbhp );
    452 		SQLFreeConnect( *dbhp );
    453 		return LDAP_UNAVAILABLE;
    454 	}
    455 	/* end TimesTen */
    456 
    457 	Debug( LDAP_DEBUG_TRACE, "<==backsql_open_db_handle()\n" );
    458 
    459 	return LDAP_SUCCESS;
    460 }
    461 
    462 static void	*backsql_db_conn_dummy;
    463 
    464 static void
    465 backsql_db_conn_keyfree(
    466 	void		*key,
    467 	void		*data )
    468 {
    469 	(void)backsql_close_db_handle( (SQLHDBC)data );
    470 }
    471 
    472 int
    473 backsql_free_db_conn( Operation *op, SQLHDBC dbh )
    474 {
    475 	Debug( LDAP_DEBUG_TRACE, "==>backsql_free_db_conn()\n" );
    476 
    477 	(void)backsql_close_db_handle( dbh );
    478 	ldap_pvt_thread_pool_setkey( op->o_threadctx,
    479 		&backsql_db_conn_dummy, (void *)SQL_NULL_HDBC,
    480 		backsql_db_conn_keyfree, NULL, NULL );
    481 
    482 	Debug( LDAP_DEBUG_TRACE, "<==backsql_free_db_conn()\n" );
    483 
    484 	return LDAP_SUCCESS;
    485 }
    486 
    487 int
    488 backsql_get_db_conn( Operation *op, SQLHDBC *dbhp )
    489 {
    490 	backsql_info	*bi = (backsql_info *)op->o_bd->be_private;
    491 	int		rc = LDAP_SUCCESS;
    492 	SQLHDBC		dbh = SQL_NULL_HDBC;
    493 
    494 	Debug( LDAP_DEBUG_TRACE, "==>backsql_get_db_conn()\n" );
    495 
    496 	assert( dbhp != NULL );
    497 	*dbhp = SQL_NULL_HDBC;
    498 
    499 	if ( op->o_threadctx ) {
    500 		void		*data = NULL;
    501 
    502 		ldap_pvt_thread_pool_getkey( op->o_threadctx,
    503 				&backsql_db_conn_dummy, &data, NULL );
    504 		dbh = (SQLHDBC)data;
    505 
    506 	} else {
    507 		dbh = bi->sql_dbh;
    508 	}
    509 
    510 	if ( dbh == SQL_NULL_HDBC ) {
    511 		rc = backsql_open_db_handle( bi, &dbh );
    512 		if ( rc != LDAP_SUCCESS ) {
    513 			return rc;
    514 		}
    515 
    516 		if ( op->o_threadctx ) {
    517 			void		*data = (void *)dbh;
    518 
    519 			ldap_pvt_thread_pool_setkey( op->o_threadctx,
    520 					&backsql_db_conn_dummy, data,
    521 					backsql_db_conn_keyfree, NULL, NULL );
    522 
    523 		} else {
    524 			bi->sql_dbh = dbh;
    525 		}
    526 	}
    527 
    528 	*dbhp = dbh;
    529 
    530 	Debug( LDAP_DEBUG_TRACE, "<==backsql_get_db_conn()\n" );
    531 
    532 	return LDAP_SUCCESS;
    533 }
    534 
    535