Skip to content

Call func step1,add "VARIABLE" and "PRINT" command#805

Merged
gaoxueyu merged 3 commits intoIvorySQL:masterfrom
jiaoshuntian:call_func
May 30, 2025
Merged

Call func step1,add "VARIABLE" and "PRINT" command#805
gaoxueyu merged 3 commits intoIvorySQL:masterfrom
jiaoshuntian:call_func

Conversation

@jiaoshuntian
Copy link
Copy Markdown
Collaborator

@jiaoshuntian jiaoshuntian commented May 21, 2025

Summary by CodeRabbit

  • New Features

    • Added support for Oracle-compatible client commands (VARIABLE and PRINT) in the interactive terminal when in Oracle compatibility mode.
    • Introduced bind variables with type-aware validation, assignment, listing, and printing, alongside existing shell variables.
    • Enhanced variable management with distinct naming rules and type information for bind variables.
  • Tests

    • Added comprehensive tests for Oracle-style VARIABLE and PRINT commands, covering declarations, assignments, error handling, and multibyte variable names.
  • Chores

    • Updated build and test configurations to integrate new parser and scanner components and ensure correct test scheduling.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented May 21, 2025

Walkthrough

This update introduces Oracle SQL*PLUS-style client command support—specifically, VARIABLE and PRINT commands—into the PostgreSQL psql interactive terminal, including full parsing, lexical analysis, variable management, and test coverage. It adds new scanner and parser components, extends the variable subsystem for type-aware bind variables, and integrates Oracle client command handling into the main input loop and build system.

Changes

File(s) Change Summary
src/bin/psql/Makefile, src/bin/psql/meson.build Build files updated to add and integrate new scanner (psqlplusscan.l) and parser (psqlplusparse.y) components for Oracle-style commands. Explicit rules, dependencies, and clean-up targets were added.
src/bin/psql/psqlplus.h, src/bin/psql/psqlplusparse.y, src/bin/psql/psqlplusscan.l New header, Bison grammar, and Flex scanner implement Oracle SQL*PLUS client command parsing (VARIABLE, PRINT), keyword management, type handling, and error reporting for use in the client.
src/bin/psql/mainloop.c Main input loop extended to detect and process Oracle client commands in Oracle compatibility mode, dispatching VARIABLE and PRINT commands internally and bypassing normal SQL execution.
src/bin/psql/variables.c, src/bin/psql/variables.h Variable subsystem extended for "bind variables" with type info, validation, assignment, listing, and printing. Structs and APIs updated to distinguish shell and bind variables, with new functions for bind variable management.
src/include/fe_utils/psqlscan_int.h Lexer state extended with Oracle command parsing support: new enums, structs for command ASTs, print lists, and literal buffer management.
src/oracle_fe_utils/ora_psqlscan.l Scanner reset function updated to initialize new Oracle command and literal buffer fields.
src/oracle_test/regress/parallel_schedule Test schedule updated for concurrency restrictions and to include new ora_psql test.
src/oracle_test/regress/sql/ora_psql.sql New test script covering VARIABLE and PRINT commands, edge cases, error handling, and Oracle compatibility for bind variables.

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
Loading

Poem

In psql’s warren, new magic appears,
With VARIABLEs and PRINT, the bunny now cheers.
Oracle friends, come join the parade—
Your bind vars and types are no longer delayed!
Lexer and parser, they hop paw in paw,
Bringing SQL*PLUS dreams to PostgreSQL’s law.
🐇✨

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit 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.
Learn more here.


Note

⚡️ Faster reviews with caching

CodeRabbit 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 Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.
Enjoy the performance boost—your workflow just got faster.


📜 Recent 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

📥 Commits

Reviewing files that changed from the base of the PR and between a10c393 and 995e13c.

📒 Files selected for processing (1)
  • src/bin/psql/Makefile (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/bin/psql/Makefile
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: build (ubuntu-latest)
  • GitHub Check: oracle_regression (ubuntu-latest)
  • GitHub Check: pg_regression (ubuntu-latest)
  • GitHub Check: meson_build (ubuntu-latest)
  • GitHub Check: contrib_regression
  • GitHub Check: oracle_pg_regression (ubuntu-latest)

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.c and psqlplusparse.c, you should also include psqlplusparse.h to ensure complete cleanup of all generated files.

-	rm -f psqlplusscan.c psqlplusparse.c
+	rm -f psqlplusscan.c psqlplusparse.c psqlplusparse.h
src/bin/psql/mainloop.c (1)

600-652: Inefficient O(n²) realloc in PRINT list builder

Reallocating the array on every item is quadratic. Two simple fixes:

  1. Pre-count tokens first pass and allocate once, or
  2. 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. Consider PSVAR_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-case

The tests check empty string ('') but not a NULL literal (variable x number = null).
Add one to confirm SetBindVariable(..., 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 of yyscan_t.
Please delete this line to avoid misleading future maintainers.

src/bin/psql/variables.c (2)

63-202: Potential NULL-dereference & duplicated code in valid_bind_variable_value

  1. PQexec can return NULL; subsequent PQresultStatus will seg-fault.
  2. PQclear(res) is called unconditionally even when res is NULL.
  3. 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-row format_type() queries in ListBindVariables

For 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

📥 Commits

Reviewing files that changed from the base of the PR and between 683f602 and a10c393.

⛔ Files ignored due to path filters (2)
  • src/oracle_test/regress/expected/compression.out is excluded by !**/*.out
  • src/oracle_test/regress/expected/ora_psql.out is 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_psql test 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 psqlplusscan and psqlplusparse are 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.h is a backend header and is already transitively included via common.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 issue

Potential 1-byte buffer overrun when doubling quotes

pg_malloc0(strlen(initval) * 2) allocates exactly 2n bytes.
Worst case (initval all quote characters) copies 2n-1 payload bytes plus the NUL terminator → needs 2n + 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 owns psqlpluscmd and 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 entire psql session, forgetting to free the command tree will leak memory on every VARIABLE/PRINT execution.

Define a clear ownership contract:
• allocate in the parser,
• free in psqlplus_scanner_finish() or right after the command has been executed.
At the very least, document the expectation here.

Comment on lines +1458 to +1468
/* 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;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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:

  1. Check if psqlpluscmd is non-NULL
  2. Free the memory
  3. 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.

Comment thread src/bin/psql/mainloop.c
Comment on lines +560 to +588
/* 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"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Comment thread src/bin/psql/mainloop.c
Comment on lines +453 to +460
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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Comment thread src/bin/psql/mainloop.c
Comment on lines +512 to +527
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);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Comment thread src/bin/psql/mainloop.c
Comment on lines +434 to +457
/*
* 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());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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_EOL is 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" -C3

Length 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.l

Length 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.c around line 434: the pset.stmt_lineno == 1 check.
  • 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.l already returns ORAPSCAN_BACKSLASH (and should support hyphens), but later lines never reach it.

Suggested refactor:

  • Remove or relax the pset.stmt_lineno == 1 guard so that every input buffer is passed to the SQL*Plus scanner once ORAPSCAN_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.

Comment on lines +474 to +495
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;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Suggested change
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.

Comment on lines +531 to +538
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++;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Comment thread src/bin/psql/variables.c
Comment on lines +54 to +66
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:
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Suggested change
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.

Comment thread src/bin/psql/variables.c
Comment on lines +958 to +970
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;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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.

Comment thread src/bin/psql/variables.c
Comment on lines +686 to +731
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;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

@gaoxueyu gaoxueyu merged commit d7154fd into IvorySQL:master May 30, 2025
6 checks passed
This was referenced Oct 28, 2025
@coderabbitai coderabbitai bot mentioned this pull request Apr 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants