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