Skip to content

Conversation

@Femi3211
Copy link
Collaborator

@Femi3211 Femi3211 commented Jan 6, 2026

Summary by CodeRabbit

  • New Features

    • Added a new "sql" CLI command to execute SQL queries against specified sources with support for row limits and custom output paths.
  • Documentation

    • Added comprehensive documentation for the sql command, including syntax, available options, and practical examples.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 6, 2026

📝 Walkthrough

Walkthrough

The changes introduce a new CLI command "sql" that enables direct SQL query execution against configured data sources. This is supported by new utility classes for query execution and CSV output handling, plus refactoring of existing query logic into reusable components. Documentation and integration tests are updated accordingly.

Changes

Cohort / File(s) Summary
CLI Command Addition
cli/src/main/java/com/adaptivescale/rosetta/cli/Cli.java
Added new sql() subcommand with options for source (-s), query (-q), limit (-l), and output path. Imports QueryService and delegates query execution to it.
Query Execution Service
queryhelper/src/main/java/queryhelper/service/QueryService.java
New class providing executeQuery() static method that orchestrates SQL execution, CSV generation, and table preview rendering. Uses utility methods from QueryUtils and FileUtils.
Utility Layer
queryhelper/src/main/java/queryhelper/utils/QueryUtils.java, queryhelper/src/main/java/queryhelper/utils/FileUtils.java
Added QueryUtils.executeQueryAndGetRecords() for JDBC-level query execution; expanded FileUtils with createCSVFile(), generateTablePreview(), and buildRowSeparator() for output formatting.
Service Refactoring
queryhelper/src/main/java/queryhelper/service/AIService.java
Extracted inline query execution and CSV/preview generation logic into static utility methods; now uses static imports to delegate these responsibilities.
Documentation & Testing
README.md, docs/markdowns/sql.md
Added documentation entry in README and new markdown file detailing the sql command syntax, options, and examples.
Integration Test Update
cli/src/test/java/integration/SqlserverIntegrationTest.java
Replaced container image from fabricioveronez/northwind-database to mcr.microsoft.com/mssql/server:2022-latest and updated instantiation to pass the image constant.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant CLI as Cli
    participant QueryService as QueryService
    participant QueryUtils as QueryUtils
    participant FileUtils as FileUtils
    participant DB as Database
    
    User->>CLI: sql -s source -q "SELECT ..." -l 200
    activate CLI
    CLI->>CLI: Resolve source connection
    CLI->>CLI: Prepare output directory
    CLI->>QueryService: executeQuery(query, source, limit, ...)
    deactivate CLI
    
    activate QueryService
    QueryService->>QueryUtils: executeQueryAndGetRecords(query, source, limit)
    activate QueryUtils
    QueryUtils->>DB: Execute SQL query
    DB-->>QueryUtils: Result set
    QueryUtils->>QueryUtils: Map results to List<Map>
    QueryUtils-->>QueryService: Query results
    deactivate QueryUtils
    
    QueryService->>FileUtils: createCSVFile(data, filename, ...)
    activate FileUtils
    FileUtils->>FileUtils: Write CSV from records
    FileUtils-->>QueryService: CSV file path
    deactivate FileUtils
    
    QueryService->>FileUtils: generateTablePreview(csvFile, limit)
    activate FileUtils
    FileUtils->>FileUtils: Format ASCII table
    FileUtils-->>QueryService: Table preview string
    deactivate FileUtils
    
    QueryService->>QueryService: Build response message
    QueryService-->>CLI: Response with CSV path & preview
    activate CLI
    CLI->>User: Log result message
    deactivate CLI
    deactivate QueryService
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A SQL query leaps from the CLI,
Through services swift, utilities fly,
CSVs take shape, tables appear,
Data flows free—no query too dear! 📊✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feature/sql command' directly corresponds to the main change: introducing a new 'sql' CLI subcommand with supporting infrastructure.
✨ Finishing touches
  • 📝 Generate docstrings

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@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: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
queryhelper/src/main/java/queryhelper/service/AIService.java (1)

36-41: Dead code: errorResponse is created but never returned.

The errorResponse object is created when the query is not a SELECT statement, but the method continues execution regardless. This appears to be incomplete logic - either the response should be returned, or this block should be removed.

🔎 Proposed fix - return early for non-SELECT statements
         boolean selectStatement = isSelectStatement(query);
         if (!selectStatement) {
             GenericResponse errorResponse = new GenericResponse();
             errorResponse.setMessage("Generated query, execute on your own will: " + query);
             errorResponse.setStatusCode(200);
+            return errorResponse;
         }
queryhelper/src/main/java/queryhelper/utils/FileUtils.java (1)

50-69: CSV generation doesn't properly escape values.

The convertToCSV method doesn't handle values containing commas, quotes, or newlines per RFC 4180. If a column value contains a comma (e.g., "Smith, John"), the CSV will be malformed and generateTablePreview will parse it incorrectly.

🔎 Proposed fix - add proper CSV escaping
     public static void convertToCSV(String fileName, List<Map<String, Object>> list) {
         try (FileWriter csvWriter = new FileWriter(fileName)) {
             if (!list.isEmpty()) {
                 Set<String> headers = list.get(0).keySet();
                 StringBuilder csvContent = new StringBuilder();
-                csvContent.append(String.join(",", headers)).append("\n");
+                csvContent.append(headers.stream().map(FileUtils::escapeCsvValue).collect(Collectors.joining(","))).append("\n");

                 for (Map<String, Object> map : list) {
                     for (String header : headers) {
-                        csvContent.append(map.getOrDefault(header, "")).append(",");
+                        Object value = map.getOrDefault(header, "");
+                        csvContent.append(escapeCsvValue(String.valueOf(value))).append(",");
                     }
                     csvContent.setLength(csvContent.length() - 1);
                     csvContent.append("\n");
                 }
                 csvWriter.write(csvContent.toString());
             }
         } catch (IOException e) {
             e.printStackTrace();
         }
     }

+    private static String escapeCsvValue(String value) {
+        if (value == null) return "";
+        if (value.contains(",") || value.contains("\"") || value.contains("\n")) {
+            return "\"" + value.replace("\"", "\"\"") + "\"";
+        }
+        return value;
+    }
🤖 Fix all issues with AI Agents
In @cli/src/test/java/integration/SqlserverIntegrationTest.java:
- Around line 38-41: The DATABASE constant is unused; either remove the DATABASE
field or set the SQL Server container to use it by adding
.withDatabaseName(DATABASE) to the MSSQLServerContainer initialization (the code
that constructs the container in SqlserverIntegrationTest), ensuring you
reference the existing IMAGE, USERNAME, PASSWORD constants and keep the
MSSQLServerContainer configuration consistent with other tests.

In @docs/markdowns/sql.md:
- Around line 2-3: Update the documentation in sql.md to fix the grammar (change
"you" to "your" in the two occurrences flagged) and align the CLI option name
with the implementation by changing the synopsis flag from `-q, --sql` to `-q,
--query` (or vice versa if you prefer changing the code in Cli.java to `--sql`);
ensure the example and any references in sql.md match the unique option name
used by the CLI implementation (look for the `-q`/`--sql`/`--query` mention and
the `sql` command description) so docs and the Cli.java option names are
consistent.
- Line 10: Fix the typo in the SQL flag description by changing "you" to "your"
so the line reads: q --sql "SQL Query Code" | specify the query you want to run
in your connected DB; update the string in the docs/markdowns/sql.md entry for
the SQL flag accordingly.

In @queryhelper/src/main/java/queryhelper/utils/FileUtils.java:
- Around line 71-109: In FileUtils.generateTablePreview the current
String.split(",") mis-parses quoted CSV fields; replace the naive split with a
real CSV parser (e.g., OpenCSV's CSVReader/CSVParser or Apache Commons CSV
CSVFormat) to correctly handle quoted fields and embedded commas, update imports
and dependencies accordingly, read each record via the parser into String[] rows
(keeping the rest of the table width/formatting logic unchanged), and ensure
IOException handling remains in place for parser errors.
- Around line 28-47: The filename sanitization in FileUtils.createCSVFile is too
weak (only replaces whitespace via csvFileName.replaceAll("\\s+", "_")), so
update that logic to strip or replace all filesystem-invalid characters (e.g., /
\ : * ? " < > | ; and control characters) and collapse runs into a safe subset
(letters, numbers, hyphen, underscore, dot), truncate excessively long names,
and fallback to a timestamp-only name if the sanitized result is empty; apply
this sanitization before building fileName and keep the rest of createCSVFile
(including the timestamp suffix) unchanged.

In @queryhelper/src/main/java/queryhelper/utils/QueryUtils.java:
- Around line 16-29: In executeQueryAndGetRecords, jdbcConnection and statement
are never closed and setMaxRows(showRowLimit) will NPE when showRowLimit is
null; fix by using try-with-resources to open the java.sql.Connection
jdbcConnection and Statement statement so both are automatically closed (wrap
driver.connect(...) and jdbcConnection.createStatement() in a try-with-resources
block), only call statement.setMaxRows(showRowLimit) when showRowLimit != null
(and optionally > 0), and then call QueryHelper.select(statement, query) inside
that try block; keep the existing SQLException handling but ensure resources are
closed even on exceptions.
🧹 Nitpick comments (3)
cli/src/test/java/integration/SqlserverIntegrationTest.java (1)

38-38: Consider pinning to a specific version for reproducibility.

The :2022-latest tag will pull the latest patch version of SQL Server 2022, which could vary across test runs and environments. For maximum test reproducibility, consider pinning to a specific version like mcr.microsoft.com/mssql/server:2022-CU10-ubuntu-22.04.

queryhelper/src/main/java/queryhelper/service/QueryService.java (1)

18-44: Consider adding input validation and error handling.

The method lacks validation for null or empty query parameter and doesn't handle the case where executeQueryAndGetRecords might throw a RuntimeException (which wraps SQLException). The caller may benefit from more specific error messages.

cli/src/main/java/com/adaptivescale/rosetta/cli/Cli.java (1)

753-778: Significant code duplication with the query command.

The path resolution logic (lines 765-773) is nearly identical to lines 733-741 in the query command. Consider extracting this into a helper method to reduce duplication and improve maintainability.

🔎 Proposed helper method extraction
// Add this helper method to the Cli class
private PathInfo resolveOutputPaths(String sourceName, Path output) throws IOException {
    Path sourceWorkspace = Paths.get("./", sourceName);
    Path dataDirectory = output != null ? output : sourceWorkspace.resolve(DEFAULT_OUTPUT_DIRECTORY);
    Path outputFile = dataDirectory.getFileName().toString().contains(".") ? dataDirectory.getFileName() : null;
    
    dataDirectory = dataDirectory.getFileName().toString().contains(".") 
        ? (dataDirectory.getParent() != null ? dataDirectory.getParent() : Paths.get(".")) 
        : dataDirectory;
    
    if (!dataDirectory.toFile().exists()) {
        Files.createDirectories(dataDirectory);
    }
    return new PathInfo(dataDirectory, outputFile);
}

// Simple record/class to hold the result
private record PathInfo(Path dataDirectory, Path outputFile) {}

Then both sql and query commands can use:

PathInfo paths = resolveOutputPaths(sourceName, output);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 79eb12c and ced3e2e.

📒 Files selected for processing (8)
  • README.md
  • cli/src/main/java/com/adaptivescale/rosetta/cli/Cli.java
  • cli/src/test/java/integration/SqlserverIntegrationTest.java
  • docs/markdowns/sql.md
  • queryhelper/src/main/java/queryhelper/service/AIService.java
  • queryhelper/src/main/java/queryhelper/service/QueryService.java
  • queryhelper/src/main/java/queryhelper/utils/FileUtils.java
  • queryhelper/src/main/java/queryhelper/utils/QueryUtils.java
🧰 Additional context used
🧬 Code graph analysis (5)
queryhelper/src/main/java/queryhelper/utils/QueryUtils.java (4)
common/src/main/java/com/adaptivescale/rosetta/common/DriverManagerDriverProvider.java (1)
  • DriverManagerDriverProvider (9-14)
common/src/main/java/com/adaptivescale/rosetta/common/JDBCUtils.java (1)
  • JDBCUtils (7-25)
common/src/main/java/com/adaptivescale/rosetta/common/models/input/Connection.java (1)
  • Connection (9-91)
source/src/main/java/com/adataptivescale/rosetta/source/common/QueryHelper.java (1)
  • QueryHelper (17-116)
queryhelper/src/main/java/queryhelper/service/QueryService.java (4)
queryhelper/src/main/java/queryhelper/pojo/GenericResponse.java (1)
  • GenericResponse (3-38)
queryhelper/src/main/java/queryhelper/pojo/QueryDataResponse.java (1)
  • QueryDataResponse (6-50)
queryhelper/src/main/java/queryhelper/utils/FileUtils.java (1)
  • FileUtils (15-123)
queryhelper/src/main/java/queryhelper/utils/QueryUtils.java (1)
  • QueryUtils (15-30)
queryhelper/src/main/java/queryhelper/utils/FileUtils.java (3)
queryhelper/src/main/java/queryhelper/pojo/GenericResponse.java (1)
  • GenericResponse (3-38)
queryhelper/src/main/java/queryhelper/pojo/QueryDataResponse.java (1)
  • QueryDataResponse (6-50)
queryhelper/src/main/java/queryhelper/utils/ErrorUtils.java (1)
  • ErrorUtils (9-56)
queryhelper/src/main/java/queryhelper/service/AIService.java (2)
queryhelper/src/main/java/queryhelper/utils/FileUtils.java (1)
  • FileUtils (15-123)
queryhelper/src/main/java/queryhelper/utils/QueryUtils.java (1)
  • QueryUtils (15-30)
cli/src/main/java/com/adaptivescale/rosetta/cli/Cli.java (1)
queryhelper/src/main/java/queryhelper/service/QueryService.java (1)
  • QueryService (16-45)
🪛 LanguageTool
docs/markdowns/sql.md

[grammar] ~3-~3: Ensure spelling is correct
Context: ...NECTION_NAME] [-q, --sql "Write SQL for you Schema"] [--output "Output DIRECTORY or...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~10-~10: Ensure spelling is correct
Context: ... | specify the query you want to run in you connected DB. -l --limit Response Row l...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 markdownlint-cli2 (0.18.1)
docs/markdowns/sql.md

2-2: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)


22-22: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

🔇 Additional comments (6)
cli/src/test/java/integration/SqlserverIntegrationTest.java (1)

172-174: LGTM! Good use of the IMAGE constant.

The container instantiation correctly uses the parameterized constructor with the IMAGE constant, which centralizes the configuration and makes it easier to update the image version.

README.md (1)

143-144: LGTM!

The documentation entry for the new SQL command is consistent with the existing command list format.

queryhelper/src/main/java/queryhelper/service/AIService.java (1)

18-21: LGTM on the refactoring to use shared utilities.

The static imports properly delegate to the new utility classes.

cli/src/main/java/com/adaptivescale/rosetta/cli/Cli.java (1)

45-45: LGTM on the import addition.

queryhelper/src/main/java/queryhelper/utils/FileUtils.java (2)

3-9: LGTM on the import additions.


111-121: LGTM on the row separator builder.

The implementation correctly generates ASCII table borders based on column widths.

Comment on lines +38 to 41
private static String IMAGE = "mcr.microsoft.com/mssql/server:2022-latest";
private static String USERNAME = "SA";
private static String PASSWORD = "123abcD!";
private static String DATABASE = "Northwind";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n cli/src/test/java/integration/SqlserverIntegrationTest.java

Repository: rosettadb/rosetta

Length of output: 26183


🏁 Script executed:

rg -n "DATABASE" cli/src/test/java/integration/ --type=java -C 2

Repository: rosettadb/rosetta

Length of output: 6599


🏁 Script executed:

rg -n "withDatabaseName" cli/src/test/java/integration/ --type=java

Repository: rosettadb/rosetta

Length of output: 437


Remove unused DATABASE constant or add .withDatabaseName(DATABASE) to container initialization.

The DATABASE constant defined on line 41 is never used. Other integration tests (MySQL, DB2, Oracle) properly configure their database names with .withDatabaseName(DATABASE), but SqlserverIntegrationTest is missing this configuration. Either add .withDatabaseName(DATABASE) to the MSSQLServerContainer initialization on line 172, or remove the unused constant.

🤖 Prompt for AI Agents
In @cli/src/test/java/integration/SqlserverIntegrationTest.java around lines 38
- 41, The DATABASE constant is unused; either remove the DATABASE field or set
the SQL Server container to use it by adding .withDatabaseName(DATABASE) to the
MSSQLServerContainer initialization (the code that constructs the container in
SqlserverIntegrationTest), ensuring you reference the existing IMAGE, USERNAME,
PASSWORD constants and keep the MSSQLServerContainer configuration consistent
with other tests.

Comment on lines +2 to +3
The sql commands allows the user to write SQL queries directly to the connected Database of his choice.
rosetta [-c, --config CONFIG_FILE] sql [-h, --help] [-s, --source CONNECTION_NAME] [-q, --sql "Write SQL for you Schema"] [--output "Output DIRECTORY or FILE"]
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix grammar: "you" should be "your".

Based on static analysis hints, there are grammar issues on lines 3 and 10 where "you" should be "your".

Also, the command synopsis shows -q, --sql but the CLI implementation in Cli.java uses -q, --query. Please align the documentation with the actual CLI option names.

🔎 Proposed fix
-The sql commands allows the user to write SQL queries directly to the connected Database of his choice.
-    rosetta [-c, --config CONFIG_FILE] sql [-h, --help] [-s, --source CONNECTION_NAME] [-q, --sql "Write SQL for you Schema"] [--output "Output DIRECTORY or FILE"]
+The sql command allows the user to write SQL queries directly to the connected database of their choice.
+    rosetta [-c, --config CONFIG_FILE] sql [-h, --help] [-s, --source CONNECTION_NAME] [-q, --query "Your SQL Query"] [-l, --limit ROW_LIMIT] [--no-limit] [--output "Output DIRECTORY or FILE"]

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 LanguageTool

[grammar] ~3-~3: Ensure spelling is correct
Context: ...NECTION_NAME] [-q, --sql "Write SQL for you Schema"] [--output "Output DIRECTORY or...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 markdownlint-cli2 (0.18.1)

2-2: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)

🤖 Prompt for AI Agents
In @docs/markdowns/sql.md around lines 2 - 3, Update the documentation in sql.md
to fix the grammar (change "you" to "your" in the two occurrences flagged) and
align the CLI option name with the implementation by changing the synopsis flag
from `-q, --sql` to `-q, --query` (or vice versa if you prefer changing the code
in Cli.java to `--sql`); ensure the example and any references in sql.md match
the unique option name used by the CLI implementation (look for the
`-q`/`--sql`/`--query` mention and the `sql` command description) so docs and
the Cli.java option names are consistent.

-h, --help | Show the help message and exit.
-c, --config CONFIG_FILE | YAML config file. If none is supplied it will use main.conf in the current directory if it exists.
-s, --source CONNECTION_NAME | The source connection is used to specify which models and connection to use.
-q --sql "SQL Query Code" | specify the query you want to run in you connected DB.
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix grammar: "you" should be "your".

🔎 Proposed fix
--q --sql "SQL Query Code"  | specify the query you want to run in you connected DB.
+-q, --query "SQL Query Code"  | Specify the query you want to run in your connected DB.
🧰 Tools
🪛 LanguageTool

[grammar] ~10-~10: Ensure spelling is correct
Context: ... | specify the query you want to run in you connected DB. -l --limit Response Row l...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🤖 Prompt for AI Agents
In @docs/markdowns/sql.md at line 10, Fix the typo in the SQL flag description
by changing "you" to "your" so the line reads: q --sql "SQL Query Code" |
specify the query you want to run in your connected DB; update the string in the
docs/markdowns/sql.md entry for the SQL flag accordingly.

response.setData(data);
response.setStatusCode(200);

String csvFile = createCSVFile(data, query, dataDirectory, outputFileName);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

SQL query as filename may contain invalid characters.

Using the raw SQL query as the CSV filename base can result in invalid filenames. SQL queries often contain characters like ;, *, /, \, ", ', etc., which are problematic for file systems. The createCSVFile method only replaces whitespace with underscores.

🔎 Proposed fix - sanitize the filename
-        String csvFile = createCSVFile(data, query, dataDirectory, outputFileName);
+        // Sanitize query for use as filename - keep only alphanumeric chars and limit length
+        String sanitizedQuery = query.replaceAll("[^a-zA-Z0-9]", "_").replaceAll("_+", "_");
+        if (sanitizedQuery.length() > 50) {
+            sanitizedQuery = sanitizedQuery.substring(0, 50);
+        }
+        String csvFile = createCSVFile(data, sanitizedQuery, dataDirectory, outputFileName);
📝 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
String csvFile = createCSVFile(data, query, dataDirectory, outputFileName);
// Sanitize query for use as filename - keep only alphanumeric chars and limit length
String sanitizedQuery = query.replaceAll("[^a-zA-Z0-9]", "_").replaceAll("_+", "_");
if (sanitizedQuery.length() > 50) {
sanitizedQuery = sanitizedQuery.substring(0, 50);
}
String csvFile = createCSVFile(data, sanitizedQuery, dataDirectory, outputFileName);

