Call func step1,add "VARIABLE" and "PRINT" command#805
Call func step1,add "VARIABLE" and "PRINT" command#805gaoxueyu merged 3 commits intoIvorySQL:masterfrom
Conversation
PRINT compatible with Oracle SQL*PLUS.
WalkthroughThis update introduces Oracle SQL*PLUS-style client command support—specifically, VARIABLE and PRINT commands—into the PostgreSQL Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant MainLoop
participant OracleScanner
participant OracleParser
participant VariableMgr
User->>MainLoop: Enter command (VARIABLE/PRINT)
MainLoop->>OracleScanner: Initialize scanner with input line
OracleScanner->>OracleParser: Tokenize and parse command
OracleParser-->>MainLoop: Return parsed command AST
alt VARIABLE command
MainLoop->>VariableMgr: Create/List/Assign bind variable
VariableMgr-->>MainLoop: Success/Error
else PRINT command
MainLoop->>VariableMgr: Print bind variable(s)
VariableMgr-->>MainLoop: Output/Error
end
MainLoop->>User: Show result or error
Poem
Note ⚡️ AI Code Reviews for VS Code, Cursor, WindsurfCodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback. Note ⚡️ Faster reviews with cachingCodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 30th. To opt out, configure 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms (6)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 19
🧹 Nitpick comments (7)
src/bin/psql/Makefile (1)
97-97: Add psqlplusparse.h to the clean target.While you've added cleanup for
psqlplusscan.candpsqlplusparse.c, you should also includepsqlplusparse.hto ensure complete cleanup of all generated files.- rm -f psqlplusscan.c psqlplusparse.c + rm -f psqlplusscan.c psqlplusparse.c psqlplusparse.hsrc/bin/psql/mainloop.c (1)
600-652: Inefficient O(n²) realloc in PRINT list builderReallocating the array on every item is quadratic. Two simple fixes:
- Pre-count tokens first pass and allocate once, or
- Use doubling strategy (
*2) on growth.For interactive printing with many variables this reduces per-command latency.
src/bin/psql/variables.h (1)
58-65: Enum prefix misleading / not future-proof
PSQL_usually prefixes backend psql internals; these values escape to Oracle-specific code too. ConsiderPSVAR_KIND_*or similar to avoid collision if more psql-scoped enums appear.Nit, but renaming early saves churn.
src/oracle_test/regress/sql/ora_psql.sql (1)
170-188: Missing coverage for NULL initialisation edge-caseThe tests check empty string (
'') but not a NULL literal (variable x number = null).
Add one to confirmSetBindVariable(..., notnull=false)behaves as expected.src/bin/psql/psqlplus.h (1)
65-65: Stale commented code clutters the public header
//#define yyscan_t void *has no documentary value and can confuse readers about the true definition ofyyscan_t.
Please delete this line to avoid misleading future maintainers.src/bin/psql/variables.c (2)
63-202: Potential NULL-dereference & duplicated code invalid_bind_variable_value
PQexeccan returnNULL; subsequentPQresultStatuswill seg-fault.PQclear(res)is called unconditionally even whenresisNULL.- The switch branches are almost identical ‒ consider a helper to avoid copy-paste.
Minimal safety fix:
res = PQexec(pset.db, query->data); -if (PQresultStatus(res) == PGRES_TUPLES_OK && +if (res != NULL && + PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1 && !PQgetisnull(res, 0, 0)) { val = pg_strdup(PQgetvalue(res, 0, 0)); } … -if (res) PQclear(res); +if (res) PQclear(res);Long-term, factor the repetitive “run-single-value query” pattern into a utility.
790-849: Inefficient per-rowformat_type()queries inListBindVariablesFor each bind variable the function issues an individual
SELECT format_type(...).
With dozens of variables this causes N round-trips.Cache the results in a single query:
SELECT name, format_type(typoid, typmod) FROM (VALUES …) AS t(name, typoid, typmod) ORDER BY name;or iterate once through the list, building a single multi-row VALUES clause.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting
⛔ Files ignored due to path filters (2)
src/oracle_test/regress/expected/compression.outis excluded by!**/*.outsrc/oracle_test/regress/expected/ora_psql.outis excluded by!**/*.out
📒 Files selected for processing (12)
src/bin/psql/Makefile(3 hunks)src/bin/psql/mainloop.c(2 hunks)src/bin/psql/meson.build(1 hunks)src/bin/psql/psqlplus.h(1 hunks)src/bin/psql/psqlplusparse.y(1 hunks)src/bin/psql/psqlplusscan.l(1 hunks)src/bin/psql/variables.c(14 hunks)src/bin/psql/variables.h(3 hunks)src/include/fe_utils/psqlscan_int.h(2 hunks)src/oracle_fe_utils/ora_psqlscan.l(1 hunks)src/oracle_test/regress/parallel_schedule(1 hunks)src/oracle_test/regress/sql/ora_psql.sql(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/bin/psql/mainloop.c (4)
src/bin/psql/common.c (1)
standard_strings(2224-2238)src/bin/psql/psqlplus.h (3)
psqlplus_scanner_init(76-76)psqlplus_yyparse(73-73)psqlplus_scanner_finish(77-77)src/bin/psql/variables.h (5)
ListBindVariables(125-125)AssignBindVariable(127-127)SetBindVariable(122-124)PrintBindVariables(126-126)ValidBindVariableName(128-128)src/bin/psql/variables.c (5)
ListBindVariables(789-849)AssignBindVariable(917-971)SetBindVariable(678-784)PrintBindVariables(854-912)ValidBindVariableName(985-1014)
src/bin/psql/variables.c (1)
src/bin/psql/variables.h (5)
SetBindVariable(122-124)ValidBindVariableName(128-128)ListBindVariables(125-125)PrintBindVariables(126-126)AssignBindVariable(127-127)
⏰ Context from checks skipped due to timeout of 90000ms (6)
- GitHub Check: build (ubuntu-latest)
- GitHub Check: pg_regression (ubuntu-latest)
- GitHub Check: meson_build (ubuntu-latest)
- GitHub Check: oracle_regression (ubuntu-latest)
- GitHub Check: contrib_regression
- GitHub Check: oracle_pg_regression (ubuntu-latest)
🔇 Additional comments (7)
src/oracle_test/regress/parallel_schedule (1)
86-89: LGTM: Appropriate test grouping for concurrency control.The addition of the
ora_psqltest to this group makes sense as it likely has similar concurrency restrictions. The updated comment clearly explains why these tests must run together.src/bin/psql/Makefile (2)
57-61: LGTM: Proper build rules for the parser and scanner.The build rules for generating the parser files and establishing dependencies are correctly implemented.
41-47: LGTM: Appropriate object file additions.The new parser and scanner objects are properly added to the build.
src/bin/psql/meson.build (1)
35-46: LGTM: Well-structured custom targets for the new components.The custom targets for
psqlplusscanandpsqlplusparseare correctly defined and integrated into the build system, following the same pattern as the existing scanner targets.src/bin/psql/mainloop.c (2)
17-21: Unnecessary dependency & build-order risk
stringutils.his a backend header and is already transitively included viacommon.h; the extra include is redundant and broadens the build-time dependency graph.
Removing it avoids accidental cyclic-include issues and keeps the frontend/build isolation cleaner.
[ suggest_nitpick ]-#include "stringutils.h"
485-505:⚠️ Potential issuePotential 1-byte buffer overrun when doubling quotes
pg_malloc0(strlen(initval) * 2)allocates exactly2nbytes.
Worst case (initvalall quote characters) copies2n-1payload bytes plus the NUL terminator → needs2n + 1.- char *str_double_quote = pg_malloc0(strlen(bind_var->init_value) * 2); /* enough */ + char *str_double_quote = pg_malloc0(strlen(bind_var->init_value) * 2 + 1);Likely an incorrect or invalid review comment.
src/include/fe_utils/psqlscan_int.h (1)
259-261: Who ownspsqlpluscmdand when is it freed?A raw pointer is added to the shared lexer state without any accompanying lifecycle comments.
Because the scanner lives for the entirepsqlsession, forgetting to free the command tree will leak memory on every VARIABLE/PRINT execution.Define a clear ownership contract:
• allocate in the parser,
• free inpsqlplus_scanner_finish()or right after the command has been executed.
At the very least, document the expectation here.
| /* TODO: | ||
| * need free if psqlpluscmd is not null ? | ||
| * or copy it using state->psqlpluscmd if it not null | ||
| * and we free it within psql_scan_finish, late free | ||
| * the copy again. | ||
| * we need make a function for free psqlpluscmd. | ||
| */ | ||
| state->psqlpluscmd = NULL; | ||
| state->literalbuf = NULL; | ||
| state->literallen= 0; | ||
| state->literalalloc = 0; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Address the memory management TODO for psqlpluscmd.
The TODO comment correctly identifies a potential memory management issue with psqlpluscmd. Since this field can potentially hold dynamically allocated memory, you need to ensure proper cleanup to avoid memory leaks.
Consider implementing a dedicated function to handle the freeing of psqlpluscmd as mentioned in the comment. This function should:
- Check if
psqlpluscmdis non-NULL - Free the memory
- Set the pointer to NULL
This function can then be called from appropriate cleanup points like ora_psql_scan_finish.
🤖 Prompt for AI Agents
In src/oracle_fe_utils/ora_psqlscan.l around lines 1458 to 1468, the TODO notes
a potential memory leak with the psqlpluscmd pointer. Implement a dedicated
function that checks if psqlpluscmd is non-NULL, frees its allocated memory, and
sets it to NULL. Then, replace direct NULL assignments with calls to this
function and ensure it is invoked during cleanup in ora_psql_scan_finish to
properly manage memory and avoid leaks.
| /* Syntax parsing failed, but we know it's a client command */ | ||
| if (pstate->is_sqlplus_cmd) | ||
| { | ||
| char *token; | ||
| const char *whitespace = " \t\n\r"; | ||
|
|
||
| token = strtokx(pstate->scanline, whitespace, NULL, NULL, | ||
| 0, false, false, pset.encoding); | ||
|
|
||
| if (token && pg_strcasecmp(token, "variable") == 0) | ||
| { | ||
| token = strtokx(NULL, whitespace, NULL, NULL, | ||
| 0, false, false, pset.encoding); | ||
|
|
||
| /* | ||
| * keep in sync with 'truncate_char' in psqlplusparse.y | ||
| */ | ||
| if (token) | ||
| token[strcspn(token, ",()")] = '\0'; | ||
|
|
||
| /* | ||
| * Theoretically, this token is the name of a VARIABLE variable. | ||
| * Check whether the reason for the parsing failure is that the | ||
| * name is illegal. | ||
| */ | ||
| if (token && !ValidBindVariableName(token)) | ||
| pg_log_error("Illegal variable name \"%s\"", token); | ||
| else | ||
| pg_log_error("Usage: VAR[IABLE] [ <variable> [ NUMBER | CHAR | CHAR (n [CHAR|BYTE]) |\n" |
There was a problem hiding this comment.
🛠️ Refactor suggestion
strtokx mutation may alter shared buffer
token[strcspn(token, ",()")] = '\0'; truncates in-situ.
token points inside pstate->scanline which is later reused by history and error reporting — those strings will now be truncated.
Copy to a scratch buffer before destructive ops to avoid surprising downstream consumers.
🤖 Prompt for AI Agents
In src/bin/psql/mainloop.c around lines 560 to 588, the code modifies the token
string in place by inserting a null terminator, which alters the shared buffer
pstate->scanline used later for history and error reporting. To fix this, copy
the token string into a separate scratch buffer before performing any truncation
or modification, ensuring the original scanline buffer remains unchanged for
downstream consumers.
| pstate = ora_psql_scan_create(&psqlplus_callbacks); | ||
| ora_psql_scan_setup(pstate, psqlplus_line, | ||
| strlen(psqlplus_line), | ||
| pset.encoding, | ||
| standard_strings()); | ||
|
|
||
| yyscanner = psqlplus_scanner_init(pstate); | ||
| if (psqlplus_yyparse(yyscanner) == 0) |
There was a problem hiding this comment.
Compile-time failure – undefined symbol psqlplus_callbacks
psqlplus_callbacks is not declared anywhere, but is passed to ora_psql_scan_create(). The header only exposes Ora_psqlscan_callbacks.
- pstate = ora_psql_scan_create(&psqlplus_callbacks);
+ pstate = ora_psql_scan_create(&Ora_psqlscan_callbacks);Verify no other references exist.
🤖 Prompt for AI Agents
In src/bin/psql/mainloop.c around lines 453 to 460, the variable
psqlplus_callbacks is used but not declared, causing a compile-time error. To
fix this, declare and properly initialize a variable of type
Ora_psqlscan_callbacks named psqlplus_callbacks before passing it to
ora_psql_scan_create(). Ensure the declaration matches the expected structure
and is visible in this scope. Also, verify no other references to
psqlplus_callbacks exist elsewhere in the code.
| else if (bind_var->assign_bind_var) | ||
| { | ||
| AssignBindVariable(pset.vars, | ||
| bind_var->var_name, | ||
| bind_var->init_value); | ||
| } | ||
| else | ||
| { | ||
| Assert(bind_var->vartype); | ||
| SetBindVariable(pset.vars, | ||
| bind_var->var_name, | ||
| bind_var->vartype->oid, | ||
| bind_var->vartype->typmod, | ||
| bind_var->init_value, | ||
| bind_var->initial_nonnull_value); | ||
| } |
There was a problem hiding this comment.
Null-dereference risk on malformed grammar
bind_var->vartype is blindly dereferenced. When the grammar recognised VARIABLE x = 1 (assignment without type) the field is NULL and dereferencing crashes.
Guard before use and emit the same “Invalid option” error shown later.
- Assert(bind_var->vartype);
+ if (bind_var->vartype == NULL)
+ {
+ pg_log_error("Usage: VAR[IABLE] <variable> [type] [= value]");
+ break;
+ }Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/bin/psql/mainloop.c around lines 512 to 527, the code dereferences
bind_var->vartype without checking if it is NULL, which can cause a crash for
assignments without a type. Add a check to ensure bind_var->vartype is not NULL
before dereferencing it; if it is NULL, emit the same "Invalid option" error
message used elsewhere and avoid dereferencing to prevent null pointer crashes.
| /* | ||
| * At present, compatible-oracle client commands are all single-line | ||
| * commands, so we only scan the input of stmt_lineno = 1, which can | ||
| * avoid the performance loss caused by the parser invoked by multi-line | ||
| * commands. | ||
| * | ||
| * Note: | ||
| * Oracle client command also extends over multiple lines, but | ||
| * must using the SQL*Plus command continuation character(eg: -), | ||
| * the ontinuation character we haven't implemented it yet, so for | ||
| * the time being we're still assuming that these client commands | ||
| * are all one-liners. | ||
| */ | ||
| if (db_mode == DB_ORACLE && pset.stmt_lineno == 1) | ||
| { | ||
| PsqlScanState pstate; | ||
| yyscan_t yyscanner; | ||
| char *psqlplus_line = pg_strdup(line);; | ||
|
|
||
| pstate = ora_psql_scan_create(&psqlplus_callbacks); | ||
| ora_psql_scan_setup(pstate, psqlplus_line, | ||
| strlen(psqlplus_line), | ||
| pset.encoding, | ||
| standard_strings()); |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
First-line-only parsing will break valid SQL*PLUS continuation
The new guard if (db_mode == DB_ORACLE && pset.stmt_lineno == 1) assumes all future SQLPLUS client commands are single-line. SQLPLUS supports line continuation with the hyphen (-) and backslash (\). Once those are implemented, this hard stop will silently mis-parse multi-line commands and execute partial SQL instead.
Decouple the optimisation from correctness:
- call the Plus-parser unconditionally on each buffer once
ORAPSCAN_EOLis reached, - or keep a flag returned by the lexer that says “this definitely isn’t PLUS”, so you only skip subsequent calls when safe.
}
🏁 Script executed:
#!/bin/bash
# Search for ORAPSCAN_EOL definitions and usages to understand multi-line continuation handling
rg -n "ORAPSCAN_EOL" -C3Length of output: 2152
🏁 Script executed:
#!/bin/bash
# Show context around ORAPSCAN_BACKSLASH in ora_psqlscan.l to see how backslash is handled
rg -n "ORAPSCAN_BACKSLASH" -C5 src/oracle_fe_utils/ora_psqlscan.lLength of output: 423
Ensure SQL*Plus multi-line continuation is parsed correctly
The current guard (db_mode == DB_ORACLE && pset.stmt_lineno == 1) only invokes the SQL*Plus scanner on the first input line. As a result, any valid continuation commands—backslashes (\) or future hyphen (-) continuations—on subsequent lines will be ignored, leading to partial SQL being sent to the server.
Locations to update:
src/bin/psql/mainloop.caround line 434: thepset.stmt_lineno == 1check.- Scanning loop in the same file (around lines 884–895): currently only feeds line 1 into
ora_psql_scan_*. - Lexer in
src/oracle_fe_utils/ora_psqlscan.lalready returnsORAPSCAN_BACKSLASH(and should support hyphens), but later lines never reach it.
Suggested refactor:
- Remove or relax the
pset.stmt_lineno == 1guard so that every input buffer is passed to the SQL*Plus scanner onceORAPSCAN_EOL(end-of-line) is reached. - Alternatively, introduce a flag from the lexer (e.g. “definitely not PLUS”) and only skip scanning when that flag is set—preserving single-line performance without sacrificing multi-line correctness.
🤖 Prompt for AI Agents
In src/bin/psql/mainloop.c around lines 434 to 457, the current condition
restricts the SQL*Plus scanner to only run on the first input line
(pset.stmt_lineno == 1), which causes multi-line SQL*Plus commands with
continuation characters to be misparsed. To fix this, remove or relax the
pset.stmt_lineno == 1 check so that the SQL*Plus scanner is invoked on every
input line once ORAPSCAN_EOL is reached, ensuring all continuation lines are
correctly parsed. Alternatively, implement a lexer flag indicating when input is
definitely not SQL*Plus and skip scanning only in those cases, preserving
performance without breaking multi-line command handling.
| static char * | ||
| get_guc_settings(const char *gucname) | ||
| { | ||
| PQExpBuffer query = createPQExpBuffer(); | ||
| PGresult *res; | ||
| char *val = NULL; | ||
|
|
||
| appendPQExpBuffer(query, "SELECT setting from pg_settings where name = '%s';", gucname); | ||
| res = PQexec(pset.db, query->data); | ||
|
|
||
| if (PQresultStatus(res) == PGRES_TUPLES_OK && | ||
| PQntuples(res) == 1 && | ||
| !PQgetisnull(res, 0, 0)) | ||
| { | ||
| val = pg_strdup(PQgetvalue(res, 0, 0)); | ||
| } | ||
|
|
||
| PQclear(res); | ||
| destroyPQExpBuffer(query); | ||
|
|
||
| return val; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
get_guc_settings() does not guard against PQexec() failures
PQexec can return NULL (OOM, lost connection, etc.). Calling PQresultStatus or PQclear on a NULL pointer causes an immediate crash.
- res = PQexec(pset.db, query->data);
+ res = PQexec(pset.db, query->data);
+ if (res == NULL)
+ {
+ pg_log_error("could not execute query to fetch GUC \"%s\"", gucname);
+ destroyPQExpBuffer(query);
+ return NULL;
+ }Remember to keep the existing PQclear call, as it is now safe.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| static char * | |
| get_guc_settings(const char *gucname) | |
| { | |
| PQExpBuffer query = createPQExpBuffer(); | |
| PGresult *res; | |
| char *val = NULL; | |
| appendPQExpBuffer(query, "SELECT setting from pg_settings where name = '%s';", gucname); | |
| res = PQexec(pset.db, query->data); | |
| if (PQresultStatus(res) == PGRES_TUPLES_OK && | |
| PQntuples(res) == 1 && | |
| !PQgetisnull(res, 0, 0)) | |
| { | |
| val = pg_strdup(PQgetvalue(res, 0, 0)); | |
| } | |
| PQclear(res); | |
| destroyPQExpBuffer(query); | |
| return val; | |
| } | |
| static char * | |
| get_guc_settings(const char *gucname) | |
| { | |
| PQExpBuffer query = createPQExpBuffer(); | |
| PGresult *res; | |
| char *val = NULL; | |
| appendPQExpBuffer(query, "SELECT setting from pg_settings where name = '%s';", gucname); | |
| res = PQexec(pset.db, query->data); | |
| if (res == NULL) | |
| { | |
| pg_log_error("could not execute query to fetch GUC \"%s\"", gucname); | |
| destroyPQExpBuffer(query); | |
| return NULL; | |
| } | |
| if (PQresultStatus(res) == PGRES_TUPLES_OK && | |
| PQntuples(res) == 1 && | |
| !PQgetisnull(res, 0, 0)) | |
| { | |
| val = pg_strdup(PQgetvalue(res, 0, 0)); | |
| } | |
| PQclear(res); | |
| destroyPQExpBuffer(query); | |
| return val; | |
| } |
🤖 Prompt for AI Agents
In src/bin/psql/psqlplusparse.y around lines 474 to 495, the function
get_guc_settings does not check if PQexec returns NULL, which can happen on
errors like OOM or lost connection. To fix this, add a check immediately after
PQexec to verify that res is not NULL before calling PQresultStatus or PQclear.
If res is NULL, handle the error appropriately (e.g., return NULL) while still
ensuring PQclear is called only when res is non-NULL.
| list1->items = (print_item **) pg_realloc(list1->items, sizeof(print_item *) * newlen); | ||
|
|
||
| for (i = 0; i < list2->length; i++) | ||
| { | ||
| list1->items[list1->length] = list2->items[i]; | ||
| list1->length++; | ||
| } | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
merge_print_list leaks the memory owned by list2
After appending list2->items to list1, the memory allocated for list2->items and for list2 itself becomes unreachable, causing a leak each time the function is used.
for (i = 0; i < list2->length; i++)
{
list1->items[list1->length] = list2->items[i];
list1->length++;
}
-
- return list1;
+ pg_free(list2->items);
+ pg_free(list2);
+
+ return list1;🤖 Prompt for AI Agents
In src/bin/psql/psqlplusparse.y around lines 531 to 538, the function merges
list2's items into list1 but does not free the memory allocated for list2->items
or list2 itself, causing a memory leak. After appending all items from list2 to
list1, add code to free the memory of list2->items and then free list2 to
properly release the allocated memory and prevent leaks.
| valid_bind_variable_value(const int typoid, const int typmod, const char *value) | ||
| { | ||
| PQExpBuffer query = createPQExpBuffer(); | ||
| PGresult *res = NULL; | ||
| char *val = NULL; | ||
|
|
||
| if (!value) | ||
| return NULL; | ||
|
|
||
| switch(typoid) | ||
| { | ||
| case ORACHARCHAROID: | ||
| { |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Early-return leaks query buffer
When value == NULL, the function returns immediately, but query has already been allocated.
Free it before returning.
- if (!value)
- return NULL;
+ if (!value)
+ {
+ destroyPQExpBuffer(query);
+ return NULL;
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| valid_bind_variable_value(const int typoid, const int typmod, const char *value) | |
| { | |
| PQExpBuffer query = createPQExpBuffer(); | |
| PGresult *res = NULL; | |
| char *val = NULL; | |
| if (!value) | |
| return NULL; | |
| switch(typoid) | |
| { | |
| case ORACHARCHAROID: | |
| { | |
| valid_bind_variable_value(const int typoid, const int typmod, const char *value) | |
| { | |
| PQExpBuffer query = createPQExpBuffer(); | |
| PGresult *res = NULL; | |
| char *val = NULL; | |
| if (!value) | |
| { | |
| destroyPQExpBuffer(query); | |
| return NULL; | |
| } | |
| switch (typoid) | |
| { | |
| case ORACHARCHAROID: | |
| { | |
| /* ... */ |
🤖 Prompt for AI Agents
In src/bin/psql/variables.c around lines 54 to 66, the function
valid_bind_variable_value allocates the query buffer before checking if value is
NULL and returns early without freeing the allocated query, causing a memory
leak. To fix this, free the query buffer before the early return when value is
NULL to ensure no memory is leaked.
| char *newvalue = valid_bind_variable_value(current->typoid, current->typmod, value); | ||
|
|
||
| if (current->value) | ||
| pg_free(current->value); | ||
| current->value = newvalue; | ||
| } | ||
| else | ||
| { | ||
| pg_log_error("Bind variable \"%s\" not declared.", name); | ||
| return false; | ||
| } | ||
|
|
||
| return true; |
There was a problem hiding this comment.
AssignBindVariable silently succeeds on invalid value
newvalue can be NULL if the conversion fails, yet the code still overwrites the old (valid) value and returns true.
Add an explicit check:
char *newvalue = valid_bind_variable_value(current->typoid,
current->typmod,
value);
-if (current->value)
- pg_free(current->value);
-current->value = newvalue;
+if (newvalue == NULL)
+ return false; /* leave previous value untouched */
+
+pg_free(current->value);
+current->value = newvalue;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| char *newvalue = valid_bind_variable_value(current->typoid, current->typmod, value); | |
| if (current->value) | |
| pg_free(current->value); | |
| current->value = newvalue; | |
| } | |
| else | |
| { | |
| pg_log_error("Bind variable \"%s\" not declared.", name); | |
| return false; | |
| } | |
| return true; | |
| char *newvalue = valid_bind_variable_value(current->typoid, | |
| current->typmod, | |
| value); | |
| if (newvalue == NULL) | |
| return false; /* leave previous value untouched */ | |
| pg_free(current->value); | |
| current->value = newvalue; | |
| } | |
| else | |
| { | |
| pg_log_error("Bind variable \"%s\" not declared.", name); | |
| return false; | |
| } | |
| return true; |
🤖 Prompt for AI Agents
In src/bin/psql/variables.c around lines 958 to 970, the function
AssignBindVariable assigns newvalue to current->value and returns true even if
newvalue is NULL due to conversion failure, which silently overwrites a valid
old value. Add an explicit check after calling valid_bind_variable_value to
verify if newvalue is NULL; if it is, do not overwrite current->value, log an
appropriate error, and return false to indicate failure.
| struct _variable *current, | ||
| *previous; | ||
| char *value = valid_bind_variable_value(typoid, typmod, initval); | ||
|
|
||
| if (!space || !name) | ||
| return false; | ||
|
|
||
| if (!ValidBindVariableName(name)) | ||
| { | ||
| pg_log_error("Illegal variable name \"%s\"", name); | ||
| return false; | ||
| } | ||
|
|
||
| if (typoid == ORACHARCHAROID || | ||
| typoid == ORACHARBYTEOID || | ||
| typoid == ORAVARCHARCHAROID || | ||
| typoid == ORAVARCHARBYTEOID) | ||
| { | ||
| if (typmod - 4 < 1) | ||
| { | ||
| pg_log_error("Bind variable length cannot be less than 1."); | ||
| return false; | ||
| } | ||
|
|
||
| if ((typoid == ORACHARCHAROID || typoid == ORACHARBYTEOID) && | ||
| typmod - 4 > MaxOraCharLen) | ||
| { | ||
| pg_log_error("Bind variable length cannot exceed %d.", MaxOraCharLen); | ||
| return false; | ||
| } | ||
|
|
||
| if ((typoid == ORAVARCHARCHAROID || typoid == ORAVARCHARBYTEOID) && | ||
| typmod - 4 > MaxOraVarcharLen) | ||
| { | ||
| pg_log_error("Bind variable length cannot exceed %d.", MaxOraVarcharLen); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| if (notnull && !initval) | ||
| { | ||
| pg_log_error("Invalid option.\n" | ||
| "Usage: VAR[IABLE] <variable> [type][= value]"); | ||
| return false; | ||
| } | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
value leaks when validation fails inside SetBindVariable
value is allocated before any validation. If a later check fails (illegal name, bad typmod, etc.) the pointer is lost.
Move the call to valid_bind_variable_value() after all early-exit checks, or free value on every failure path.
- char *value = valid_bind_variable_value(typoid, typmod, initval);
…
- if (!ValidBindVariableName(name))
+ if (!ValidBindVariableName(name))
{
- pg_log_error(...);
- return false; /* leaks 'value' */
+ pg_log_error(...);
+ return false;
}
…
+/* Only now, after all “return false” checks: */
+char *value = valid_bind_variable_value(typoid, typmod, initval);🤖 Prompt for AI Agents
In src/bin/psql/variables.c between lines 686 and 731, the variable 'value' is
assigned by calling valid_bind_variable_value() before any validation checks,
causing a memory leak if validation fails and the function returns early. To fix
this, move the call to valid_bind_variable_value() to after all the early
validation checks have passed, or alternatively, ensure that 'value' is freed on
every failure path before returning false.
Summary by CodeRabbit
New Features
VARIABLEandPRINT) in the interactive terminal when in Oracle compatibility mode.Tests
VARIABLEandPRINTcommands, covering declarations, assignments, error handling, and multibyte variable names.Chores