Skip to content

Commit f3bad22

Browse files
author
RUI LUO
committed
Address all blocking reviewer comments and improve code quality for the
remote procedure execution feature via linked servers. Includes fixes for return code propagation, memory safety, FreeTDS build abstraction, and significant code consolidation. Blocking Items Resolved: - Return code propagation: Use dbretstatus() to capture the remote procedure's actual RETURN value instead of hardcoding 0. Store in pltsql_proc_return_code for the caller to read via LINKED_SERVER_RET_STATUS macro. - Memory safety in validate_remote_procedure_recursive(): Initialize query.data to NULL before PG_TRY so PG_FINALLY can safely check it. Guard all pfree() calls with NULL checks to prevent double-free on error paths. - FreeTDS abstraction layer: Add comprehensive no-op stubs in linked_servers.h for non-FreeTDS builds covering RPC operations (LINKED_SERVER_RPC_INIT/PARAM/SEND/EXEC), OUTPUT parameter retrieval (LINKED_SERVER_NUM_RETS/RET_NAME/RET_DATA/RET_LEN/RET_TYPE/RET_STATUS), TDS type constants (LS_TYPE_VARCHAR, LS_TYPE_INT4, etc.), and FreeTDS data type aliases (LS_DBFLT8, LS_DBINT, etc.). - sp_serveroption: Accept "yes"/"no" in addition to "true"/"false" for rpc out option using pg_strcasecmp for case-insensitive comparison. Code Quality Improvements: - Debug logging cleanup: Remove 46 debug log statements, convert remaining 19 to DEBUG level using LINKED_SERVER_DEBUG macro instead of elog(LOG, ...). - Code consolidation: Refactor get_tds_type_from_pg_oid() and convert_datum_to_tds_bytes() to eliminate duplicate type-mapping logic.
1 parent 49c1680 commit f3bad22

File tree

10 files changed

+324
-337
lines changed

10 files changed

+324
-337
lines changed

contrib/babelfishpg_tsql/src/linked_servers.c

Lines changed: 193 additions & 235 deletions
Large diffs are not rendered by default.

contrib/babelfishpg_tsql/src/linked_servers.h

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ typedef DBPROCESS * LinkedServerProcess;
119119
#define LINKED_SERVER_RET_DATA(process, retnum) dbretdata(process, retnum)
120120
#define LINKED_SERVER_RET_LEN(process, retnum) dbretlen(process, retnum)
121121
#define LINKED_SERVER_RET_TYPE(process, retnum) dbrettype(process, retnum)
122+
#define LINKED_SERVER_RET_STATUS(process) dbretstatus(process)
122123

123124
#define LS_NTBSTRINGBING NTBSTRINGBIND
124125
#define LS_INTBIND INTBIND
@@ -202,12 +203,63 @@ typedef int *LinkedServerProcess;
202203
#define LINKED_SERVER_SET_QUERY_TIMEOUT(timeout) ((void)0)
203204
#define LINKED_SERVER_SET_CONNECT_TIMEOUT(timeout) ((void)0)
204205

206+
/* RPC (Remote Procedure Call) stubs - no-op when TDS library is not available */
207+
#define LINKED_SERVER_RPC_INIT(process, procname) ((void)0)
208+
#define LINKED_SERVER_RPC_PARAM(process, name, status, type, maxlen, datalen, value) \
209+
((void)0)
210+
#define LINKED_SERVER_RPC_SEND(process) ((void)0)
211+
#define LINKED_SERVER_RPC_EXEC(process) ((void)0)
212+
213+
/* RPC OUTPUT parameter retrieval stubs */
214+
#define LINKED_SERVER_NUM_RETS(process) (0)
215+
#define LINKED_SERVER_RET_NAME(process, retnum) (NULL)
216+
#define LINKED_SERVER_RET_DATA(process, retnum) (NULL)
217+
#define LINKED_SERVER_RET_LEN(process, retnum) (0)
218+
#define LINKED_SERVER_RET_TYPE(process, retnum) (0)
219+
#define LINKED_SERVER_RET_STATUS(process) (0)
220+
205221
#define LS_NTBSTRINGBING 0
206222
#define LS_INTBIND 0
207223

208224
#define LS_BYTE unsigned char
209225
#define LS_TYPEINFO int
210226

227+
/* TDS Type Abstraction Layer stubs */
228+
#define LS_TYPE_VARCHAR 0
229+
#define LS_TYPE_NVARCHAR 0
230+
#define LS_TYPE_CHAR 0
231+
#define LS_TYPE_NCHAR 0
232+
#define LS_TYPE_TEXT 0
233+
#define LS_TYPE_NTEXT 0
234+
#define LS_TYPE_INT1 0
235+
#define LS_TYPE_INT2 0
236+
#define LS_TYPE_INT4 0
237+
#define LS_TYPE_INT8 0
238+
#define LS_TYPE_FLOAT 0
239+
#define LS_TYPE_REAL 0
240+
#define LS_TYPE_BIT 0
241+
#define LS_TYPE_DATETIME 0
242+
#define LS_TYPE_DATETIME4 0
243+
#define LS_TYPE_DATETIME2 0
244+
#define LS_TYPE_DATE 0
245+
#define LS_TYPE_TIME 0
246+
#define LS_TYPE_NUMERIC 0
247+
#define LS_TYPE_DECIMAL 0
248+
#define LS_TYPE_VARBINARY 0
249+
#define LS_TYPE_BINARY 0
250+
#define LS_TYPE_UNIQUE 0
251+
#define LS_TYPE_DATETIMEOFFSET 0
252+
253+
/* FreeTDS data type stubs */
254+
#define LS_DBFLT8 double
255+
#define LS_DBREAL float
256+
#define LS_DBINT int
257+
#define LS_DBSMALLINT short
258+
#define LS_DBBOOL unsigned char
259+
#define LS_DBDATETIME int
260+
261+
typedef int LINKED_SERVER_RETCODE;
262+
211263
#endif
212264

213265
/* Debug macros */

contrib/babelfishpg_tsql/src/pl_exec-2.c

Lines changed: 25 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -884,7 +884,6 @@ execute_remote_procedure_rpc(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
884884
Portal portal = NULL;
885885
DestReceiver *receiver = NULL;
886886
QueryCompletion qc;
887-
uint64 processed = 0;
888887
ListCell *lc;
889888
List *param_data_buffers = NIL; /* Track parameter buffers for deferred cleanup */
890889

@@ -913,15 +912,15 @@ execute_remote_procedure_rpc(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
913912
stmt->schema_name ? stmt->schema_name : "dbo",
914913
stmt->proc_name);
915914

916-
elog(LOG, "DEBUG_LINKED_SERVER: (RPC) - Executing procedure: %s", full_proc_name);
915+
LINKED_SERVER_DEBUG("Executing remote procedure: %s", full_proc_name);
917916

918917
PG_TRY();
919918
{
920919
/*
921920
* PHASE 1: Validate procedure contains only SELECT statements
922921
* This will establish a temporary connection, fetch the definition, validate it, and close
923922
*/
924-
elog(LOG, "SELECT-only validation: Validating procedure %s", full_proc_name);
923+
LINKED_SERVER_DEBUG("SELECT-only validation: Validating procedure %s", full_proc_name);
925924

926925
/* Temporarily establish connection just for validation */
927926
linked_server_establish_connection(stmt->server_name, &validation_lsproc, false);
@@ -935,14 +934,12 @@ execute_remote_procedure_rpc(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
935934
/* Validation connection has been closed internally - mark as cleaned up */
936935
validation_lsproc = NULL;
937936

938-
elog(LOG, "SELECT-only validation: Procedure %s passed validation", full_proc_name);
939937

940938
/*
941939
* PHASE 2: Open fresh TDS connection for RPC execution
942940
* Clean connection with no SQL query history
943941
*/
944942
Assert(lsproc == NULL); /* Sanity check - RPC connection must be clean */
945-
elog(LOG, "DEBUG_LINKED_SERVER: (RPC) - Opening fresh connection for RPC");
946943
linked_server_establish_connection(stmt->server_name, &lsproc, false);
947944

948945
/* Initialize RPC call on clean connection */
@@ -951,7 +948,6 @@ execute_remote_procedure_rpc(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
951948
(errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION),
952949
errmsg("Failed to initialize RPC call for procedure %s", full_proc_name)));
953950

954-
elog(LOG, "DEBUG_LINKED_SERVER: (RPC) - RPC call initialized, binding parameters");
955951

956952
/* Bind each parameter */
957953
foreach(lc, stmt->params)
@@ -974,8 +970,6 @@ execute_remote_procedure_rpc(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
974970
tds_type = get_tds_type_from_pg_oid(valtype);
975971

976972
/* DEBUG: Log before conversion */
977-
elog(LOG, "DEBUG_RPC_PARAM: Converting parameter: name=%s, pg_type_oid=%u, tds_type=%d",
978-
p->name ? p->name : "(positional)", valtype, tds_type);
979973

980974
/* Convert Datum to raw bytes */
981975
convert_datum_to_tds_bytes(val, valtype, valtypmod, isnull,
@@ -1003,22 +997,9 @@ execute_remote_procedure_rpc(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
1003997
}
1004998

1005999
/* DEBUG: Log parameter details before dbrpcparam call */
1006-
elog(LOG, "DEBUG_RPC_PARAM: Binding parameter: name=%s, tds_type=%d, maxlen=%d, len=%d, isnull=%d",
1000+
LINKED_SERVER_DEBUG_FINER("RPC param: name=%s, tds_type=%d, maxlen=%d, len=%d, isnull=%d",
10071001
p->name ? p->name : "(positional)", tds_type, maxlen, param_len, isnull);
10081002

1009-
/* Log first bytes if data present */
1010-
if (param_data && param_len > 0)
1011-
{
1012-
int bytes_to_show = (param_len < 20) ? param_len : 20;
1013-
StringInfoData hex_dump;
1014-
initStringInfo(&hex_dump);
1015-
for (int i = 0; i < bytes_to_show; i++)
1016-
{
1017-
appendStringInfo(&hex_dump, "%02x ", ((unsigned char*)param_data)[i]);
1018-
}
1019-
elog(LOG, "DEBUG_RPC_PARAM: Parameter data first %d bytes (hex): %s", bytes_to_show, hex_dump.data);
1020-
pfree(hex_dump.data);
1021-
}
10221003

10231004
/* Bind the parameter */
10241005
if (LINKED_SERVER_RPC_PARAM(lsproc,
@@ -1046,15 +1027,13 @@ execute_remote_procedure_rpc(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
10461027
param_data_buffers = lappend(param_data_buffers, param_data);
10471028
}
10481029

1049-
elog(LOG, "DEBUG_LINKED_SERVER: (RPC) - All parameters bound, sending RPC call");
10501030

10511031
/* Send the RPC call */
10521032
if (LINKED_SERVER_RPC_SEND(lsproc) != SUCCEED)
10531033
ereport(ERROR,
10541034
(errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION),
10551035
errmsg("Failed to send RPC call for procedure %s", full_proc_name)));
10561036

1057-
elog(LOG, "DEBUG_LINKED_SERVER: (RPC) - RPC call sent, data transmitted to server");
10581037

10591038
/*
10601039
* NOW it's safe to free parameter data buffers - dbrpcsend() has transmitted the data.
@@ -1071,7 +1050,6 @@ execute_remote_procedure_rpc(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
10711050
}
10721051
list_free(param_data_buffers);
10731052
param_data_buffers = NIL;
1074-
elog(LOG, "DEBUG_LINKED_SERVER: (RPC) - Parameter buffers freed after RPC send");
10751053
}
10761054

10771055
/* Execute the RPC */
@@ -1080,7 +1058,6 @@ execute_remote_procedure_rpc(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
10801058
(errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION),
10811059
errmsg("Failed to execute remote procedure %s", full_proc_name)));
10821060

1083-
elog(LOG, "DEBUG_LINKED_SERVER: (RPC) - Remote procedure executed successfully");
10841061

10851062
/* Get first result set (procedures may return multiple result sets) */
10861063
if ((erc = LINKED_SERVER_RESULTS(lsproc)) != NO_MORE_RESULTS)
@@ -1098,7 +1075,7 @@ execute_remote_procedure_rpc(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
10981075
void *val[MAX_COLS_SELECT];
10991076

11001077
/* Procedure returned a result set */
1101-
elog(LOG, "DEBUG_LINKED_SERVER: (RPC) - Result set with %d columns", colcount);
1078+
LINKED_SERVER_DEBUG("Remote RPC result set with %d columns", colcount);
11021079

11031080
/* Build TupleDesc from column metadata */
11041081
tupdesc = CreateTemplateTupleDesc(colcount);
@@ -1149,7 +1126,7 @@ execute_remote_procedure_rpc(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
11491126
pfree(nulls);
11501127
}
11511128

1152-
elog(LOG, "DEBUG_LINKED_SERVER: (RPC) - Fetched %d rows", rowcount);
1129+
LINKED_SERVER_DEBUG("Remote RPC fetched %d rows", rowcount);
11531130

11541131
/* Finalize tuplestore */
11551132
tuplestore_donestoring(tupstore);
@@ -1173,16 +1150,14 @@ execute_remote_procedure_rpc(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
11731150
receiver = CreateDestReceiver(DestRemote);
11741151
SetRemoteDestReceiverParams(receiver, portal);
11751152

1176-
if (PortalRun(portal,
1177-
FETCH_ALL,
1178-
true,
1179-
true,
1180-
receiver,
1181-
receiver,
1182-
&qc))
1183-
processed = portal->portalPos;
1153+
PortalRun(portal,
1154+
FETCH_ALL,
1155+
true,
1156+
true,
1157+
receiver,
1158+
receiver,
1159+
&qc);
11841160

1185-
elog(LOG, "DEBUG_LINKED_SERVER: (RPC) - Sent %d rows to client", (int)processed);
11861161

11871162
receiver->rDestroy(receiver);
11881163

@@ -1213,7 +1188,7 @@ execute_remote_procedure_rpc(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
12131188
ListCell *lc_out;
12141189
int param_idx;
12151190

1216-
elog(LOG, "DEBUG_LINKED_SERVER: (RPC) - Retrieving %d OUTPUT parameter values", num_rets);
1191+
LINKED_SERVER_DEBUG("Retrieving %d OUTPUT parameter values", num_rets);
12171192

12181193
/*
12191194
* Iterate through the stmt->params list to find OUTPUT parameters
@@ -1251,7 +1226,7 @@ execute_remote_procedure_rpc(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
12511226
int ret_len = LINKED_SERVER_RET_LEN(lsproc, ret_idx);
12521227
int ret_type = LINKED_SERVER_RET_TYPE(lsproc, ret_idx);
12531228

1254-
elog(LOG, "DEBUG_LINKED_SERVER: (RPC) - OUTPUT param %s: type=%d, len=%d, data=%s",
1229+
LINKED_SERVER_DEBUG_FINER("OUTPUT param %s: type=%d, len=%d, data=%s",
12551230
p->name ? p->name : "(positional)",
12561231
ret_type, ret_len,
12571232
ret_data ? "present" : "NULL");
@@ -1300,15 +1275,22 @@ execute_remote_procedure_rpc(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
13001275
exec_set_rowcount(rowcount);
13011276
exec_set_found(estate, rowcount != 0);
13021277

1303-
elog(LOG, "DEBUG_LINKED_SERVER: (RPC) - Remote procedure execution completed successfully");
1278+
/*
1279+
* PHASE 4: Capture the remote procedure's return status.
1280+
* In TDS protocol, dbretstatus() returns the RETURN value from the
1281+
* remote stored procedure (the value in "RETURN <n>").
1282+
* Store it in pltsql_proc_return_code so the caller can read it.
1283+
*/
1284+
pltsql_proc_return_code = LINKED_SERVER_RET_STATUS(lsproc);
1285+
1286+
LINKED_SERVER_DEBUG("Remote procedure completed, return status=%d", pltsql_proc_return_code);
13041287
}
13051288
PG_FINALLY();
13061289
{
13071290
/* Clean up parameter buffers if error occurred before RPC send completed */
13081291
if (param_data_buffers != NIL)
13091292
{
13101293
ListCell *buf_cell;
1311-
elog(LOG, "DEBUG_LINKED_SERVER: (CLEANUP) - Freeing parameter buffers in error path");
13121294
foreach(buf_cell, param_data_buffers)
13131295
{
13141296
void *buf = lfirst(buf_cell);
@@ -1322,7 +1304,6 @@ execute_remote_procedure_rpc(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
13221304
/* Clean up validation connection if error occurred during validation */
13231305
if (validation_lsproc)
13241306
{
1325-
elog(LOG, "DEBUG_LINKED_SERVER: (CLEANUP) - Closing validation connection");
13261307
LINKED_SERVER_CANCEL(validation_lsproc);
13271308
LINKED_SERVER_CLOSE(validation_lsproc);
13281309
validation_lsproc = NULL;
@@ -1331,7 +1312,6 @@ execute_remote_procedure_rpc(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
13311312
/* Clean up RPC connection if error occurred during RPC */
13321313
if (lsproc)
13331314
{
1334-
elog(LOG, "DEBUG_LINKED_SERVER: (RPC) - Closing connection");
13351315
LINKED_SERVER_CANCEL(lsproc);
13361316
LINKED_SERVER_CLOSE(lsproc);
13371317
lsproc = NULL;
@@ -1367,11 +1347,6 @@ exec_stmt_exec(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
13671347
* Check if this is a remote procedure call via linked server.
13681348
* If server_name is set, this is a 4-part name like: server.db.schema.proc
13691349
*/
1370-
elog(LOG, "DEBUG_REMOTE_EXEC: Checking server_name - value: %s, db_name: %s, schema_name: %s, proc_name: %s",
1371-
stmt->server_name ? stmt->server_name : "NULL",
1372-
stmt->db_name ? stmt->db_name : "NULL",
1373-
stmt->schema_name ? stmt->schema_name : "NULL",
1374-
stmt->proc_name ? stmt->proc_name : "NULL");
13751350

13761351
if (stmt->server_name != NULL)
13771352
{
@@ -1396,7 +1371,6 @@ exec_stmt_exec(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
13961371
errhint("Execute: EXEC sp_serveroption '%s', 'rpc out', 'true'",
13971372
stmt->server_name)));
13981373

1399-
elog(LOG, "DEBUG_REMOTE_EXEC: RPC out is enabled, proceeding with remote execution for server: %s", stmt->server_name);
14001374

14011375
/* Execute remote procedure using secure TDS RPC parameter binding */
14021376
#ifdef ENABLE_TDS_LIB
@@ -1409,8 +1383,8 @@ exec_stmt_exec(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
14091383
if (stmt->return_code_dno >= 0)
14101384
{
14111385
PLtsql_var *return_code = (PLtsql_var *) estate->datums[stmt->return_code_dno];
1412-
/* Set return code to 0 (success) for now */
1413-
exec_assign_value(estate, (PLtsql_datum *) return_code, Int32GetDatum(0), false, INT4OID, 0);
1386+
/* Use the actual return status from the remote procedure (set by execute_remote_procedure_rpc) */
1387+
exec_assign_value(estate, (PLtsql_datum *) return_code, Int32GetDatum(pltsql_proc_return_code), false, INT4OID, 0);
14141388
}
14151389

14161390
return remote_rc;
@@ -1424,7 +1398,6 @@ exec_stmt_exec(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
14241398
#endif
14251399
}
14261400

1427-
elog(LOG, "DEBUG_REMOTE_EXEC: Taking LOCAL execution path (server_name is NULL)");
14281401

14291402
/*
14301403
* We need to disable the explain gucs incase of sp_reset_connection

contrib/babelfishpg_tsql/src/procedures.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2835,15 +2835,15 @@ update_bbf_server_options(char *servername, char *optname, char *optvalue, bool
28352835
errmsg("The 'rpc out' option is not available. The catalog needs to be upgraded.")));
28362836
}
28372837

2838-
/* Validate rpc out value: must be "true" or "false" (case-insensitive) */
2838+
/* Validate rpc out value: "true"/"false" or "yes"/"no" (case-insensitive, matching SQL Server) */
28392839
if (strlen(optvalue) == 0)
28402840
ereport(ERROR,
28412841
(errcode(ERRCODE_FDW_ERROR),
28422842
errmsg("Invalid option value for rpc out")));
28432843

2844-
if (strcmp(optvalue, "true") == 0)
2844+
if (pg_strcasecmp(optvalue, "true") == 0 || pg_strcasecmp(optvalue, "yes") == 0)
28452845
rpc_out_enabled = true;
2846-
else if (strcmp(optvalue, "false") == 0)
2846+
else if (pg_strcasecmp(optvalue, "false") == 0 || pg_strcasecmp(optvalue, "no") == 0)
28472847
rpc_out_enabled = false;
28482848
else
28492849
ereport(ERROR,

0 commit comments

Comments
 (0)