Comment on lines +28 to +47
public static String createCSVFile(QueryDataResponse queryDataResponse, String csvFileName, Path dataDirectory, Path outputFileName) {
try {
if (outputFileName == null) {
String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String fileName = csvFileName.replaceAll("\\s+", "_") + "_" + timestamp + ".csv";
Path csvFilePath = dataDirectory.resolve(fileName);
FileUtils.convertToCSV(csvFilePath.toString(), queryDataResponse.getRecords());

return csvFilePath.toString();
}

Path csvFilePath = dataDirectory.resolve(outputFileName.toString());
FileUtils.convertToCSV(csvFilePath.toString(), queryDataResponse.getRecords());
return csvFilePath.toString();

} catch (Exception e) {
GenericResponse genericResponse = ErrorUtils.csvFileError(e);
throw new RuntimeException(genericResponse.getMessage());
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Filename sanitization is insufficient.

The replaceAll("\\s+", "_") only handles whitespace. If csvFileName contains other filesystem-invalid characters (;, *, /, \, ", <, >, |, etc.), file creation may fail or produce unexpected results.

🔎 Proposed fix
             if (outputFileName == null) {
                 String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
-                String fileName = csvFileName.replaceAll("\\s+", "_") + "_" + timestamp + ".csv";
+                String sanitized = csvFileName.replaceAll("[^a-zA-Z0-9._-]", "_").replaceAll("_+", "_");
+                if (sanitized.length() > 100) {
+                    sanitized = sanitized.substring(0, 100);
+                }
+                String fileName = sanitized + "_" + timestamp + ".csv";
                 Path csvFilePath = dataDirectory.resolve(fileName);
🤖 Prompt for AI Agents
In @queryhelper/src/main/java/queryhelper/utils/FileUtils.java around lines 28 -
47, The filename sanitization in FileUtils.createCSVFile is too weak (only
replaces whitespace via csvFileName.replaceAll("\\s+", "_")), so update that
logic to strip or replace all filesystem-invalid characters (e.g., / \ : * ? " <
> | ; and control characters) and collapse runs into a safe subset (letters,
numbers, hyphen, underscore, dot), truncate excessively long names, and fallback
to a timestamp-only name if the sanitized result is empty; apply this
sanitization before building fileName and keep the rest of createCSVFile
(including the timestamp suffix) unchanged.

Comment on lines +71 to +109
public static String generateTablePreview(String csvFile, int rowLimit) {
List<String[]> rows = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new FileReader(csvFile))) {
String line;
int rowCount = 0;
while ((line = reader.readLine()) != null && rowCount < rowLimit) {
String[] columns = line.split(",");
rows.add(columns);
rowCount++;
}
} catch (IOException e) {
throw new RuntimeException("Error reading CSV file", e);
}

if (rows.isEmpty()) {
return "No data available to display.";
}
int maxColumns = rows.stream().mapToInt(row -> row.length).max().orElse(0);
int[] columnWidths = new int[maxColumns];
for (String[] row : rows) {
for (int i = 0; i < row.length; i++) {
columnWidths[i] = Math.max(columnWidths[i], row[i].length());
}
}
StringBuilder table = new StringBuilder();
String rowSeparator = buildRowSeparator(columnWidths);

table.append(rowSeparator);
for (String[] row : rows) {
table.append("|");
for (int i = 0; i < maxColumns; i++) {
String cell = (i < row.length) ? row[i] : "";
table.append(" ").append(String.format("%-" + columnWidths[i] + "s", cell)).append(" |");
}
table.append("\n").append(rowSeparator);
}

return table.toString();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

CSV parsing with split(",") doesn't handle quoted values.

If CSV values contain commas (which is valid CSV when quoted), the simple split(",") will incorrectly split the value. This will cause misaligned columns in the table preview.

Consider using a proper CSV parser or at minimum handling quoted values.

🤖 Prompt for AI Agents
In @queryhelper/src/main/java/queryhelper/utils/FileUtils.java around lines 71 -
109, In FileUtils.generateTablePreview the current String.split(",") mis-parses
quoted CSV fields; replace the naive split with a real CSV parser (e.g.,
OpenCSV's CSVReader/CSVParser or Apache Commons CSV CSVFormat) to correctly
handle quoted fields and embedded commas, update imports and dependencies
accordingly, read each record via the parser into String[] rows (keeping the
rest of the table width/formatting logic unchanged), and ensure IOException
handling remains in place for parser errors.

Comment on lines +16 to +29
public static List<Map<String, Object>> executeQueryAndGetRecords(String query, Connection source, Integer showRowLimit) {
try {
DriverManagerDriverProvider driverManagerDriverProvider = new DriverManagerDriverProvider();
Driver driver = driverManagerDriverProvider.getDriver(source);
Properties properties = JDBCUtils.setJDBCAuth(source);
java.sql.Connection jdbcConnection = driver.connect(source.getUrl(), properties);
Statement statement = jdbcConnection.createStatement();
statement.setMaxRows(showRowLimit);
List<Map<String, Object>> select = QueryHelper.select(statement, query);
return select;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: JDBC resources are never closed, causing resource leaks.

The jdbcConnection and statement are never closed, which will exhaust database connection pools and cause memory leaks. Additionally, if showRowLimit is null, setMaxRows(showRowLimit) will throw a NullPointerException due to auto-unboxing.

🔎 Proposed fix using try-with-resources
 public class QueryUtils {
     public static List<Map<String, Object>> executeQueryAndGetRecords(String query, Connection source, Integer showRowLimit) {
+        DriverManagerDriverProvider driverManagerDriverProvider = new DriverManagerDriverProvider();
         try {
-            DriverManagerDriverProvider driverManagerDriverProvider = new DriverManagerDriverProvider();
             Driver driver = driverManagerDriverProvider.getDriver(source);
             Properties properties = JDBCUtils.setJDBCAuth(source);
-            java.sql.Connection jdbcConnection = driver.connect(source.getUrl(), properties);
-            Statement statement = jdbcConnection.createStatement();
-            statement.setMaxRows(showRowLimit);
-            List<Map<String, Object>> select = QueryHelper.select(statement, query);
-            return select;
+            try (java.sql.Connection jdbcConnection = driver.connect(source.getUrl(), properties);
+                 Statement statement = jdbcConnection.createStatement()) {
+                if (showRowLimit != null && showRowLimit > 0) {
+                    statement.setMaxRows(showRowLimit);
+                }
+                return QueryHelper.select(statement, query);
+            }
         } catch (SQLException e) {
             throw new RuntimeException(e);
         }
     }
 }
📝 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
public static List<Map<String, Object>> executeQueryAndGetRecords(String query, Connection source, Integer showRowLimit) {
try {
DriverManagerDriverProvider driverManagerDriverProvider = new DriverManagerDriverProvider();
Driver driver = driverManagerDriverProvider.getDriver(source);
Properties properties = JDBCUtils.setJDBCAuth(source);
java.sql.Connection jdbcConnection = driver.connect(source.getUrl(), properties);
Statement statement = jdbcConnection.createStatement();
statement.setMaxRows(showRowLimit);
List<Map<String, Object>> select = QueryHelper.select(statement, query);
return select;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static List<Map<String, Object>> executeQueryAndGetRecords(String query, Connection source, Integer showRowLimit) {
DriverManagerDriverProvider driverManagerDriverProvider = new DriverManagerDriverProvider();
try {
Driver driver = driverManagerDriverProvider.getDriver(source);
Properties properties = JDBCUtils.setJDBCAuth(source);
try (java.sql.Connection jdbcConnection = driver.connect(source.getUrl(), properties);
Statement statement = jdbcConnection.createStatement()) {
if (showRowLimit != null && showRowLimit > 0) {
statement.setMaxRows(showRowLimit);
}
return QueryHelper.select(statement, query);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
🤖 Prompt for AI Agents
In @queryhelper/src/main/java/queryhelper/utils/QueryUtils.java around lines 16
- 29, In executeQueryAndGetRecords, jdbcConnection and statement are never
closed and setMaxRows(showRowLimit) will NPE when showRowLimit is null; fix by
using try-with-resources to open the java.sql.Connection jdbcConnection and
Statement statement so both are automatically closed (wrap driver.connect(...)
and jdbcConnection.createStatement() in a try-with-resources block), only call
statement.setMaxRows(showRowLimit) when showRowLimit != null (and optionally >
0), and then call QueryHelper.select(statement, query) inside that try block;
keep the existing SQLException handling but ensure resources are closed even on
exceptions.

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.

2 participants