Added export to file button with CSV support.
This commit is contained in:
parent
3908515ca4
commit
408d5e415d
|
@ -3,6 +3,7 @@ package com.andrewlalis.perfin.control;
|
|||
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
|
||||
import com.andrewlalis.perfin.data.SavedQueryRepository;
|
||||
import com.andrewlalis.perfin.data.impl.JdbcDataSource;
|
||||
import com.andrewlalis.perfin.data.util.FileUtil;
|
||||
import com.andrewlalis.perfin.model.Profile;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
|
@ -10,12 +11,17 @@ import javafx.scene.control.*;
|
|||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.stage.Window;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
|
@ -42,12 +48,7 @@ public class SqlConsoleViewController implements RouteSelectionListener {
|
|||
}
|
||||
|
||||
@FXML public void executeQuery() {
|
||||
String queryText = sqlEditorTextArea.getText().strip();
|
||||
String[] rawQueries = queryText.split("\\s*;\\s*");
|
||||
List<String> queries = Arrays.stream(rawQueries)
|
||||
.filter(s -> !s.isBlank())
|
||||
.filter(s -> !s.startsWith("#") && !s.startsWith("//"))
|
||||
.toList();
|
||||
List<String> queries = getCurrentQueries();
|
||||
outputTextArea.clear();
|
||||
JdbcDataSource dataSource = (JdbcDataSource) Profile.getCurrent().dataSource();
|
||||
try (
|
||||
|
@ -107,6 +108,71 @@ public class SqlConsoleViewController implements RouteSelectionListener {
|
|||
}
|
||||
}
|
||||
|
||||
@FXML public void exportToFile() {
|
||||
if (sqlEditorTextArea.getText().isBlank()) {
|
||||
Popups.message(sqlEditorTextArea, "Cannot export the results of an empty query.");
|
||||
return;
|
||||
}
|
||||
if (getCurrentQueries().size() > 1) {
|
||||
Popups.message(sqlEditorTextArea, "Note: Export to file will only export the results of your first query.");
|
||||
}
|
||||
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle("Export to File");
|
||||
fileChooser.setInitialFileName("export.csv");
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(
|
||||
"CSV Files", ".csv"
|
||||
));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(
|
||||
"JSON Files", ".json"
|
||||
));
|
||||
fileChooser.setInitialDirectory(Profile.getCurrent().dataSource().getContentDir().toFile());
|
||||
File chosenFile = fileChooser.showSaveDialog(sqlEditorTextArea.getScene().getWindow());
|
||||
if (chosenFile == null) return;
|
||||
|
||||
String name = chosenFile.getName().strip().toLowerCase();
|
||||
if (!name.endsWith(".csv") && !name.endsWith(".json")) {
|
||||
Popups.error(sqlEditorTextArea, "Invalid file format. Only CSV and JSON are permitted.");
|
||||
return;
|
||||
}
|
||||
String query = getCurrentQueries().getFirst();
|
||||
JdbcDataSource dataSource = (JdbcDataSource) Profile.getCurrent().dataSource();
|
||||
try (
|
||||
var conn = dataSource.getConnection();
|
||||
var stmt = conn.createStatement()
|
||||
) {
|
||||
ResultSet rs = stmt.executeQuery(query);
|
||||
if (name.endsWith(".csv")) {
|
||||
writeQueryResultsToCsv(rs, chosenFile.toPath());
|
||||
} else if (name.endsWith(".json")) {
|
||||
// writeQueryResultsToJson(rs, chosenFile.toPath());
|
||||
Popups.message(sqlEditorTextArea, "JSON not yet supported.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Popups.error(sqlEditorTextArea, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeQueryResultsToCsv(ResultSet rs, Path file) throws SQLException, IOException {
|
||||
try (var out = Files.newOutputStream(file); var writer = new PrintWriter(out)) {
|
||||
final int columnCount = rs.getMetaData().getColumnCount();
|
||||
// First write the header.
|
||||
for (int i = 1; i <= columnCount; i++) {
|
||||
writer.append(FileUtil.escapeCSVText(rs.getMetaData().getColumnLabel(i)));
|
||||
if (i < columnCount) writer.append(',');
|
||||
}
|
||||
writer.println();
|
||||
// Then write the body rows.
|
||||
while (rs.next()) {
|
||||
for (int i = 1; i <= columnCount; i++) {
|
||||
writer.append(FileUtil.escapeCSVText(rs.getString(i)));
|
||||
if (i < columnCount) writer.append(',');
|
||||
}
|
||||
writer.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshSavedQueries() {
|
||||
savedQueriesVBox.getChildren().clear();
|
||||
List<String> savedQueries = Profile.getCurrent().dataSource()
|
||||
|
@ -147,6 +213,15 @@ public class SqlConsoleViewController implements RouteSelectionListener {
|
|||
return pane;
|
||||
}
|
||||
|
||||
private List<String> getCurrentQueries() {
|
||||
String queryText = sqlEditorTextArea.getText().strip();
|
||||
String[] rawQueries = queryText.split("\\s*;\\s*");
|
||||
return Arrays.stream(rawQueries)
|
||||
.filter(s -> !s.isBlank())
|
||||
.filter(s -> !s.startsWith("#") && !s.startsWith("//"))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@FXML public void showSchema() {
|
||||
SchemaDialog dialog = new SchemaDialog(sqlEditorTextArea.getScene().getWindow());
|
||||
dialog.show();
|
||||
|
|
|
@ -114,4 +114,10 @@ public class FileUtil {
|
|||
in.transferTo(out);
|
||||
}
|
||||
}
|
||||
|
||||
public static String escapeCSVText(String raw) {
|
||||
if (raw == null) return "NULL";
|
||||
if (!raw.contains("\"") && !raw.contains(",") && !raw.contains(";")) return raw;
|
||||
return '"' + raw.replaceAll("\"", "\"\"") + '"';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
<Button text="Execute Query" onAction="#executeQuery"/>
|
||||
<Button text="View Schema" onAction="#showSchema"/>
|
||||
<Button text="Save Query" onAction="#saveQuery"/>
|
||||
<Button text="Export to File" onAction="#exportToFile"/>
|
||||
</HBox>
|
||||
<ScrollPane fitToHeight="true" fitToWidth="true">
|
||||
<TextArea
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package com.andrewlalis.perfin.data.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class FileUtilTest {
|
||||
@Test
|
||||
public void testEscapeCSVText() {
|
||||
assertEquals("regular_value", FileUtil.escapeCSVText("regular_value"));
|
||||
assertEquals("\"\"\"\"", FileUtil.escapeCSVText("\""));
|
||||
assertEquals("\"\"\",\"\"\"", FileUtil.escapeCSVText("\",\""));
|
||||
assertEquals("\"and, this\"", FileUtil.escapeCSVText("and, this"));
|
||||
assertEquals("\"1,345.43\"", FileUtil.escapeCSVText("1,345.43"));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue